From fe63c8d5e6e841f474eec96e2dd38d8fa97a17f8 Mon Sep 17 00:00:00 2001 From: Jeff Vander Stoep Date: Thu, 18 Jun 2015 07:52:02 -0700 Subject: [PATCH] SELinux: python modules for selinux tools : audit2allow audit2why: sepolgen selinux sesearch: setools setoolsgui Change-Id: Ifc19252d8d8e178b86c51fe1f54e162a61ffc0dd --- .../site-packages/selinux/__init__.py | 2445 +++ .../site-packages/selinux/_selinux.so | Bin 0 -> 333725 bytes .../site-packages/selinux/audit2why.so | Bin 0 -> 243205 bytes .../site-packages/sepolgen/__init__.py | 0 .../site-packages/sepolgen/access.py | 331 + lib/python2.7/site-packages/sepolgen/audit.py | 549 + .../site-packages/sepolgen/classperms.py | 116 + .../site-packages/sepolgen/defaults.py | 77 + .../site-packages/sepolgen/interfaces.py | 509 + lib/python2.7/site-packages/sepolgen/lex.py | 866 ++ .../site-packages/sepolgen/matching.py | 255 + .../site-packages/sepolgen/module.py | 213 + .../site-packages/sepolgen/objectmodel.py | 172 + .../site-packages/sepolgen/output.py | 173 + .../site-packages/sepolgen/policygen.py | 402 + .../site-packages/sepolgen/refparser.py | 1128 ++ .../site-packages/sepolgen/refpolicy.py | 917 ++ .../site-packages/sepolgen/sepolgeni18n.py | 26 + lib/python2.7/site-packages/sepolgen/util.py | 87 + lib/python2.7/site-packages/sepolgen/yacc.py | 2209 +++ .../site-packages/setools/__init__.py | 68 + .../site-packages/setools/boolquery.py | 66 + .../site-packages/setools/categoryquery.py | 55 + .../site-packages/setools/commonquery.py | 60 + .../site-packages/setools/compquery.py | 39 + .../site-packages/setools/constraintquery.py | 142 + .../site-packages/setools/contextquery.py | 98 + .../site-packages/setools/descriptors.py | 230 + lib/python2.7/site-packages/setools/dta.py | 603 + .../site-packages/setools/exception.py | 62 + .../site-packages/setools/fsusequery.py | 87 + .../site-packages/setools/genfsconquery.py | 98 + .../site-packages/setools/infoflow.py | 403 + .../site-packages/setools/initsidquery.py | 74 + lib/python2.7/site-packages/setools/mixins.py | 91 + .../site-packages/setools/mlsrulequery.py | 115 + .../site-packages/setools/netifconquery.py | 77 + .../site-packages/setools/nodeconquery.py | 148 + .../site-packages/setools/objclassquery.py | 101 + .../site-packages/setools/permmap.py | 363 + .../site-packages/setools/polcapquery.py | 47 + .../setools/policyrep/__init__.py | 568 + .../site-packages/setools/policyrep/_qpol.so | Bin 0 -> 2151445 bytes .../setools/policyrep/boolcond.py | 167 + .../setools/policyrep/constraint.py | 297 + .../setools/policyrep/context.py | 68 + .../setools/policyrep/default.py | 128 + .../setools/policyrep/exception.py | 248 + .../setools/policyrep/fscontext.py | 123 + .../setools/policyrep/initsid.py | 50 + .../site-packages/setools/policyrep/mls.py | 463 + .../setools/policyrep/mlsrule.py | 62 + .../setools/policyrep/netcontext.py | 167 + .../setools/policyrep/objclass.py | 110 + .../site-packages/setools/policyrep/polcap.py | 40 + .../site-packages/setools/policyrep/qpol.py | 1114 ++ .../setools/policyrep/rbacrule.py | 92 + .../site-packages/setools/policyrep/role.py | 81 + .../site-packages/setools/policyrep/rule.py | 72 + .../site-packages/setools/policyrep/symbol.py | 74 + .../site-packages/setools/policyrep/terule.py | 155 + .../setools/policyrep/typeattr.py | 174 + .../site-packages/setools/policyrep/user.py | 86 + .../site-packages/setools/portconquery.py | 146 + lib/python2.7/site-packages/setools/query.py | 192 + .../site-packages/setools/rbacrulequery.py | 147 + .../site-packages/setools/rolequery.py | 77 + .../site-packages/setools/sensitivityquery.py | 74 + .../site-packages/setools/terulequery.py | 178 + .../site-packages/setools/typeattrquery.py | 70 + .../site-packages/setools/typequery.py | 96 + .../site-packages/setools/userquery.py | 116 + .../site-packages/setoolsgui/__init__.py | 21 + .../site-packages/setoolsgui/apol/__init__.py | 24 + .../setoolsgui/apol/mainwindow.py | 261 + .../site-packages/setoolsgui/apol/models.py | 103 + .../setoolsgui/apol/rulemodels.py | 116 + .../setoolsgui/apol/terulequery.py | 271 + .../site-packages/setoolsgui/libselinux.so.1 | Bin 0 -> 172235 bytes .../setoolsgui/networkx/__init__.py | 85 + .../networkx/algorithms/__init__.py | 51 + .../algorithms/approximation/__init__.py | 6 + .../algorithms/approximation/clique.py | 97 + .../approximation/dominating_set.py | 114 + .../approximation/independent_set.py | 63 + .../algorithms/approximation/matching.py | 46 + .../algorithms/approximation/ramsey.py | 37 + .../approximation/tests/test_clique.py | 41 + .../tests/test_dominating_set.py | 53 + .../tests/test_independent_set.py | 8 + .../approximation/tests/test_matching.py | 8 + .../approximation/tests/test_ramsey.py | 27 + .../approximation/tests/test_vertex_cover.py | 39 + .../algorithms/approximation/vertex_cover.py | 65 + .../algorithms/assortativity/__init__.py | 5 + .../algorithms/assortativity/connectivity.py | 123 + .../algorithms/assortativity/correlation.py | 298 + .../algorithms/assortativity/mixing.py | 248 + .../assortativity/neighbor_degree.py | 133 + .../algorithms/assortativity/pairs.py | 134 + .../assortativity/tests/base_test.py | 50 + .../assortativity/tests/test_connectivity.py | 121 + .../assortativity/tests/test_correlation.py | 101 + .../assortativity/tests/test_mixing.py | 186 + .../tests/test_neighbor_degree.py | 82 + .../assortativity/tests/test_pairs.py | 113 + .../networkx/algorithms/bipartite/__init__.py | 93 + .../networkx/algorithms/bipartite/basic.py | 335 + .../algorithms/bipartite/centrality.py | 266 + .../networkx/algorithms/bipartite/cluster.py | 266 + .../algorithms/bipartite/projection.py | 497 + .../algorithms/bipartite/redundancy.py | 84 + .../networkx/algorithms/bipartite/spectral.py | 88 + .../algorithms/bipartite/tests/test_basic.py | 117 + .../bipartite/tests/test_centrality.py | 169 + .../bipartite/tests/test_cluster.py | 70 + .../bipartite/tests/test_project.py | 363 + .../tests/test_spectral_bipartivity.py | 93 + .../setoolsgui/networkx/algorithms/block.py | 115 + .../networkx/algorithms/boundary.py | 102 + .../algorithms/centrality/__init__.py | 20 + .../algorithms/centrality/betweenness.py | 334 + .../centrality/betweenness_subset.py | 263 + .../algorithms/centrality/closeness.py | 103 + .../centrality/communicability_alg.py | 495 + .../centrality/current_flow_betweenness.py | 361 + .../current_flow_betweenness_subset.py | 263 + .../centrality/current_flow_closeness.py | 127 + .../algorithms/centrality/degree_alg.py | 131 + .../algorithms/centrality/eigenvector.py | 169 + .../algorithms/centrality/flow_matrix.py | 139 + .../networkx/algorithms/centrality/katz.py | 296 + .../networkx/algorithms/centrality/load.py | 190 + .../tests/test_betweenness_centrality.py | 462 + .../test_betweenness_centrality_subset.py | 258 + .../tests/test_closeness_centrality.py | 93 + .../centrality/tests/test_communicability.py | 134 + ...est_current_flow_betweenness_centrality.py | 211 + ...rent_flow_betweenness_centrality_subset.py | 181 + .../tests/test_current_flow_closeness.py | 56 + .../tests/test_degree_centrality.py | 92 + .../tests/test_eigenvector_centrality.py | 123 + .../centrality/tests/test_katz_centrality.py | 289 + .../centrality/tests/test_load_centrality.py | 273 + .../networkx/algorithms/chordal/__init__.py | 3 + .../algorithms/chordal/chordal_alg.py | 347 + .../algorithms/chordal/tests/test_chordal.py | 59 + .../setoolsgui/networkx/algorithms/clique.py | 516 + .../setoolsgui/networkx/algorithms/cluster.py | 363 + .../networkx/algorithms/community/__init__.py | 1 + .../networkx/algorithms/community/kclique.py | 82 + .../community/tests/test_kclique.py | 46 + .../algorithms/components/__init__.py | 5 + .../algorithms/components/attracting.py | 133 + .../algorithms/components/biconnected.py | 417 + .../algorithms/components/connected.py | 192 + .../components/strongly_connected.py | 359 + .../components/tests/test_attracting.py | 64 + .../components/tests/test_biconnected.py | 191 + .../components/tests/test_connected.py | 72 + .../tests/test_strongly_connected.py | 138 + .../components/tests/test_weakly_connected.py | 88 + .../algorithms/components/weakly_connected.py | 126 + .../algorithms/connectivity/__init__.py | 4 + .../algorithms/connectivity/connectivity.py | 607 + .../networkx/algorithms/connectivity/cuts.py | 382 + .../connectivity/tests/test_connectivity.py | 145 + .../connectivity/tests/test_cuts.py | 157 + .../setoolsgui/networkx/algorithms/core.py | 324 + .../setoolsgui/networkx/algorithms/cycles.py | 317 + .../setoolsgui/networkx/algorithms/dag.py | 275 + .../networkx/algorithms/distance_measures.py | 170 + .../networkx/algorithms/distance_regular.py | 179 + .../setoolsgui/networkx/algorithms/euler.py | 135 + .../networkx/algorithms/flow/__init__.py | 3 + .../networkx/algorithms/flow/maxflow.py | 477 + .../networkx/algorithms/flow/mincost.py | 802 + .../algorithms/flow/tests/test_maxflow.py | 273 + .../flow/tests/test_maxflow_large_graph.py | 51 + .../algorithms/flow/tests/test_mincost.py | 284 + .../networkx/algorithms/graphical.py | 405 + .../networkx/algorithms/hierarchy.py | 53 + .../setoolsgui/networkx/algorithms/isolate.py | 77 + .../algorithms/isomorphism/__init__.py | 4 + .../algorithms/isomorphism/isomorph.py | 227 + .../algorithms/isomorphism/isomorphvf2.py | 965 ++ .../algorithms/isomorphism/matchhelpers.py | 346 + .../isomorphism/tests/iso_r01_s80.A99 | Bin 0 -> 1442 bytes .../isomorphism/tests/iso_r01_s80.B99 | Bin 0 -> 1442 bytes .../isomorphism/tests/si2_b06_m200.A99 | Bin 0 -> 310 bytes .../isomorphism/tests/si2_b06_m200.B99 | Bin 0 -> 1602 bytes .../isomorphism/tests/test_isomorphism.py | 32 + .../isomorphism/tests/test_isomorphvf2.py | 217 + .../isomorphism/tests/test_vf2userfunc.py | 192 + .../algorithms/isomorphism/vf2userfunc.py | 198 + .../algorithms/link_analysis/__init__.py | 2 + .../algorithms/link_analysis/hits_alg.py | 308 + .../algorithms/link_analysis/pagerank_alg.py | 399 + .../link_analysis/tests/test_hits.py | 93 + .../link_analysis/tests/test_pagerank.py | 122 + .../networkx/algorithms/matching.py | 825 ++ .../setoolsgui/networkx/algorithms/mis.py | 81 + .../setoolsgui/networkx/algorithms/mst.py | 254 + .../networkx/algorithms/operators/__init__.py | 4 + .../networkx/algorithms/operators/all.py | 151 + .../networkx/algorithms/operators/binary.py | 329 + .../networkx/algorithms/operators/product.py | 330 + .../algorithms/operators/tests/test_all.py | 167 + .../algorithms/operators/tests/test_binary.py | 270 + .../operators/tests/test_product.py | 334 + .../algorithms/operators/tests/test_unary.py | 47 + .../networkx/algorithms/operators/unary.py | 69 + .../networkx/algorithms/richclub.py | 101 + .../algorithms/shortest_paths/__init__.py | 6 + .../algorithms/shortest_paths/astar.py | 159 + .../algorithms/shortest_paths/dense.py | 156 + .../algorithms/shortest_paths/generic.py | 392 + .../shortest_paths/tests/test_astar.py | 137 + .../shortest_paths/tests/test_dense.py | 106 + .../shortest_paths/tests/test_dense_numpy.py | 53 + .../shortest_paths/tests/test_generic.py | 145 + .../shortest_paths/tests/test_unweighted.py | 81 + .../shortest_paths/tests/test_weighted.py | 246 + .../algorithms/shortest_paths/unweighted.py | 359 + .../algorithms/shortest_paths/weighted.py | 765 + .../networkx/algorithms/simple_paths.py | 124 + .../setoolsgui/networkx/algorithms/smetric.py | 37 + .../setoolsgui/networkx/algorithms/swap.py | 185 + .../networkx/algorithms/tests/test_block.py | 103 + .../algorithms/tests/test_boundary.py | 104 + .../networkx/algorithms/tests/test_clique.py | 114 + .../networkx/algorithms/tests/test_cluster.py | 195 + .../networkx/algorithms/tests/test_core.py | 114 + .../networkx/algorithms/tests/test_cycles.py | 122 + .../networkx/algorithms/tests/test_dag.py | 163 + .../tests/test_distance_measures.py | 69 + .../algorithms/tests/test_distance_regular.py | 44 + .../networkx/algorithms/tests/test_euler.py | 84 + .../algorithms/tests/test_graphical.py | 114 + .../algorithms/tests/test_hierarchy.py | 30 + .../algorithms/tests/test_matching.py | 247 + .../networkx/algorithms/tests/test_mis.py | 89 + .../networkx/algorithms/tests/test_mst.py | 133 + .../algorithms/tests/test_richclub.py | 30 + .../algorithms/tests/test_simple_paths.py | 73 + .../networkx/algorithms/tests/test_smetric.py | 19 + .../networkx/algorithms/tests/test_swap.py | 42 + .../algorithms/tests/test_vitality.py | 35 + .../networkx/algorithms/traversal/__init__.py | 4 + .../traversal/breadth_first_search.py | 53 + .../traversal/depth_first_search.py | 124 + .../algorithms/traversal/tests/test_bfs.py | 36 + .../algorithms/traversal/tests/test_dfs.py | 68 + .../networkx/algorithms/vitality.py | 84 + .../setoolsgui/networkx/classes/__init__.py | 5 + .../setoolsgui/networkx/classes/digraph.py | 1236 ++ .../setoolsgui/networkx/classes/function.py | 423 + .../setoolsgui/networkx/classes/graph.py | 1816 +++ .../networkx/classes/multidigraph.py | 851 ++ .../setoolsgui/networkx/classes/multigraph.py | 966 ++ .../classes/tests/historical_tests.py | 477 + .../networkx/classes/tests/test_digraph.py | 257 + .../classes/tests/test_digraph_historical.py | 119 + .../networkx/classes/tests/test_function.py | 190 + .../networkx/classes/tests/test_graph.py | 602 + .../classes/tests/test_graph_historical.py | 14 + .../classes/tests/test_multidigraph.py | 327 + .../networkx/classes/tests/test_multigraph.py | 244 + .../setoolsgui/networkx/convert.py | 847 ++ .../setoolsgui/networkx/drawing/__init__.py | 20 + .../setoolsgui/networkx/drawing/layout.py | 540 + .../setoolsgui/networkx/drawing/nx_agraph.py | 447 + .../setoolsgui/networkx/drawing/nx_pydot.py | 287 + .../setoolsgui/networkx/drawing/nx_pylab.py | 896 ++ .../networkx/drawing/tests/test_agraph.py | 75 + .../networkx/drawing/tests/test_layout.py | 61 + .../networkx/drawing/tests/test_pydot.py | 62 + .../networkx/drawing/tests/test_pylab.py | 40 + .../setoolsgui/networkx/exception.py | 50 + .../setoolsgui/networkx/external/__init__.py | 0 .../networkx/external/decorator/__init__.py | 8 + .../external/decorator/decorator2/__init__.py | 1 + .../decorator/decorator2/_decorator2.py | 210 + .../networkx/generators/__init__.py | 21 + .../setoolsgui/networkx/generators/atlas.py | 12336 ++++++++++++++++ .../networkx/generators/bipartite.py | 529 + .../setoolsgui/networkx/generators/classic.py | 508 + .../networkx/generators/degree_seq.py | 793 + .../networkx/generators/directed.py | 304 + .../setoolsgui/networkx/generators/ego.py | 70 + .../networkx/generators/geometric.py | 352 + .../setoolsgui/networkx/generators/hybrid.py | 116 + .../networkx/generators/intersection.py | 118 + .../setoolsgui/networkx/generators/line.py | 69 + .../networkx/generators/random_clustered.py | 125 + .../networkx/generators/random_graphs.py | 890 ++ .../setoolsgui/networkx/generators/small.py | 412 + .../setoolsgui/networkx/generators/social.py | 280 + .../networkx/generators/stochastic.py | 46 + .../networkx/generators/tests/test_atlas.py | 55 + .../generators/tests/test_bipartite.py | 176 + .../networkx/generators/tests/test_classic.py | 408 + .../generators/tests/test_degree_seq.py | 169 + .../generators/tests/test_directed.py | 36 + .../networkx/generators/tests/test_ego.py | 42 + .../generators/tests/test_geometric.py | 31 + .../networkx/generators/tests/test_hybrid.py | 24 + .../generators/tests/test_intersection.py | 19 + .../networkx/generators/tests/test_line.py | 30 + .../generators/tests/test_random_clustered.py | 28 + .../generators/tests/test_random_graphs.py | 129 + .../networkx/generators/tests/test_small.py | 181 + .../generators/tests/test_stochastic.py | 33 + .../generators/tests/test_threshold.py | 183 + .../networkx/generators/threshold.py | 906 ++ .../setoolsgui/networkx/linalg/__init__.py | 9 + .../setoolsgui/networkx/linalg/attrmatrix.py | 458 + .../setoolsgui/networkx/linalg/graphmatrix.py | 156 + .../networkx/linalg/laplacianmatrix.py | 277 + .../setoolsgui/networkx/linalg/spectrum.py | 90 + .../networkx/linalg/tests/test_graphmatrix.py | 89 + .../networkx/linalg/tests/test_laplacian.py | 101 + .../networkx/linalg/tests/test_spectrum.py | 44 + .../setoolsgui/networkx/readwrite/__init__.py | 16 + .../setoolsgui/networkx/readwrite/adjlist.py | 314 + .../setoolsgui/networkx/readwrite/edgelist.py | 464 + .../setoolsgui/networkx/readwrite/gexf.py | 926 ++ .../setoolsgui/networkx/readwrite/gml.py | 410 + .../setoolsgui/networkx/readwrite/gpickle.py | 100 + .../setoolsgui/networkx/readwrite/graphml.py | 579 + .../networkx/readwrite/json_graph/__init__.py | 10 + .../readwrite/json_graph/adjacency.py | 123 + .../readwrite/json_graph/node_link.py | 116 + .../readwrite/json_graph/serialize.py | 31 + .../json_graph/tests/test_adjacency.py | 52 + .../json_graph/tests/test_node_link.py | 44 + .../json_graph/tests/test_serialize.py | 49 + .../readwrite/json_graph/tests/test_tree.py | 29 + .../networkx/readwrite/json_graph/tree.py | 113 + .../setoolsgui/networkx/readwrite/leda.py | 106 + .../networkx/readwrite/multiline_adjlist.py | 390 + .../setoolsgui/networkx/readwrite/nx_shp.py | 224 + .../setoolsgui/networkx/readwrite/nx_yaml.py | 109 + .../setoolsgui/networkx/readwrite/p2g.py | 107 + .../setoolsgui/networkx/readwrite/pajek.py | 231 + .../networkx/readwrite/sparsegraph6.py | 169 + .../networkx/readwrite/tests/test_adjlist.py | 283 + .../networkx/readwrite/tests/test_edgelist.py | 234 + .../networkx/readwrite/tests/test_gexf.py | 306 + .../networkx/readwrite/tests/test_gml.py | 135 + .../networkx/readwrite/tests/test_gpickle.py | 27 + .../networkx/readwrite/tests/test_graphml.py | 445 + .../networkx/readwrite/tests/test_leda.py | 35 + .../networkx/readwrite/tests/test_p2g.py | 64 + .../networkx/readwrite/tests/test_pajek.py | 51 + .../networkx/readwrite/tests/test_shp.py | 140 + .../readwrite/tests/test_sparsegraph6.py | 87 + .../networkx/readwrite/tests/test_yaml.py | 53 + .../setoolsgui/networkx/relabel.py | 205 + .../setoolsgui/networkx/release.py | 254 + .../setoolsgui/networkx/testing/__init__.py | 1 + .../networkx/testing/tests/test_utils.py | 108 + .../setoolsgui/networkx/testing/utils.py | 57 + .../setoolsgui/networkx/tests/__init__.py | 0 .../setoolsgui/networkx/tests/benchmark.py | 248 + .../setoolsgui/networkx/tests/test.py | 45 + .../setoolsgui/networkx/tests/test_convert.py | 224 + .../networkx/tests/test_convert_numpy.py | 172 + .../networkx/tests/test_convert_scipy.py | 179 + .../networkx/tests/test_exceptions.py | 33 + .../setoolsgui/networkx/tests/test_relabel.py | 163 + .../setoolsgui/networkx/utils/__init__.py | 5 + .../setoolsgui/networkx/utils/decorators.py | 270 + .../setoolsgui/networkx/utils/misc.py | 151 + .../networkx/utils/random_sequence.py | 222 + .../setoolsgui/networkx/utils/rcm.py | 150 + .../networkx/utils/tests/test_decorators.py | 160 + .../networkx/utils/tests/test_misc.py | 72 + .../utils/tests/test_random_sequence.py | 33 + .../networkx/utils/tests/test_rcm.py | 13 + .../setoolsgui/networkx/utils/union_find.py | 75 + .../setoolsgui/networkx/version.py | 25 + .../setoolsgui/selinux/__init__.py | 2445 +++ .../setoolsgui/selinux/_selinux.so | Bin 0 -> 333725 bytes .../setoolsgui/selinux/audit2why.so | Bin 0 -> 243205 bytes .../setoolsgui/sepolgen/__init__.py | 0 .../setoolsgui/sepolgen/access.py | 331 + .../setoolsgui/sepolgen/audit.py | 549 + .../setoolsgui/sepolgen/classperms.py | 116 + .../setoolsgui/sepolgen/defaults.py | 77 + .../setoolsgui/sepolgen/interfaces.py | 509 + .../site-packages/setoolsgui/sepolgen/lex.py | 866 ++ .../setoolsgui/sepolgen/matching.py | 255 + .../setoolsgui/sepolgen/module.py | 213 + .../setoolsgui/sepolgen/objectmodel.py | 172 + .../setoolsgui/sepolgen/output.py | 173 + .../setoolsgui/sepolgen/policygen.py | 402 + .../setoolsgui/sepolgen/refparser.py | 1128 ++ .../setoolsgui/sepolgen/refpolicy.py | 917 ++ .../setoolsgui/sepolgen/sepolgeni18n.py | 26 + .../site-packages/setoolsgui/sepolgen/util.py | 87 + .../site-packages/setoolsgui/sepolgen/yacc.py | 2209 +++ .../site-packages/setoolsgui/sesearch | 206 + .../setoolsgui/setools/__init__.py | 68 + .../setoolsgui/setools/boolquery.py | 66 + .../setoolsgui/setools/categoryquery.py | 55 + .../setoolsgui/setools/commonquery.py | 60 + .../setoolsgui/setools/compquery.py | 39 + .../setoolsgui/setools/constraintquery.py | 142 + .../setoolsgui/setools/contextquery.py | 98 + .../setoolsgui/setools/descriptors.py | 230 + .../site-packages/setoolsgui/setools/dta.py | 603 + .../setoolsgui/setools/exception.py | 62 + .../setoolsgui/setools/fsusequery.py | 87 + .../setoolsgui/setools/genfsconquery.py | 98 + .../setoolsgui/setools/infoflow.py | 403 + .../setoolsgui/setools/initsidquery.py | 74 + .../setoolsgui/setools/mixins.py | 91 + .../setoolsgui/setools/mlsrulequery.py | 115 + .../setoolsgui/setools/netifconquery.py | 77 + .../setoolsgui/setools/nodeconquery.py | 148 + .../setoolsgui/setools/objclassquery.py | 101 + .../setoolsgui/setools/permmap.py | 363 + .../setoolsgui/setools/polcapquery.py | 47 + .../setoolsgui/setools/policyrep/__init__.py | 568 + .../setoolsgui/setools/policyrep/_qpol.so | Bin 0 -> 2151445 bytes .../setoolsgui/setools/policyrep/boolcond.py | 167 + .../setools/policyrep/constraint.py | 297 + .../setoolsgui/setools/policyrep/context.py | 68 + .../setoolsgui/setools/policyrep/default.py | 128 + .../setoolsgui/setools/policyrep/exception.py | 248 + .../setoolsgui/setools/policyrep/fscontext.py | 123 + .../setoolsgui/setools/policyrep/initsid.py | 50 + .../setoolsgui/setools/policyrep/mls.py | 463 + .../setoolsgui/setools/policyrep/mlsrule.py | 62 + .../setools/policyrep/netcontext.py | 167 + .../setoolsgui/setools/policyrep/objclass.py | 110 + .../setoolsgui/setools/policyrep/polcap.py | 40 + .../setoolsgui/setools/policyrep/qpol.py | 1114 ++ .../setoolsgui/setools/policyrep/rbacrule.py | 92 + .../setoolsgui/setools/policyrep/role.py | 81 + .../setoolsgui/setools/policyrep/rule.py | 72 + .../setoolsgui/setools/policyrep/symbol.py | 74 + .../setoolsgui/setools/policyrep/terule.py | 155 + .../setoolsgui/setools/policyrep/typeattr.py | 174 + .../setoolsgui/setools/policyrep/user.py | 86 + .../setoolsgui/setools/portconquery.py | 146 + .../site-packages/setoolsgui/setools/query.py | 192 + .../setoolsgui/setools/rbacrulequery.py | 147 + .../setoolsgui/setools/rolequery.py | 77 + .../setoolsgui/setools/sensitivityquery.py | 74 + .../setoolsgui/setools/terulequery.py | 178 + .../setoolsgui/setools/typeattrquery.py | 70 + .../setoolsgui/setools/typequery.py | 96 + .../setoolsgui/setools/userquery.py | 116 + .../site-packages/setoolsgui/widget.py | 37 + 456 files changed, 109752 insertions(+) create mode 100644 lib/python2.7/site-packages/selinux/__init__.py create mode 100755 lib/python2.7/site-packages/selinux/_selinux.so create mode 100755 lib/python2.7/site-packages/selinux/audit2why.so create mode 100644 lib/python2.7/site-packages/sepolgen/__init__.py create mode 100644 lib/python2.7/site-packages/sepolgen/access.py create mode 100644 lib/python2.7/site-packages/sepolgen/audit.py create mode 100644 lib/python2.7/site-packages/sepolgen/classperms.py create mode 100644 lib/python2.7/site-packages/sepolgen/defaults.py create mode 100644 lib/python2.7/site-packages/sepolgen/interfaces.py create mode 100644 lib/python2.7/site-packages/sepolgen/lex.py create mode 100644 lib/python2.7/site-packages/sepolgen/matching.py create mode 100644 lib/python2.7/site-packages/sepolgen/module.py create mode 100644 lib/python2.7/site-packages/sepolgen/objectmodel.py create mode 100644 lib/python2.7/site-packages/sepolgen/output.py create mode 100644 lib/python2.7/site-packages/sepolgen/policygen.py create mode 100644 lib/python2.7/site-packages/sepolgen/refparser.py create mode 100644 lib/python2.7/site-packages/sepolgen/refpolicy.py create mode 100644 lib/python2.7/site-packages/sepolgen/sepolgeni18n.py create mode 100644 lib/python2.7/site-packages/sepolgen/util.py create mode 100644 lib/python2.7/site-packages/sepolgen/yacc.py create mode 100644 lib/python2.7/site-packages/setools/__init__.py create mode 100644 lib/python2.7/site-packages/setools/boolquery.py create mode 100644 lib/python2.7/site-packages/setools/categoryquery.py create mode 100644 lib/python2.7/site-packages/setools/commonquery.py create mode 100644 lib/python2.7/site-packages/setools/compquery.py create mode 100644 lib/python2.7/site-packages/setools/constraintquery.py create mode 100644 lib/python2.7/site-packages/setools/contextquery.py create mode 100644 lib/python2.7/site-packages/setools/descriptors.py create mode 100644 lib/python2.7/site-packages/setools/dta.py create mode 100644 lib/python2.7/site-packages/setools/exception.py create mode 100644 lib/python2.7/site-packages/setools/fsusequery.py create mode 100644 lib/python2.7/site-packages/setools/genfsconquery.py create mode 100644 lib/python2.7/site-packages/setools/infoflow.py create mode 100644 lib/python2.7/site-packages/setools/initsidquery.py create mode 100644 lib/python2.7/site-packages/setools/mixins.py create mode 100644 lib/python2.7/site-packages/setools/mlsrulequery.py create mode 100644 lib/python2.7/site-packages/setools/netifconquery.py create mode 100644 lib/python2.7/site-packages/setools/nodeconquery.py create mode 100644 lib/python2.7/site-packages/setools/objclassquery.py create mode 100644 lib/python2.7/site-packages/setools/permmap.py create mode 100644 lib/python2.7/site-packages/setools/polcapquery.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/__init__.py create mode 100755 lib/python2.7/site-packages/setools/policyrep/_qpol.so create mode 100644 lib/python2.7/site-packages/setools/policyrep/boolcond.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/constraint.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/context.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/default.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/exception.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/fscontext.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/initsid.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/mls.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/mlsrule.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/netcontext.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/objclass.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/polcap.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/qpol.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/rbacrule.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/role.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/rule.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/symbol.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/terule.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/typeattr.py create mode 100644 lib/python2.7/site-packages/setools/policyrep/user.py create mode 100644 lib/python2.7/site-packages/setools/portconquery.py create mode 100644 lib/python2.7/site-packages/setools/query.py create mode 100644 lib/python2.7/site-packages/setools/rbacrulequery.py create mode 100644 lib/python2.7/site-packages/setools/rolequery.py create mode 100644 lib/python2.7/site-packages/setools/sensitivityquery.py create mode 100644 lib/python2.7/site-packages/setools/terulequery.py create mode 100644 lib/python2.7/site-packages/setools/typeattrquery.py create mode 100644 lib/python2.7/site-packages/setools/typequery.py create mode 100644 lib/python2.7/site-packages/setools/userquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/apol/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/apol/mainwindow.py create mode 100644 lib/python2.7/site-packages/setoolsgui/apol/models.py create mode 100644 lib/python2.7/site-packages/setoolsgui/apol/rulemodels.py create mode 100644 lib/python2.7/site-packages/setoolsgui/apol/terulequery.py create mode 100755 lib/python2.7/site-packages/setoolsgui/libselinux.so.1 create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/clique.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/dominating_set.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/independent_set.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/matching.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/ramsey.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_clique.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_dominating_set.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_independent_set.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_matching.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_ramsey.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_vertex_cover.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/vertex_cover.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/connectivity.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/correlation.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/mixing.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/neighbor_degree.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/pairs.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/base_test.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_connectivity.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_correlation.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_mixing.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_neighbor_degree.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_pairs.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/basic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/cluster.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/projection.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/redundancy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/spectral.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_basic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_cluster.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_project.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/block.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/boundary.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness_subset.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/closeness.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/communicability_alg.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness_subset.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_closeness.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/degree_alg.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/eigenvector.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/flow_matrix.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/katz.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/load.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_closeness_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_communicability.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_closeness.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_degree_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_katz_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_load_centrality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/chordal_alg.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/tests/test_chordal.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/clique.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cluster.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/kclique.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/tests/test_kclique.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/attracting.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/biconnected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/connected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/strongly_connected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_attracting.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_biconnected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_connected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_strongly_connected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_weakly_connected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/weakly_connected.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/connectivity.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/cuts.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_connectivity.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_cuts.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/core.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cycles.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/dag.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_measures.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_regular.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/euler.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/maxflow.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/mincost.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow_large_graph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_mincost.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/graphical.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/hierarchy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isolate.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/matchhelpers.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/test_isomorphism.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/vf2userfunc.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/hits_alg.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/pagerank_alg.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_hits.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_pagerank.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/matching.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mis.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mst.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/all.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/binary.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/product.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_all.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_binary.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_product.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_unary.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/unary.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/richclub.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/astar.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/dense.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/generic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_astar.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_generic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_unweighted.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_weighted.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/unweighted.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/weighted.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/simple_paths.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/smetric.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/swap.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_block.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_boundary.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_clique.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_cluster.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_core.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_cycles.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_dag.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_distance_measures.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_distance_regular.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_euler.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_graphical.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_hierarchy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_matching.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mis.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mst.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_richclub.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_simple_paths.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_smetric.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_swap.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_vitality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/breadth_first_search.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/depth_first_search.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_bfs.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_dfs.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/algorithms/vitality.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/digraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/function.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/graph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/multidigraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/multigraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/historical_tests.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph_historical.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_function.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph_historical.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multidigraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multigraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/convert.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/layout.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_agraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pydot.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pylab.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_agraph.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_layout.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pydot.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pylab.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/exception.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/external/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/_decorator2.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/atlas.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/bipartite.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/classic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/degree_seq.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/directed.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/ego.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/geometric.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/hybrid.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/intersection.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/line.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/random_clustered.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/random_graphs.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/small.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/social.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/stochastic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_atlas.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_bipartite.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_classic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_degree_seq.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_directed.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_ego.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_geometric.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_hybrid.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_intersection.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_line.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_clustered.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_graphs.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_small.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_stochastic.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_threshold.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/generators/threshold.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/attrmatrix.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/graphmatrix.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/laplacianmatrix.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/spectrum.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_graphmatrix.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_laplacian.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_spectrum.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/adjlist.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/edgelist.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gexf.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gml.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gpickle.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/graphml.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/adjacency.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/node_link.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/serialize.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_adjacency.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_node_link.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_serialize.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_tree.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tree.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/leda.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/multiline_adjlist.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_shp.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_yaml.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/p2g.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/pajek.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/sparsegraph6.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_adjlist.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_edgelist.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gexf.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gml.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gpickle.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_graphml.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_leda.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_p2g.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_pajek.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_shp.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_sparsegraph6.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_yaml.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/relabel.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/release.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/testing/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/testing/tests/test_utils.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/testing/utils.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/benchmark.py create mode 100755 lib/python2.7/site-packages/setoolsgui/networkx/tests/test.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_numpy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_scipy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/test_exceptions.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/tests/test_relabel.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/decorators.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/misc.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/random_sequence.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/rcm.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_decorators.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_misc.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_random_sequence.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_rcm.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/utils/union_find.py create mode 100644 lib/python2.7/site-packages/setoolsgui/networkx/version.py create mode 100644 lib/python2.7/site-packages/setoolsgui/selinux/__init__.py create mode 100755 lib/python2.7/site-packages/setoolsgui/selinux/_selinux.so create mode 100755 lib/python2.7/site-packages/setoolsgui/selinux/audit2why.so create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/access.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/audit.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/classperms.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/defaults.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/interfaces.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/lex.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/matching.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/module.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/objectmodel.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/output.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/policygen.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/refparser.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/refpolicy.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/sepolgeni18n.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/util.py create mode 100644 lib/python2.7/site-packages/setoolsgui/sepolgen/yacc.py create mode 100755 lib/python2.7/site-packages/setoolsgui/sesearch create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/__init__.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/boolquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/categoryquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/commonquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/compquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/constraintquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/contextquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/descriptors.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/dta.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/exception.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/fsusequery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/genfsconquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/infoflow.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/initsidquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/mixins.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/mlsrulequery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/netifconquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/nodeconquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/objclassquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/permmap.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/polcapquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/__init__.py create mode 100755 lib/python2.7/site-packages/setoolsgui/setools/policyrep/_qpol.so create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/boolcond.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/constraint.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/context.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/default.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/exception.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/fscontext.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/initsid.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/mls.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/mlsrule.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/netcontext.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/objclass.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/polcap.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/qpol.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/rbacrule.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/role.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/rule.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/symbol.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/terule.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/typeattr.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/policyrep/user.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/portconquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/query.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/rbacrulequery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/rolequery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/sensitivityquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/terulequery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/typeattrquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/typequery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/setools/userquery.py create mode 100644 lib/python2.7/site-packages/setoolsgui/widget.py diff --git a/lib/python2.7/site-packages/selinux/__init__.py b/lib/python2.7/site-packages/selinux/__init__.py new file mode 100644 index 0000000..b81b031 --- /dev/null +++ b/lib/python2.7/site-packages/selinux/__init__.py @@ -0,0 +1,2445 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 2.0.11 +# +# Do not make changes to this file unless you know what you are doing--modify +# the SWIG interface file instead. + + + + + +from sys import version_info +if version_info >= (2,6,0): + def swig_import_helper(): + from os.path import dirname + import imp + fp = None + try: + fp, pathname, description = imp.find_module('_selinux', [dirname(__file__)]) + except ImportError: + import _selinux + return _selinux + if fp is not None: + try: + _mod = imp.load_module('_selinux', fp, pathname, description) + finally: + fp.close() + return _mod + _selinux = swig_import_helper() + del swig_import_helper +else: + import _selinux +del version_info +try: + _swig_property = property +except NameError: + pass # Python < 2.2 doesn't have 'property'. +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'SwigPyObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError(name) + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +try: + _object = object + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 + + +import shutil, os, stat + +DISABLED = -1 +PERMISSIVE = 0 +ENFORCING = 1 + +def restorecon(path, recursive=False): + """ Restore SELinux context on a given path """ + + try: + mode = os.lstat(path)[stat.ST_MODE] + status, context = matchpathcon(path, mode) + except OSError: + path = os.path.realpath(os.path.expanduser(path)) + mode = os.lstat(path)[stat.ST_MODE] + status, context = matchpathcon(path, mode) + + if status == 0: + status, oldcontext = lgetfilecon(path) + if context != oldcontext: + lsetfilecon(path, context) + + if recursive: + for root, dirs, files in os.walk(path): + for name in files + dirs: + restorecon(os.path.join(root, name)) + +def chcon(path, context, recursive=False): + """ Set the SELinux context on a given path """ + lsetfilecon(path, context) + if recursive: + for root, dirs, files in os.walk(path): + for name in files + dirs: + lsetfilecon(os.path.join(root,name), context) + +def copytree(src, dest): + """ An SELinux-friendly shutil.copytree method """ + shutil.copytree(src, dest) + restorecon(dest, recursive=True) + +def install(src, dest): + """ An SELinux-friendly shutil.move method """ + shutil.move(src, dest) + restorecon(dest, recursive=True) + +class security_id(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, security_id, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, security_id, name) + __repr__ = _swig_repr + __swig_setmethods__["ctx"] = _selinux.security_id_ctx_set + __swig_getmethods__["ctx"] = _selinux.security_id_ctx_get + if _newclass:ctx = _swig_property(_selinux.security_id_ctx_get, _selinux.security_id_ctx_set) + __swig_setmethods__["refcnt"] = _selinux.security_id_refcnt_set + __swig_getmethods__["refcnt"] = _selinux.security_id_refcnt_get + if _newclass:refcnt = _swig_property(_selinux.security_id_refcnt_get, _selinux.security_id_refcnt_set) + def __init__(self): + this = _selinux.new_security_id() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_security_id + __del__ = lambda self : None; +security_id_swigregister = _selinux.security_id_swigregister +security_id_swigregister(security_id) + + +def avc_sid_to_context(*args): + return _selinux.avc_sid_to_context(*args) +avc_sid_to_context = _selinux.avc_sid_to_context + +def avc_sid_to_context_raw(*args): + return _selinux.avc_sid_to_context_raw(*args) +avc_sid_to_context_raw = _selinux.avc_sid_to_context_raw + +def avc_context_to_sid(*args): + return _selinux.avc_context_to_sid(*args) +avc_context_to_sid = _selinux.avc_context_to_sid + +def avc_context_to_sid_raw(*args): + return _selinux.avc_context_to_sid_raw(*args) +avc_context_to_sid_raw = _selinux.avc_context_to_sid_raw + +def sidget(*args): + return _selinux.sidget(*args) +sidget = _selinux.sidget + +def sidput(*args): + return _selinux.sidput(*args) +sidput = _selinux.sidput + +def avc_get_initial_sid(*args): + return _selinux.avc_get_initial_sid(*args) +avc_get_initial_sid = _selinux.avc_get_initial_sid +class avc_entry_ref(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_entry_ref, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_entry_ref, name) + __repr__ = _swig_repr + __swig_setmethods__["ae"] = _selinux.avc_entry_ref_ae_set + __swig_getmethods__["ae"] = _selinux.avc_entry_ref_ae_get + if _newclass:ae = _swig_property(_selinux.avc_entry_ref_ae_get, _selinux.avc_entry_ref_ae_set) + def __init__(self): + this = _selinux.new_avc_entry_ref() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_entry_ref + __del__ = lambda self : None; +avc_entry_ref_swigregister = _selinux.avc_entry_ref_swigregister +avc_entry_ref_swigregister(avc_entry_ref) + +class avc_memory_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_memory_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_memory_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_malloc"] = _selinux.avc_memory_callback_func_malloc_set + __swig_getmethods__["func_malloc"] = _selinux.avc_memory_callback_func_malloc_get + if _newclass:func_malloc = _swig_property(_selinux.avc_memory_callback_func_malloc_get, _selinux.avc_memory_callback_func_malloc_set) + __swig_setmethods__["func_free"] = _selinux.avc_memory_callback_func_free_set + __swig_getmethods__["func_free"] = _selinux.avc_memory_callback_func_free_get + if _newclass:func_free = _swig_property(_selinux.avc_memory_callback_func_free_get, _selinux.avc_memory_callback_func_free_set) + def __init__(self): + this = _selinux.new_avc_memory_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_memory_callback + __del__ = lambda self : None; +avc_memory_callback_swigregister = _selinux.avc_memory_callback_swigregister +avc_memory_callback_swigregister(avc_memory_callback) + +class avc_log_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_log_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_log_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_log"] = _selinux.avc_log_callback_func_log_set + __swig_getmethods__["func_log"] = _selinux.avc_log_callback_func_log_get + if _newclass:func_log = _swig_property(_selinux.avc_log_callback_func_log_get, _selinux.avc_log_callback_func_log_set) + __swig_setmethods__["func_audit"] = _selinux.avc_log_callback_func_audit_set + __swig_getmethods__["func_audit"] = _selinux.avc_log_callback_func_audit_get + if _newclass:func_audit = _swig_property(_selinux.avc_log_callback_func_audit_get, _selinux.avc_log_callback_func_audit_set) + def __init__(self): + this = _selinux.new_avc_log_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_log_callback + __del__ = lambda self : None; +avc_log_callback_swigregister = _selinux.avc_log_callback_swigregister +avc_log_callback_swigregister(avc_log_callback) + +class avc_thread_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_thread_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_thread_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_create_thread"] = _selinux.avc_thread_callback_func_create_thread_set + __swig_getmethods__["func_create_thread"] = _selinux.avc_thread_callback_func_create_thread_get + if _newclass:func_create_thread = _swig_property(_selinux.avc_thread_callback_func_create_thread_get, _selinux.avc_thread_callback_func_create_thread_set) + __swig_setmethods__["func_stop_thread"] = _selinux.avc_thread_callback_func_stop_thread_set + __swig_getmethods__["func_stop_thread"] = _selinux.avc_thread_callback_func_stop_thread_get + if _newclass:func_stop_thread = _swig_property(_selinux.avc_thread_callback_func_stop_thread_get, _selinux.avc_thread_callback_func_stop_thread_set) + def __init__(self): + this = _selinux.new_avc_thread_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_thread_callback + __del__ = lambda self : None; +avc_thread_callback_swigregister = _selinux.avc_thread_callback_swigregister +avc_thread_callback_swigregister(avc_thread_callback) + +class avc_lock_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_lock_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_lock_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_alloc_lock"] = _selinux.avc_lock_callback_func_alloc_lock_set + __swig_getmethods__["func_alloc_lock"] = _selinux.avc_lock_callback_func_alloc_lock_get + if _newclass:func_alloc_lock = _swig_property(_selinux.avc_lock_callback_func_alloc_lock_get, _selinux.avc_lock_callback_func_alloc_lock_set) + __swig_setmethods__["func_get_lock"] = _selinux.avc_lock_callback_func_get_lock_set + __swig_getmethods__["func_get_lock"] = _selinux.avc_lock_callback_func_get_lock_get + if _newclass:func_get_lock = _swig_property(_selinux.avc_lock_callback_func_get_lock_get, _selinux.avc_lock_callback_func_get_lock_set) + __swig_setmethods__["func_release_lock"] = _selinux.avc_lock_callback_func_release_lock_set + __swig_getmethods__["func_release_lock"] = _selinux.avc_lock_callback_func_release_lock_get + if _newclass:func_release_lock = _swig_property(_selinux.avc_lock_callback_func_release_lock_get, _selinux.avc_lock_callback_func_release_lock_set) + __swig_setmethods__["func_free_lock"] = _selinux.avc_lock_callback_func_free_lock_set + __swig_getmethods__["func_free_lock"] = _selinux.avc_lock_callback_func_free_lock_get + if _newclass:func_free_lock = _swig_property(_selinux.avc_lock_callback_func_free_lock_get, _selinux.avc_lock_callback_func_free_lock_set) + def __init__(self): + this = _selinux.new_avc_lock_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_lock_callback + __del__ = lambda self : None; +avc_lock_callback_swigregister = _selinux.avc_lock_callback_swigregister +avc_lock_callback_swigregister(avc_lock_callback) + +AVC_OPT_UNUSED = _selinux.AVC_OPT_UNUSED +AVC_OPT_SETENFORCE = _selinux.AVC_OPT_SETENFORCE + +def avc_init(*args): + return _selinux.avc_init(*args) +avc_init = _selinux.avc_init + +def avc_open(*args): + return _selinux.avc_open(*args) +avc_open = _selinux.avc_open + +def avc_cleanup(): + return _selinux.avc_cleanup() +avc_cleanup = _selinux.avc_cleanup + +def avc_reset(): + return _selinux.avc_reset() +avc_reset = _selinux.avc_reset + +def avc_destroy(): + return _selinux.avc_destroy() +avc_destroy = _selinux.avc_destroy + +def avc_has_perm_noaudit(*args): + return _selinux.avc_has_perm_noaudit(*args) +avc_has_perm_noaudit = _selinux.avc_has_perm_noaudit + +def avc_has_perm(*args): + return _selinux.avc_has_perm(*args) +avc_has_perm = _selinux.avc_has_perm + +def avc_audit(*args): + return _selinux.avc_audit(*args) +avc_audit = _selinux.avc_audit + +def avc_compute_create(*args): + return _selinux.avc_compute_create(*args) +avc_compute_create = _selinux.avc_compute_create + +def avc_compute_member(*args): + return _selinux.avc_compute_member(*args) +avc_compute_member = _selinux.avc_compute_member +AVC_CALLBACK_GRANT = _selinux.AVC_CALLBACK_GRANT +AVC_CALLBACK_TRY_REVOKE = _selinux.AVC_CALLBACK_TRY_REVOKE +AVC_CALLBACK_REVOKE = _selinux.AVC_CALLBACK_REVOKE +AVC_CALLBACK_RESET = _selinux.AVC_CALLBACK_RESET +AVC_CALLBACK_AUDITALLOW_ENABLE = _selinux.AVC_CALLBACK_AUDITALLOW_ENABLE +AVC_CALLBACK_AUDITALLOW_DISABLE = _selinux.AVC_CALLBACK_AUDITALLOW_DISABLE +AVC_CALLBACK_AUDITDENY_ENABLE = _selinux.AVC_CALLBACK_AUDITDENY_ENABLE +AVC_CALLBACK_AUDITDENY_DISABLE = _selinux.AVC_CALLBACK_AUDITDENY_DISABLE +AVC_CACHE_STATS = _selinux.AVC_CACHE_STATS +class avc_cache_stats(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_cache_stats, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_cache_stats, name) + __repr__ = _swig_repr + __swig_setmethods__["entry_lookups"] = _selinux.avc_cache_stats_entry_lookups_set + __swig_getmethods__["entry_lookups"] = _selinux.avc_cache_stats_entry_lookups_get + if _newclass:entry_lookups = _swig_property(_selinux.avc_cache_stats_entry_lookups_get, _selinux.avc_cache_stats_entry_lookups_set) + __swig_setmethods__["entry_hits"] = _selinux.avc_cache_stats_entry_hits_set + __swig_getmethods__["entry_hits"] = _selinux.avc_cache_stats_entry_hits_get + if _newclass:entry_hits = _swig_property(_selinux.avc_cache_stats_entry_hits_get, _selinux.avc_cache_stats_entry_hits_set) + __swig_setmethods__["entry_misses"] = _selinux.avc_cache_stats_entry_misses_set + __swig_getmethods__["entry_misses"] = _selinux.avc_cache_stats_entry_misses_get + if _newclass:entry_misses = _swig_property(_selinux.avc_cache_stats_entry_misses_get, _selinux.avc_cache_stats_entry_misses_set) + __swig_setmethods__["entry_discards"] = _selinux.avc_cache_stats_entry_discards_set + __swig_getmethods__["entry_discards"] = _selinux.avc_cache_stats_entry_discards_get + if _newclass:entry_discards = _swig_property(_selinux.avc_cache_stats_entry_discards_get, _selinux.avc_cache_stats_entry_discards_set) + __swig_setmethods__["cav_lookups"] = _selinux.avc_cache_stats_cav_lookups_set + __swig_getmethods__["cav_lookups"] = _selinux.avc_cache_stats_cav_lookups_get + if _newclass:cav_lookups = _swig_property(_selinux.avc_cache_stats_cav_lookups_get, _selinux.avc_cache_stats_cav_lookups_set) + __swig_setmethods__["cav_hits"] = _selinux.avc_cache_stats_cav_hits_set + __swig_getmethods__["cav_hits"] = _selinux.avc_cache_stats_cav_hits_get + if _newclass:cav_hits = _swig_property(_selinux.avc_cache_stats_cav_hits_get, _selinux.avc_cache_stats_cav_hits_set) + __swig_setmethods__["cav_probes"] = _selinux.avc_cache_stats_cav_probes_set + __swig_getmethods__["cav_probes"] = _selinux.avc_cache_stats_cav_probes_get + if _newclass:cav_probes = _swig_property(_selinux.avc_cache_stats_cav_probes_get, _selinux.avc_cache_stats_cav_probes_set) + __swig_setmethods__["cav_misses"] = _selinux.avc_cache_stats_cav_misses_set + __swig_getmethods__["cav_misses"] = _selinux.avc_cache_stats_cav_misses_get + if _newclass:cav_misses = _swig_property(_selinux.avc_cache_stats_cav_misses_get, _selinux.avc_cache_stats_cav_misses_set) + def __init__(self): + this = _selinux.new_avc_cache_stats() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_cache_stats + __del__ = lambda self : None; +avc_cache_stats_swigregister = _selinux.avc_cache_stats_swigregister +avc_cache_stats_swigregister(avc_cache_stats) + + +def avc_av_stats(): + return _selinux.avc_av_stats() +avc_av_stats = _selinux.avc_av_stats + +def avc_sid_stats(): + return _selinux.avc_sid_stats() +avc_sid_stats = _selinux.avc_sid_stats + +def avc_netlink_open(*args): + return _selinux.avc_netlink_open(*args) +avc_netlink_open = _selinux.avc_netlink_open + +def avc_netlink_loop(): + return _selinux.avc_netlink_loop() +avc_netlink_loop = _selinux.avc_netlink_loop + +def avc_netlink_close(): + return _selinux.avc_netlink_close() +avc_netlink_close = _selinux.avc_netlink_close + +def selinux_status_open(*args): + return _selinux.selinux_status_open(*args) +selinux_status_open = _selinux.selinux_status_open + +def selinux_status_close(): + return _selinux.selinux_status_close() +selinux_status_close = _selinux.selinux_status_close + +def selinux_status_updated(): + return _selinux.selinux_status_updated() +selinux_status_updated = _selinux.selinux_status_updated + +def selinux_status_getenforce(): + return _selinux.selinux_status_getenforce() +selinux_status_getenforce = _selinux.selinux_status_getenforce + +def selinux_status_policyload(): + return _selinux.selinux_status_policyload() +selinux_status_policyload = _selinux.selinux_status_policyload + +def selinux_status_deny_unknown(): + return _selinux.selinux_status_deny_unknown() +selinux_status_deny_unknown = _selinux.selinux_status_deny_unknown +COMMON_FILE__IOCTL = _selinux.COMMON_FILE__IOCTL +COMMON_FILE__READ = _selinux.COMMON_FILE__READ +COMMON_FILE__WRITE = _selinux.COMMON_FILE__WRITE +COMMON_FILE__CREATE = _selinux.COMMON_FILE__CREATE +COMMON_FILE__GETATTR = _selinux.COMMON_FILE__GETATTR +COMMON_FILE__SETATTR = _selinux.COMMON_FILE__SETATTR +COMMON_FILE__LOCK = _selinux.COMMON_FILE__LOCK +COMMON_FILE__RELABELFROM = _selinux.COMMON_FILE__RELABELFROM +COMMON_FILE__RELABELTO = _selinux.COMMON_FILE__RELABELTO +COMMON_FILE__APPEND = _selinux.COMMON_FILE__APPEND +COMMON_FILE__UNLINK = _selinux.COMMON_FILE__UNLINK +COMMON_FILE__LINK = _selinux.COMMON_FILE__LINK +COMMON_FILE__RENAME = _selinux.COMMON_FILE__RENAME +COMMON_FILE__EXECUTE = _selinux.COMMON_FILE__EXECUTE +COMMON_FILE__SWAPON = _selinux.COMMON_FILE__SWAPON +COMMON_FILE__QUOTAON = _selinux.COMMON_FILE__QUOTAON +COMMON_FILE__MOUNTON = _selinux.COMMON_FILE__MOUNTON +COMMON_SOCKET__IOCTL = _selinux.COMMON_SOCKET__IOCTL +COMMON_SOCKET__READ = _selinux.COMMON_SOCKET__READ +COMMON_SOCKET__WRITE = _selinux.COMMON_SOCKET__WRITE +COMMON_SOCKET__CREATE = _selinux.COMMON_SOCKET__CREATE +COMMON_SOCKET__GETATTR = _selinux.COMMON_SOCKET__GETATTR +COMMON_SOCKET__SETATTR = _selinux.COMMON_SOCKET__SETATTR +COMMON_SOCKET__LOCK = _selinux.COMMON_SOCKET__LOCK +COMMON_SOCKET__RELABELFROM = _selinux.COMMON_SOCKET__RELABELFROM +COMMON_SOCKET__RELABELTO = _selinux.COMMON_SOCKET__RELABELTO +COMMON_SOCKET__APPEND = _selinux.COMMON_SOCKET__APPEND +COMMON_SOCKET__BIND = _selinux.COMMON_SOCKET__BIND +COMMON_SOCKET__CONNECT = _selinux.COMMON_SOCKET__CONNECT +COMMON_SOCKET__LISTEN = _selinux.COMMON_SOCKET__LISTEN +COMMON_SOCKET__ACCEPT = _selinux.COMMON_SOCKET__ACCEPT +COMMON_SOCKET__GETOPT = _selinux.COMMON_SOCKET__GETOPT +COMMON_SOCKET__SETOPT = _selinux.COMMON_SOCKET__SETOPT +COMMON_SOCKET__SHUTDOWN = _selinux.COMMON_SOCKET__SHUTDOWN +COMMON_SOCKET__RECVFROM = _selinux.COMMON_SOCKET__RECVFROM +COMMON_SOCKET__SENDTO = _selinux.COMMON_SOCKET__SENDTO +COMMON_SOCKET__RECV_MSG = _selinux.COMMON_SOCKET__RECV_MSG +COMMON_SOCKET__SEND_MSG = _selinux.COMMON_SOCKET__SEND_MSG +COMMON_SOCKET__NAME_BIND = _selinux.COMMON_SOCKET__NAME_BIND +COMMON_IPC__CREATE = _selinux.COMMON_IPC__CREATE +COMMON_IPC__DESTROY = _selinux.COMMON_IPC__DESTROY +COMMON_IPC__GETATTR = _selinux.COMMON_IPC__GETATTR +COMMON_IPC__SETATTR = _selinux.COMMON_IPC__SETATTR +COMMON_IPC__READ = _selinux.COMMON_IPC__READ +COMMON_IPC__WRITE = _selinux.COMMON_IPC__WRITE +COMMON_IPC__ASSOCIATE = _selinux.COMMON_IPC__ASSOCIATE +COMMON_IPC__UNIX_READ = _selinux.COMMON_IPC__UNIX_READ +COMMON_IPC__UNIX_WRITE = _selinux.COMMON_IPC__UNIX_WRITE +COMMON_DATABASE__CREATE = _selinux.COMMON_DATABASE__CREATE +COMMON_DATABASE__DROP = _selinux.COMMON_DATABASE__DROP +COMMON_DATABASE__GETATTR = _selinux.COMMON_DATABASE__GETATTR +COMMON_DATABASE__SETATTR = _selinux.COMMON_DATABASE__SETATTR +COMMON_DATABASE__RELABELFROM = _selinux.COMMON_DATABASE__RELABELFROM +COMMON_DATABASE__RELABELTO = _selinux.COMMON_DATABASE__RELABELTO +FILESYSTEM__MOUNT = _selinux.FILESYSTEM__MOUNT +FILESYSTEM__REMOUNT = _selinux.FILESYSTEM__REMOUNT +FILESYSTEM__UNMOUNT = _selinux.FILESYSTEM__UNMOUNT +FILESYSTEM__GETATTR = _selinux.FILESYSTEM__GETATTR +FILESYSTEM__RELABELFROM = _selinux.FILESYSTEM__RELABELFROM +FILESYSTEM__RELABELTO = _selinux.FILESYSTEM__RELABELTO +FILESYSTEM__TRANSITION = _selinux.FILESYSTEM__TRANSITION +FILESYSTEM__ASSOCIATE = _selinux.FILESYSTEM__ASSOCIATE +FILESYSTEM__QUOTAMOD = _selinux.FILESYSTEM__QUOTAMOD +FILESYSTEM__QUOTAGET = _selinux.FILESYSTEM__QUOTAGET +DIR__IOCTL = _selinux.DIR__IOCTL +DIR__READ = _selinux.DIR__READ +DIR__WRITE = _selinux.DIR__WRITE +DIR__CREATE = _selinux.DIR__CREATE +DIR__GETATTR = _selinux.DIR__GETATTR +DIR__SETATTR = _selinux.DIR__SETATTR +DIR__LOCK = _selinux.DIR__LOCK +DIR__RELABELFROM = _selinux.DIR__RELABELFROM +DIR__RELABELTO = _selinux.DIR__RELABELTO +DIR__APPEND = _selinux.DIR__APPEND +DIR__UNLINK = _selinux.DIR__UNLINK +DIR__LINK = _selinux.DIR__LINK +DIR__RENAME = _selinux.DIR__RENAME +DIR__EXECUTE = _selinux.DIR__EXECUTE +DIR__SWAPON = _selinux.DIR__SWAPON +DIR__QUOTAON = _selinux.DIR__QUOTAON +DIR__MOUNTON = _selinux.DIR__MOUNTON +DIR__ADD_NAME = _selinux.DIR__ADD_NAME +DIR__REMOVE_NAME = _selinux.DIR__REMOVE_NAME +DIR__REPARENT = _selinux.DIR__REPARENT +DIR__SEARCH = _selinux.DIR__SEARCH +DIR__RMDIR = _selinux.DIR__RMDIR +DIR__OPEN = _selinux.DIR__OPEN +FILE__IOCTL = _selinux.FILE__IOCTL +FILE__READ = _selinux.FILE__READ +FILE__WRITE = _selinux.FILE__WRITE +FILE__CREATE = _selinux.FILE__CREATE +FILE__GETATTR = _selinux.FILE__GETATTR +FILE__SETATTR = _selinux.FILE__SETATTR +FILE__LOCK = _selinux.FILE__LOCK +FILE__RELABELFROM = _selinux.FILE__RELABELFROM +FILE__RELABELTO = _selinux.FILE__RELABELTO +FILE__APPEND = _selinux.FILE__APPEND +FILE__UNLINK = _selinux.FILE__UNLINK +FILE__LINK = _selinux.FILE__LINK +FILE__RENAME = _selinux.FILE__RENAME +FILE__EXECUTE = _selinux.FILE__EXECUTE +FILE__SWAPON = _selinux.FILE__SWAPON +FILE__QUOTAON = _selinux.FILE__QUOTAON +FILE__MOUNTON = _selinux.FILE__MOUNTON +FILE__EXECUTE_NO_TRANS = _selinux.FILE__EXECUTE_NO_TRANS +FILE__ENTRYPOINT = _selinux.FILE__ENTRYPOINT +FILE__EXECMOD = _selinux.FILE__EXECMOD +FILE__OPEN = _selinux.FILE__OPEN +LNK_FILE__IOCTL = _selinux.LNK_FILE__IOCTL +LNK_FILE__READ = _selinux.LNK_FILE__READ +LNK_FILE__WRITE = _selinux.LNK_FILE__WRITE +LNK_FILE__CREATE = _selinux.LNK_FILE__CREATE +LNK_FILE__GETATTR = _selinux.LNK_FILE__GETATTR +LNK_FILE__SETATTR = _selinux.LNK_FILE__SETATTR +LNK_FILE__LOCK = _selinux.LNK_FILE__LOCK +LNK_FILE__RELABELFROM = _selinux.LNK_FILE__RELABELFROM +LNK_FILE__RELABELTO = _selinux.LNK_FILE__RELABELTO +LNK_FILE__APPEND = _selinux.LNK_FILE__APPEND +LNK_FILE__UNLINK = _selinux.LNK_FILE__UNLINK +LNK_FILE__LINK = _selinux.LNK_FILE__LINK +LNK_FILE__RENAME = _selinux.LNK_FILE__RENAME +LNK_FILE__EXECUTE = _selinux.LNK_FILE__EXECUTE +LNK_FILE__SWAPON = _selinux.LNK_FILE__SWAPON +LNK_FILE__QUOTAON = _selinux.LNK_FILE__QUOTAON +LNK_FILE__MOUNTON = _selinux.LNK_FILE__MOUNTON +CHR_FILE__IOCTL = _selinux.CHR_FILE__IOCTL +CHR_FILE__READ = _selinux.CHR_FILE__READ +CHR_FILE__WRITE = _selinux.CHR_FILE__WRITE +CHR_FILE__CREATE = _selinux.CHR_FILE__CREATE +CHR_FILE__GETATTR = _selinux.CHR_FILE__GETATTR +CHR_FILE__SETATTR = _selinux.CHR_FILE__SETATTR +CHR_FILE__LOCK = _selinux.CHR_FILE__LOCK +CHR_FILE__RELABELFROM = _selinux.CHR_FILE__RELABELFROM +CHR_FILE__RELABELTO = _selinux.CHR_FILE__RELABELTO +CHR_FILE__APPEND = _selinux.CHR_FILE__APPEND +CHR_FILE__UNLINK = _selinux.CHR_FILE__UNLINK +CHR_FILE__LINK = _selinux.CHR_FILE__LINK +CHR_FILE__RENAME = _selinux.CHR_FILE__RENAME +CHR_FILE__EXECUTE = _selinux.CHR_FILE__EXECUTE +CHR_FILE__SWAPON = _selinux.CHR_FILE__SWAPON +CHR_FILE__QUOTAON = _selinux.CHR_FILE__QUOTAON +CHR_FILE__MOUNTON = _selinux.CHR_FILE__MOUNTON +CHR_FILE__EXECUTE_NO_TRANS = _selinux.CHR_FILE__EXECUTE_NO_TRANS +CHR_FILE__ENTRYPOINT = _selinux.CHR_FILE__ENTRYPOINT +CHR_FILE__EXECMOD = _selinux.CHR_FILE__EXECMOD +CHR_FILE__OPEN = _selinux.CHR_FILE__OPEN +BLK_FILE__IOCTL = _selinux.BLK_FILE__IOCTL +BLK_FILE__READ = _selinux.BLK_FILE__READ +BLK_FILE__WRITE = _selinux.BLK_FILE__WRITE +BLK_FILE__CREATE = _selinux.BLK_FILE__CREATE +BLK_FILE__GETATTR = _selinux.BLK_FILE__GETATTR +BLK_FILE__SETATTR = _selinux.BLK_FILE__SETATTR +BLK_FILE__LOCK = _selinux.BLK_FILE__LOCK +BLK_FILE__RELABELFROM = _selinux.BLK_FILE__RELABELFROM +BLK_FILE__RELABELTO = _selinux.BLK_FILE__RELABELTO +BLK_FILE__APPEND = _selinux.BLK_FILE__APPEND +BLK_FILE__UNLINK = _selinux.BLK_FILE__UNLINK +BLK_FILE__LINK = _selinux.BLK_FILE__LINK +BLK_FILE__RENAME = _selinux.BLK_FILE__RENAME +BLK_FILE__EXECUTE = _selinux.BLK_FILE__EXECUTE +BLK_FILE__SWAPON = _selinux.BLK_FILE__SWAPON +BLK_FILE__QUOTAON = _selinux.BLK_FILE__QUOTAON +BLK_FILE__MOUNTON = _selinux.BLK_FILE__MOUNTON +BLK_FILE__OPEN = _selinux.BLK_FILE__OPEN +SOCK_FILE__IOCTL = _selinux.SOCK_FILE__IOCTL +SOCK_FILE__READ = _selinux.SOCK_FILE__READ +SOCK_FILE__WRITE = _selinux.SOCK_FILE__WRITE +SOCK_FILE__CREATE = _selinux.SOCK_FILE__CREATE +SOCK_FILE__GETATTR = _selinux.SOCK_FILE__GETATTR +SOCK_FILE__SETATTR = _selinux.SOCK_FILE__SETATTR +SOCK_FILE__LOCK = _selinux.SOCK_FILE__LOCK +SOCK_FILE__RELABELFROM = _selinux.SOCK_FILE__RELABELFROM +SOCK_FILE__RELABELTO = _selinux.SOCK_FILE__RELABELTO +SOCK_FILE__APPEND = _selinux.SOCK_FILE__APPEND +SOCK_FILE__UNLINK = _selinux.SOCK_FILE__UNLINK +SOCK_FILE__LINK = _selinux.SOCK_FILE__LINK +SOCK_FILE__RENAME = _selinux.SOCK_FILE__RENAME +SOCK_FILE__EXECUTE = _selinux.SOCK_FILE__EXECUTE +SOCK_FILE__SWAPON = _selinux.SOCK_FILE__SWAPON +SOCK_FILE__QUOTAON = _selinux.SOCK_FILE__QUOTAON +SOCK_FILE__MOUNTON = _selinux.SOCK_FILE__MOUNTON +FIFO_FILE__IOCTL = _selinux.FIFO_FILE__IOCTL +FIFO_FILE__READ = _selinux.FIFO_FILE__READ +FIFO_FILE__WRITE = _selinux.FIFO_FILE__WRITE +FIFO_FILE__CREATE = _selinux.FIFO_FILE__CREATE +FIFO_FILE__GETATTR = _selinux.FIFO_FILE__GETATTR +FIFO_FILE__SETATTR = _selinux.FIFO_FILE__SETATTR +FIFO_FILE__LOCK = _selinux.FIFO_FILE__LOCK +FIFO_FILE__RELABELFROM = _selinux.FIFO_FILE__RELABELFROM +FIFO_FILE__RELABELTO = _selinux.FIFO_FILE__RELABELTO +FIFO_FILE__APPEND = _selinux.FIFO_FILE__APPEND +FIFO_FILE__UNLINK = _selinux.FIFO_FILE__UNLINK +FIFO_FILE__LINK = _selinux.FIFO_FILE__LINK +FIFO_FILE__RENAME = _selinux.FIFO_FILE__RENAME +FIFO_FILE__EXECUTE = _selinux.FIFO_FILE__EXECUTE +FIFO_FILE__SWAPON = _selinux.FIFO_FILE__SWAPON +FIFO_FILE__QUOTAON = _selinux.FIFO_FILE__QUOTAON +FIFO_FILE__MOUNTON = _selinux.FIFO_FILE__MOUNTON +FIFO_FILE__OPEN = _selinux.FIFO_FILE__OPEN +FD__USE = _selinux.FD__USE +SOCKET__IOCTL = _selinux.SOCKET__IOCTL +SOCKET__READ = _selinux.SOCKET__READ +SOCKET__WRITE = _selinux.SOCKET__WRITE +SOCKET__CREATE = _selinux.SOCKET__CREATE +SOCKET__GETATTR = _selinux.SOCKET__GETATTR +SOCKET__SETATTR = _selinux.SOCKET__SETATTR +SOCKET__LOCK = _selinux.SOCKET__LOCK +SOCKET__RELABELFROM = _selinux.SOCKET__RELABELFROM +SOCKET__RELABELTO = _selinux.SOCKET__RELABELTO +SOCKET__APPEND = _selinux.SOCKET__APPEND +SOCKET__BIND = _selinux.SOCKET__BIND +SOCKET__CONNECT = _selinux.SOCKET__CONNECT +SOCKET__LISTEN = _selinux.SOCKET__LISTEN +SOCKET__ACCEPT = _selinux.SOCKET__ACCEPT +SOCKET__GETOPT = _selinux.SOCKET__GETOPT +SOCKET__SETOPT = _selinux.SOCKET__SETOPT +SOCKET__SHUTDOWN = _selinux.SOCKET__SHUTDOWN +SOCKET__RECVFROM = _selinux.SOCKET__RECVFROM +SOCKET__SENDTO = _selinux.SOCKET__SENDTO +SOCKET__RECV_MSG = _selinux.SOCKET__RECV_MSG +SOCKET__SEND_MSG = _selinux.SOCKET__SEND_MSG +SOCKET__NAME_BIND = _selinux.SOCKET__NAME_BIND +TCP_SOCKET__IOCTL = _selinux.TCP_SOCKET__IOCTL +TCP_SOCKET__READ = _selinux.TCP_SOCKET__READ +TCP_SOCKET__WRITE = _selinux.TCP_SOCKET__WRITE +TCP_SOCKET__CREATE = _selinux.TCP_SOCKET__CREATE +TCP_SOCKET__GETATTR = _selinux.TCP_SOCKET__GETATTR +TCP_SOCKET__SETATTR = _selinux.TCP_SOCKET__SETATTR +TCP_SOCKET__LOCK = _selinux.TCP_SOCKET__LOCK +TCP_SOCKET__RELABELFROM = _selinux.TCP_SOCKET__RELABELFROM +TCP_SOCKET__RELABELTO = _selinux.TCP_SOCKET__RELABELTO +TCP_SOCKET__APPEND = _selinux.TCP_SOCKET__APPEND +TCP_SOCKET__BIND = _selinux.TCP_SOCKET__BIND +TCP_SOCKET__CONNECT = _selinux.TCP_SOCKET__CONNECT +TCP_SOCKET__LISTEN = _selinux.TCP_SOCKET__LISTEN +TCP_SOCKET__ACCEPT = _selinux.TCP_SOCKET__ACCEPT +TCP_SOCKET__GETOPT = _selinux.TCP_SOCKET__GETOPT +TCP_SOCKET__SETOPT = _selinux.TCP_SOCKET__SETOPT +TCP_SOCKET__SHUTDOWN = _selinux.TCP_SOCKET__SHUTDOWN +TCP_SOCKET__RECVFROM = _selinux.TCP_SOCKET__RECVFROM +TCP_SOCKET__SENDTO = _selinux.TCP_SOCKET__SENDTO +TCP_SOCKET__RECV_MSG = _selinux.TCP_SOCKET__RECV_MSG +TCP_SOCKET__SEND_MSG = _selinux.TCP_SOCKET__SEND_MSG +TCP_SOCKET__NAME_BIND = _selinux.TCP_SOCKET__NAME_BIND +TCP_SOCKET__CONNECTTO = _selinux.TCP_SOCKET__CONNECTTO +TCP_SOCKET__NEWCONN = _selinux.TCP_SOCKET__NEWCONN +TCP_SOCKET__ACCEPTFROM = _selinux.TCP_SOCKET__ACCEPTFROM +TCP_SOCKET__NODE_BIND = _selinux.TCP_SOCKET__NODE_BIND +TCP_SOCKET__NAME_CONNECT = _selinux.TCP_SOCKET__NAME_CONNECT +UDP_SOCKET__IOCTL = _selinux.UDP_SOCKET__IOCTL +UDP_SOCKET__READ = _selinux.UDP_SOCKET__READ +UDP_SOCKET__WRITE = _selinux.UDP_SOCKET__WRITE +UDP_SOCKET__CREATE = _selinux.UDP_SOCKET__CREATE +UDP_SOCKET__GETATTR = _selinux.UDP_SOCKET__GETATTR +UDP_SOCKET__SETATTR = _selinux.UDP_SOCKET__SETATTR +UDP_SOCKET__LOCK = _selinux.UDP_SOCKET__LOCK +UDP_SOCKET__RELABELFROM = _selinux.UDP_SOCKET__RELABELFROM +UDP_SOCKET__RELABELTO = _selinux.UDP_SOCKET__RELABELTO +UDP_SOCKET__APPEND = _selinux.UDP_SOCKET__APPEND +UDP_SOCKET__BIND = _selinux.UDP_SOCKET__BIND +UDP_SOCKET__CONNECT = _selinux.UDP_SOCKET__CONNECT +UDP_SOCKET__LISTEN = _selinux.UDP_SOCKET__LISTEN +UDP_SOCKET__ACCEPT = _selinux.UDP_SOCKET__ACCEPT +UDP_SOCKET__GETOPT = _selinux.UDP_SOCKET__GETOPT +UDP_SOCKET__SETOPT = _selinux.UDP_SOCKET__SETOPT +UDP_SOCKET__SHUTDOWN = _selinux.UDP_SOCKET__SHUTDOWN +UDP_SOCKET__RECVFROM = _selinux.UDP_SOCKET__RECVFROM +UDP_SOCKET__SENDTO = _selinux.UDP_SOCKET__SENDTO +UDP_SOCKET__RECV_MSG = _selinux.UDP_SOCKET__RECV_MSG +UDP_SOCKET__SEND_MSG = _selinux.UDP_SOCKET__SEND_MSG +UDP_SOCKET__NAME_BIND = _selinux.UDP_SOCKET__NAME_BIND +UDP_SOCKET__NODE_BIND = _selinux.UDP_SOCKET__NODE_BIND +RAWIP_SOCKET__IOCTL = _selinux.RAWIP_SOCKET__IOCTL +RAWIP_SOCKET__READ = _selinux.RAWIP_SOCKET__READ +RAWIP_SOCKET__WRITE = _selinux.RAWIP_SOCKET__WRITE +RAWIP_SOCKET__CREATE = _selinux.RAWIP_SOCKET__CREATE +RAWIP_SOCKET__GETATTR = _selinux.RAWIP_SOCKET__GETATTR +RAWIP_SOCKET__SETATTR = _selinux.RAWIP_SOCKET__SETATTR +RAWIP_SOCKET__LOCK = _selinux.RAWIP_SOCKET__LOCK +RAWIP_SOCKET__RELABELFROM = _selinux.RAWIP_SOCKET__RELABELFROM +RAWIP_SOCKET__RELABELTO = _selinux.RAWIP_SOCKET__RELABELTO +RAWIP_SOCKET__APPEND = _selinux.RAWIP_SOCKET__APPEND +RAWIP_SOCKET__BIND = _selinux.RAWIP_SOCKET__BIND +RAWIP_SOCKET__CONNECT = _selinux.RAWIP_SOCKET__CONNECT +RAWIP_SOCKET__LISTEN = _selinux.RAWIP_SOCKET__LISTEN +RAWIP_SOCKET__ACCEPT = _selinux.RAWIP_SOCKET__ACCEPT +RAWIP_SOCKET__GETOPT = _selinux.RAWIP_SOCKET__GETOPT +RAWIP_SOCKET__SETOPT = _selinux.RAWIP_SOCKET__SETOPT +RAWIP_SOCKET__SHUTDOWN = _selinux.RAWIP_SOCKET__SHUTDOWN +RAWIP_SOCKET__RECVFROM = _selinux.RAWIP_SOCKET__RECVFROM +RAWIP_SOCKET__SENDTO = _selinux.RAWIP_SOCKET__SENDTO +RAWIP_SOCKET__RECV_MSG = _selinux.RAWIP_SOCKET__RECV_MSG +RAWIP_SOCKET__SEND_MSG = _selinux.RAWIP_SOCKET__SEND_MSG +RAWIP_SOCKET__NAME_BIND = _selinux.RAWIP_SOCKET__NAME_BIND +RAWIP_SOCKET__NODE_BIND = _selinux.RAWIP_SOCKET__NODE_BIND +NODE__TCP_RECV = _selinux.NODE__TCP_RECV +NODE__TCP_SEND = _selinux.NODE__TCP_SEND +NODE__UDP_RECV = _selinux.NODE__UDP_RECV +NODE__UDP_SEND = _selinux.NODE__UDP_SEND +NODE__RAWIP_RECV = _selinux.NODE__RAWIP_RECV +NODE__RAWIP_SEND = _selinux.NODE__RAWIP_SEND +NODE__ENFORCE_DEST = _selinux.NODE__ENFORCE_DEST +NODE__DCCP_RECV = _selinux.NODE__DCCP_RECV +NODE__DCCP_SEND = _selinux.NODE__DCCP_SEND +NODE__RECVFROM = _selinux.NODE__RECVFROM +NODE__SENDTO = _selinux.NODE__SENDTO +NETIF__TCP_RECV = _selinux.NETIF__TCP_RECV +NETIF__TCP_SEND = _selinux.NETIF__TCP_SEND +NETIF__UDP_RECV = _selinux.NETIF__UDP_RECV +NETIF__UDP_SEND = _selinux.NETIF__UDP_SEND +NETIF__RAWIP_RECV = _selinux.NETIF__RAWIP_RECV +NETIF__RAWIP_SEND = _selinux.NETIF__RAWIP_SEND +NETIF__DCCP_RECV = _selinux.NETIF__DCCP_RECV +NETIF__DCCP_SEND = _selinux.NETIF__DCCP_SEND +NETIF__INGRESS = _selinux.NETIF__INGRESS +NETIF__EGRESS = _selinux.NETIF__EGRESS +NETLINK_SOCKET__IOCTL = _selinux.NETLINK_SOCKET__IOCTL +NETLINK_SOCKET__READ = _selinux.NETLINK_SOCKET__READ +NETLINK_SOCKET__WRITE = _selinux.NETLINK_SOCKET__WRITE +NETLINK_SOCKET__CREATE = _selinux.NETLINK_SOCKET__CREATE +NETLINK_SOCKET__GETATTR = _selinux.NETLINK_SOCKET__GETATTR +NETLINK_SOCKET__SETATTR = _selinux.NETLINK_SOCKET__SETATTR +NETLINK_SOCKET__LOCK = _selinux.NETLINK_SOCKET__LOCK +NETLINK_SOCKET__RELABELFROM = _selinux.NETLINK_SOCKET__RELABELFROM +NETLINK_SOCKET__RELABELTO = _selinux.NETLINK_SOCKET__RELABELTO +NETLINK_SOCKET__APPEND = _selinux.NETLINK_SOCKET__APPEND +NETLINK_SOCKET__BIND = _selinux.NETLINK_SOCKET__BIND +NETLINK_SOCKET__CONNECT = _selinux.NETLINK_SOCKET__CONNECT +NETLINK_SOCKET__LISTEN = _selinux.NETLINK_SOCKET__LISTEN +NETLINK_SOCKET__ACCEPT = _selinux.NETLINK_SOCKET__ACCEPT +NETLINK_SOCKET__GETOPT = _selinux.NETLINK_SOCKET__GETOPT +NETLINK_SOCKET__SETOPT = _selinux.NETLINK_SOCKET__SETOPT +NETLINK_SOCKET__SHUTDOWN = _selinux.NETLINK_SOCKET__SHUTDOWN +NETLINK_SOCKET__RECVFROM = _selinux.NETLINK_SOCKET__RECVFROM +NETLINK_SOCKET__SENDTO = _selinux.NETLINK_SOCKET__SENDTO +NETLINK_SOCKET__RECV_MSG = _selinux.NETLINK_SOCKET__RECV_MSG +NETLINK_SOCKET__SEND_MSG = _selinux.NETLINK_SOCKET__SEND_MSG +NETLINK_SOCKET__NAME_BIND = _selinux.NETLINK_SOCKET__NAME_BIND +PACKET_SOCKET__IOCTL = _selinux.PACKET_SOCKET__IOCTL +PACKET_SOCKET__READ = _selinux.PACKET_SOCKET__READ +PACKET_SOCKET__WRITE = _selinux.PACKET_SOCKET__WRITE +PACKET_SOCKET__CREATE = _selinux.PACKET_SOCKET__CREATE +PACKET_SOCKET__GETATTR = _selinux.PACKET_SOCKET__GETATTR +PACKET_SOCKET__SETATTR = _selinux.PACKET_SOCKET__SETATTR +PACKET_SOCKET__LOCK = _selinux.PACKET_SOCKET__LOCK +PACKET_SOCKET__RELABELFROM = _selinux.PACKET_SOCKET__RELABELFROM +PACKET_SOCKET__RELABELTO = _selinux.PACKET_SOCKET__RELABELTO +PACKET_SOCKET__APPEND = _selinux.PACKET_SOCKET__APPEND +PACKET_SOCKET__BIND = _selinux.PACKET_SOCKET__BIND +PACKET_SOCKET__CONNECT = _selinux.PACKET_SOCKET__CONNECT +PACKET_SOCKET__LISTEN = _selinux.PACKET_SOCKET__LISTEN +PACKET_SOCKET__ACCEPT = _selinux.PACKET_SOCKET__ACCEPT +PACKET_SOCKET__GETOPT = _selinux.PACKET_SOCKET__GETOPT +PACKET_SOCKET__SETOPT = _selinux.PACKET_SOCKET__SETOPT +PACKET_SOCKET__SHUTDOWN = _selinux.PACKET_SOCKET__SHUTDOWN +PACKET_SOCKET__RECVFROM = _selinux.PACKET_SOCKET__RECVFROM +PACKET_SOCKET__SENDTO = _selinux.PACKET_SOCKET__SENDTO +PACKET_SOCKET__RECV_MSG = _selinux.PACKET_SOCKET__RECV_MSG +PACKET_SOCKET__SEND_MSG = _selinux.PACKET_SOCKET__SEND_MSG +PACKET_SOCKET__NAME_BIND = _selinux.PACKET_SOCKET__NAME_BIND +KEY_SOCKET__IOCTL = _selinux.KEY_SOCKET__IOCTL +KEY_SOCKET__READ = _selinux.KEY_SOCKET__READ +KEY_SOCKET__WRITE = _selinux.KEY_SOCKET__WRITE +KEY_SOCKET__CREATE = _selinux.KEY_SOCKET__CREATE +KEY_SOCKET__GETATTR = _selinux.KEY_SOCKET__GETATTR +KEY_SOCKET__SETATTR = _selinux.KEY_SOCKET__SETATTR +KEY_SOCKET__LOCK = _selinux.KEY_SOCKET__LOCK +KEY_SOCKET__RELABELFROM = _selinux.KEY_SOCKET__RELABELFROM +KEY_SOCKET__RELABELTO = _selinux.KEY_SOCKET__RELABELTO +KEY_SOCKET__APPEND = _selinux.KEY_SOCKET__APPEND +KEY_SOCKET__BIND = _selinux.KEY_SOCKET__BIND +KEY_SOCKET__CONNECT = _selinux.KEY_SOCKET__CONNECT +KEY_SOCKET__LISTEN = _selinux.KEY_SOCKET__LISTEN +KEY_SOCKET__ACCEPT = _selinux.KEY_SOCKET__ACCEPT +KEY_SOCKET__GETOPT = _selinux.KEY_SOCKET__GETOPT +KEY_SOCKET__SETOPT = _selinux.KEY_SOCKET__SETOPT +KEY_SOCKET__SHUTDOWN = _selinux.KEY_SOCKET__SHUTDOWN +KEY_SOCKET__RECVFROM = _selinux.KEY_SOCKET__RECVFROM +KEY_SOCKET__SENDTO = _selinux.KEY_SOCKET__SENDTO +KEY_SOCKET__RECV_MSG = _selinux.KEY_SOCKET__RECV_MSG +KEY_SOCKET__SEND_MSG = _selinux.KEY_SOCKET__SEND_MSG +KEY_SOCKET__NAME_BIND = _selinux.KEY_SOCKET__NAME_BIND +UNIX_STREAM_SOCKET__IOCTL = _selinux.UNIX_STREAM_SOCKET__IOCTL +UNIX_STREAM_SOCKET__READ = _selinux.UNIX_STREAM_SOCKET__READ +UNIX_STREAM_SOCKET__WRITE = _selinux.UNIX_STREAM_SOCKET__WRITE +UNIX_STREAM_SOCKET__CREATE = _selinux.UNIX_STREAM_SOCKET__CREATE +UNIX_STREAM_SOCKET__GETATTR = _selinux.UNIX_STREAM_SOCKET__GETATTR +UNIX_STREAM_SOCKET__SETATTR = _selinux.UNIX_STREAM_SOCKET__SETATTR +UNIX_STREAM_SOCKET__LOCK = _selinux.UNIX_STREAM_SOCKET__LOCK +UNIX_STREAM_SOCKET__RELABELFROM = _selinux.UNIX_STREAM_SOCKET__RELABELFROM +UNIX_STREAM_SOCKET__RELABELTO = _selinux.UNIX_STREAM_SOCKET__RELABELTO +UNIX_STREAM_SOCKET__APPEND = _selinux.UNIX_STREAM_SOCKET__APPEND +UNIX_STREAM_SOCKET__BIND = _selinux.UNIX_STREAM_SOCKET__BIND +UNIX_STREAM_SOCKET__CONNECT = _selinux.UNIX_STREAM_SOCKET__CONNECT +UNIX_STREAM_SOCKET__LISTEN = _selinux.UNIX_STREAM_SOCKET__LISTEN +UNIX_STREAM_SOCKET__ACCEPT = _selinux.UNIX_STREAM_SOCKET__ACCEPT +UNIX_STREAM_SOCKET__GETOPT = _selinux.UNIX_STREAM_SOCKET__GETOPT +UNIX_STREAM_SOCKET__SETOPT = _selinux.UNIX_STREAM_SOCKET__SETOPT +UNIX_STREAM_SOCKET__SHUTDOWN = _selinux.UNIX_STREAM_SOCKET__SHUTDOWN +UNIX_STREAM_SOCKET__RECVFROM = _selinux.UNIX_STREAM_SOCKET__RECVFROM +UNIX_STREAM_SOCKET__SENDTO = _selinux.UNIX_STREAM_SOCKET__SENDTO +UNIX_STREAM_SOCKET__RECV_MSG = _selinux.UNIX_STREAM_SOCKET__RECV_MSG +UNIX_STREAM_SOCKET__SEND_MSG = _selinux.UNIX_STREAM_SOCKET__SEND_MSG +UNIX_STREAM_SOCKET__NAME_BIND = _selinux.UNIX_STREAM_SOCKET__NAME_BIND +UNIX_STREAM_SOCKET__CONNECTTO = _selinux.UNIX_STREAM_SOCKET__CONNECTTO +UNIX_STREAM_SOCKET__NEWCONN = _selinux.UNIX_STREAM_SOCKET__NEWCONN +UNIX_STREAM_SOCKET__ACCEPTFROM = _selinux.UNIX_STREAM_SOCKET__ACCEPTFROM +UNIX_DGRAM_SOCKET__IOCTL = _selinux.UNIX_DGRAM_SOCKET__IOCTL +UNIX_DGRAM_SOCKET__READ = _selinux.UNIX_DGRAM_SOCKET__READ +UNIX_DGRAM_SOCKET__WRITE = _selinux.UNIX_DGRAM_SOCKET__WRITE +UNIX_DGRAM_SOCKET__CREATE = _selinux.UNIX_DGRAM_SOCKET__CREATE +UNIX_DGRAM_SOCKET__GETATTR = _selinux.UNIX_DGRAM_SOCKET__GETATTR +UNIX_DGRAM_SOCKET__SETATTR = _selinux.UNIX_DGRAM_SOCKET__SETATTR +UNIX_DGRAM_SOCKET__LOCK = _selinux.UNIX_DGRAM_SOCKET__LOCK +UNIX_DGRAM_SOCKET__RELABELFROM = _selinux.UNIX_DGRAM_SOCKET__RELABELFROM +UNIX_DGRAM_SOCKET__RELABELTO = _selinux.UNIX_DGRAM_SOCKET__RELABELTO +UNIX_DGRAM_SOCKET__APPEND = _selinux.UNIX_DGRAM_SOCKET__APPEND +UNIX_DGRAM_SOCKET__BIND = _selinux.UNIX_DGRAM_SOCKET__BIND +UNIX_DGRAM_SOCKET__CONNECT = _selinux.UNIX_DGRAM_SOCKET__CONNECT +UNIX_DGRAM_SOCKET__LISTEN = _selinux.UNIX_DGRAM_SOCKET__LISTEN +UNIX_DGRAM_SOCKET__ACCEPT = _selinux.UNIX_DGRAM_SOCKET__ACCEPT +UNIX_DGRAM_SOCKET__GETOPT = _selinux.UNIX_DGRAM_SOCKET__GETOPT +UNIX_DGRAM_SOCKET__SETOPT = _selinux.UNIX_DGRAM_SOCKET__SETOPT +UNIX_DGRAM_SOCKET__SHUTDOWN = _selinux.UNIX_DGRAM_SOCKET__SHUTDOWN +UNIX_DGRAM_SOCKET__RECVFROM = _selinux.UNIX_DGRAM_SOCKET__RECVFROM +UNIX_DGRAM_SOCKET__SENDTO = _selinux.UNIX_DGRAM_SOCKET__SENDTO +UNIX_DGRAM_SOCKET__RECV_MSG = _selinux.UNIX_DGRAM_SOCKET__RECV_MSG +UNIX_DGRAM_SOCKET__SEND_MSG = _selinux.UNIX_DGRAM_SOCKET__SEND_MSG +UNIX_DGRAM_SOCKET__NAME_BIND = _selinux.UNIX_DGRAM_SOCKET__NAME_BIND +PROCESS__FORK = _selinux.PROCESS__FORK +PROCESS__TRANSITION = _selinux.PROCESS__TRANSITION +PROCESS__SIGCHLD = _selinux.PROCESS__SIGCHLD +PROCESS__SIGKILL = _selinux.PROCESS__SIGKILL +PROCESS__SIGSTOP = _selinux.PROCESS__SIGSTOP +PROCESS__SIGNULL = _selinux.PROCESS__SIGNULL +PROCESS__SIGNAL = _selinux.PROCESS__SIGNAL +PROCESS__PTRACE = _selinux.PROCESS__PTRACE +PROCESS__GETSCHED = _selinux.PROCESS__GETSCHED +PROCESS__SETSCHED = _selinux.PROCESS__SETSCHED +PROCESS__GETSESSION = _selinux.PROCESS__GETSESSION +PROCESS__GETPGID = _selinux.PROCESS__GETPGID +PROCESS__SETPGID = _selinux.PROCESS__SETPGID +PROCESS__GETCAP = _selinux.PROCESS__GETCAP +PROCESS__SETCAP = _selinux.PROCESS__SETCAP +PROCESS__SHARE = _selinux.PROCESS__SHARE +PROCESS__GETATTR = _selinux.PROCESS__GETATTR +PROCESS__SETEXEC = _selinux.PROCESS__SETEXEC +PROCESS__SETFSCREATE = _selinux.PROCESS__SETFSCREATE +PROCESS__NOATSECURE = _selinux.PROCESS__NOATSECURE +PROCESS__SIGINH = _selinux.PROCESS__SIGINH +PROCESS__SETRLIMIT = _selinux.PROCESS__SETRLIMIT +PROCESS__RLIMITINH = _selinux.PROCESS__RLIMITINH +PROCESS__DYNTRANSITION = _selinux.PROCESS__DYNTRANSITION +PROCESS__SETCURRENT = _selinux.PROCESS__SETCURRENT +PROCESS__EXECMEM = _selinux.PROCESS__EXECMEM +PROCESS__EXECSTACK = _selinux.PROCESS__EXECSTACK +PROCESS__EXECHEAP = _selinux.PROCESS__EXECHEAP +PROCESS__SETKEYCREATE = _selinux.PROCESS__SETKEYCREATE +PROCESS__SETSOCKCREATE = _selinux.PROCESS__SETSOCKCREATE +IPC__CREATE = _selinux.IPC__CREATE +IPC__DESTROY = _selinux.IPC__DESTROY +IPC__GETATTR = _selinux.IPC__GETATTR +IPC__SETATTR = _selinux.IPC__SETATTR +IPC__READ = _selinux.IPC__READ +IPC__WRITE = _selinux.IPC__WRITE +IPC__ASSOCIATE = _selinux.IPC__ASSOCIATE +IPC__UNIX_READ = _selinux.IPC__UNIX_READ +IPC__UNIX_WRITE = _selinux.IPC__UNIX_WRITE +SEM__CREATE = _selinux.SEM__CREATE +SEM__DESTROY = _selinux.SEM__DESTROY +SEM__GETATTR = _selinux.SEM__GETATTR +SEM__SETATTR = _selinux.SEM__SETATTR +SEM__READ = _selinux.SEM__READ +SEM__WRITE = _selinux.SEM__WRITE +SEM__ASSOCIATE = _selinux.SEM__ASSOCIATE +SEM__UNIX_READ = _selinux.SEM__UNIX_READ +SEM__UNIX_WRITE = _selinux.SEM__UNIX_WRITE +MSGQ__CREATE = _selinux.MSGQ__CREATE +MSGQ__DESTROY = _selinux.MSGQ__DESTROY +MSGQ__GETATTR = _selinux.MSGQ__GETATTR +MSGQ__SETATTR = _selinux.MSGQ__SETATTR +MSGQ__READ = _selinux.MSGQ__READ +MSGQ__WRITE = _selinux.MSGQ__WRITE +MSGQ__ASSOCIATE = _selinux.MSGQ__ASSOCIATE +MSGQ__UNIX_READ = _selinux.MSGQ__UNIX_READ +MSGQ__UNIX_WRITE = _selinux.MSGQ__UNIX_WRITE +MSGQ__ENQUEUE = _selinux.MSGQ__ENQUEUE +MSG__SEND = _selinux.MSG__SEND +MSG__RECEIVE = _selinux.MSG__RECEIVE +SHM__CREATE = _selinux.SHM__CREATE +SHM__DESTROY = _selinux.SHM__DESTROY +SHM__GETATTR = _selinux.SHM__GETATTR +SHM__SETATTR = _selinux.SHM__SETATTR +SHM__READ = _selinux.SHM__READ +SHM__WRITE = _selinux.SHM__WRITE +SHM__ASSOCIATE = _selinux.SHM__ASSOCIATE +SHM__UNIX_READ = _selinux.SHM__UNIX_READ +SHM__UNIX_WRITE = _selinux.SHM__UNIX_WRITE +SHM__LOCK = _selinux.SHM__LOCK +SECURITY__COMPUTE_AV = _selinux.SECURITY__COMPUTE_AV +SECURITY__COMPUTE_CREATE = _selinux.SECURITY__COMPUTE_CREATE +SECURITY__COMPUTE_MEMBER = _selinux.SECURITY__COMPUTE_MEMBER +SECURITY__CHECK_CONTEXT = _selinux.SECURITY__CHECK_CONTEXT +SECURITY__LOAD_POLICY = _selinux.SECURITY__LOAD_POLICY +SECURITY__COMPUTE_RELABEL = _selinux.SECURITY__COMPUTE_RELABEL +SECURITY__COMPUTE_USER = _selinux.SECURITY__COMPUTE_USER +SECURITY__SETENFORCE = _selinux.SECURITY__SETENFORCE +SECURITY__SETBOOL = _selinux.SECURITY__SETBOOL +SECURITY__SETSECPARAM = _selinux.SECURITY__SETSECPARAM +SECURITY__SETCHECKREQPROT = _selinux.SECURITY__SETCHECKREQPROT +SYSTEM__IPC_INFO = _selinux.SYSTEM__IPC_INFO +SYSTEM__SYSLOG_READ = _selinux.SYSTEM__SYSLOG_READ +SYSTEM__SYSLOG_MOD = _selinux.SYSTEM__SYSLOG_MOD +SYSTEM__SYSLOG_CONSOLE = _selinux.SYSTEM__SYSLOG_CONSOLE +CAPABILITY__CHOWN = _selinux.CAPABILITY__CHOWN +CAPABILITY__DAC_OVERRIDE = _selinux.CAPABILITY__DAC_OVERRIDE +CAPABILITY__DAC_READ_SEARCH = _selinux.CAPABILITY__DAC_READ_SEARCH +CAPABILITY__FOWNER = _selinux.CAPABILITY__FOWNER +CAPABILITY__FSETID = _selinux.CAPABILITY__FSETID +CAPABILITY__KILL = _selinux.CAPABILITY__KILL +CAPABILITY__SETGID = _selinux.CAPABILITY__SETGID +CAPABILITY__SETUID = _selinux.CAPABILITY__SETUID +CAPABILITY__SETPCAP = _selinux.CAPABILITY__SETPCAP +CAPABILITY__LINUX_IMMUTABLE = _selinux.CAPABILITY__LINUX_IMMUTABLE +CAPABILITY__NET_BIND_SERVICE = _selinux.CAPABILITY__NET_BIND_SERVICE +CAPABILITY__NET_BROADCAST = _selinux.CAPABILITY__NET_BROADCAST +CAPABILITY__NET_ADMIN = _selinux.CAPABILITY__NET_ADMIN +CAPABILITY__NET_RAW = _selinux.CAPABILITY__NET_RAW +CAPABILITY__IPC_LOCK = _selinux.CAPABILITY__IPC_LOCK +CAPABILITY__IPC_OWNER = _selinux.CAPABILITY__IPC_OWNER +CAPABILITY__SYS_MODULE = _selinux.CAPABILITY__SYS_MODULE +CAPABILITY__SYS_RAWIO = _selinux.CAPABILITY__SYS_RAWIO +CAPABILITY__SYS_CHROOT = _selinux.CAPABILITY__SYS_CHROOT +CAPABILITY__SYS_PTRACE = _selinux.CAPABILITY__SYS_PTRACE +CAPABILITY__SYS_PACCT = _selinux.CAPABILITY__SYS_PACCT +CAPABILITY__SYS_ADMIN = _selinux.CAPABILITY__SYS_ADMIN +CAPABILITY__SYS_BOOT = _selinux.CAPABILITY__SYS_BOOT +CAPABILITY__SYS_NICE = _selinux.CAPABILITY__SYS_NICE +CAPABILITY__SYS_RESOURCE = _selinux.CAPABILITY__SYS_RESOURCE +CAPABILITY__SYS_TIME = _selinux.CAPABILITY__SYS_TIME +CAPABILITY__SYS_TTY_CONFIG = _selinux.CAPABILITY__SYS_TTY_CONFIG +CAPABILITY__MKNOD = _selinux.CAPABILITY__MKNOD +CAPABILITY__LEASE = _selinux.CAPABILITY__LEASE +CAPABILITY__AUDIT_WRITE = _selinux.CAPABILITY__AUDIT_WRITE +CAPABILITY__AUDIT_CONTROL = _selinux.CAPABILITY__AUDIT_CONTROL +CAPABILITY__SETFCAP = _selinux.CAPABILITY__SETFCAP +CAPABILITY2__MAC_OVERRIDE = _selinux.CAPABILITY2__MAC_OVERRIDE +CAPABILITY2__MAC_ADMIN = _selinux.CAPABILITY2__MAC_ADMIN +PASSWD__PASSWD = _selinux.PASSWD__PASSWD +PASSWD__CHFN = _selinux.PASSWD__CHFN +PASSWD__CHSH = _selinux.PASSWD__CHSH +PASSWD__ROOTOK = _selinux.PASSWD__ROOTOK +PASSWD__CRONTAB = _selinux.PASSWD__CRONTAB +X_DRAWABLE__CREATE = _selinux.X_DRAWABLE__CREATE +X_DRAWABLE__DESTROY = _selinux.X_DRAWABLE__DESTROY +X_DRAWABLE__READ = _selinux.X_DRAWABLE__READ +X_DRAWABLE__WRITE = _selinux.X_DRAWABLE__WRITE +X_DRAWABLE__BLEND = _selinux.X_DRAWABLE__BLEND +X_DRAWABLE__GETATTR = _selinux.X_DRAWABLE__GETATTR +X_DRAWABLE__SETATTR = _selinux.X_DRAWABLE__SETATTR +X_DRAWABLE__LIST_CHILD = _selinux.X_DRAWABLE__LIST_CHILD +X_DRAWABLE__ADD_CHILD = _selinux.X_DRAWABLE__ADD_CHILD +X_DRAWABLE__REMOVE_CHILD = _selinux.X_DRAWABLE__REMOVE_CHILD +X_DRAWABLE__LIST_PROPERTY = _selinux.X_DRAWABLE__LIST_PROPERTY +X_DRAWABLE__GET_PROPERTY = _selinux.X_DRAWABLE__GET_PROPERTY +X_DRAWABLE__SET_PROPERTY = _selinux.X_DRAWABLE__SET_PROPERTY +X_DRAWABLE__MANAGE = _selinux.X_DRAWABLE__MANAGE +X_DRAWABLE__OVERRIDE = _selinux.X_DRAWABLE__OVERRIDE +X_DRAWABLE__SHOW = _selinux.X_DRAWABLE__SHOW +X_DRAWABLE__HIDE = _selinux.X_DRAWABLE__HIDE +X_DRAWABLE__SEND = _selinux.X_DRAWABLE__SEND +X_DRAWABLE__RECEIVE = _selinux.X_DRAWABLE__RECEIVE +X_SCREEN__GETATTR = _selinux.X_SCREEN__GETATTR +X_SCREEN__SETATTR = _selinux.X_SCREEN__SETATTR +X_SCREEN__HIDE_CURSOR = _selinux.X_SCREEN__HIDE_CURSOR +X_SCREEN__SHOW_CURSOR = _selinux.X_SCREEN__SHOW_CURSOR +X_SCREEN__SAVER_GETATTR = _selinux.X_SCREEN__SAVER_GETATTR +X_SCREEN__SAVER_SETATTR = _selinux.X_SCREEN__SAVER_SETATTR +X_SCREEN__SAVER_HIDE = _selinux.X_SCREEN__SAVER_HIDE +X_SCREEN__SAVER_SHOW = _selinux.X_SCREEN__SAVER_SHOW +X_GC__CREATE = _selinux.X_GC__CREATE +X_GC__DESTROY = _selinux.X_GC__DESTROY +X_GC__GETATTR = _selinux.X_GC__GETATTR +X_GC__SETATTR = _selinux.X_GC__SETATTR +X_GC__USE = _selinux.X_GC__USE +X_FONT__CREATE = _selinux.X_FONT__CREATE +X_FONT__DESTROY = _selinux.X_FONT__DESTROY +X_FONT__GETATTR = _selinux.X_FONT__GETATTR +X_FONT__ADD_GLYPH = _selinux.X_FONT__ADD_GLYPH +X_FONT__REMOVE_GLYPH = _selinux.X_FONT__REMOVE_GLYPH +X_FONT__USE = _selinux.X_FONT__USE +X_COLORMAP__CREATE = _selinux.X_COLORMAP__CREATE +X_COLORMAP__DESTROY = _selinux.X_COLORMAP__DESTROY +X_COLORMAP__READ = _selinux.X_COLORMAP__READ +X_COLORMAP__WRITE = _selinux.X_COLORMAP__WRITE +X_COLORMAP__GETATTR = _selinux.X_COLORMAP__GETATTR +X_COLORMAP__ADD_COLOR = _selinux.X_COLORMAP__ADD_COLOR +X_COLORMAP__REMOVE_COLOR = _selinux.X_COLORMAP__REMOVE_COLOR +X_COLORMAP__INSTALL = _selinux.X_COLORMAP__INSTALL +X_COLORMAP__UNINSTALL = _selinux.X_COLORMAP__UNINSTALL +X_COLORMAP__USE = _selinux.X_COLORMAP__USE +X_PROPERTY__CREATE = _selinux.X_PROPERTY__CREATE +X_PROPERTY__DESTROY = _selinux.X_PROPERTY__DESTROY +X_PROPERTY__READ = _selinux.X_PROPERTY__READ +X_PROPERTY__WRITE = _selinux.X_PROPERTY__WRITE +X_PROPERTY__APPEND = _selinux.X_PROPERTY__APPEND +X_PROPERTY__GETATTR = _selinux.X_PROPERTY__GETATTR +X_PROPERTY__SETATTR = _selinux.X_PROPERTY__SETATTR +X_SELECTION__READ = _selinux.X_SELECTION__READ +X_SELECTION__WRITE = _selinux.X_SELECTION__WRITE +X_SELECTION__GETATTR = _selinux.X_SELECTION__GETATTR +X_SELECTION__SETATTR = _selinux.X_SELECTION__SETATTR +X_CURSOR__CREATE = _selinux.X_CURSOR__CREATE +X_CURSOR__DESTROY = _selinux.X_CURSOR__DESTROY +X_CURSOR__READ = _selinux.X_CURSOR__READ +X_CURSOR__WRITE = _selinux.X_CURSOR__WRITE +X_CURSOR__GETATTR = _selinux.X_CURSOR__GETATTR +X_CURSOR__SETATTR = _selinux.X_CURSOR__SETATTR +X_CURSOR__USE = _selinux.X_CURSOR__USE +X_CLIENT__DESTROY = _selinux.X_CLIENT__DESTROY +X_CLIENT__GETATTR = _selinux.X_CLIENT__GETATTR +X_CLIENT__SETATTR = _selinux.X_CLIENT__SETATTR +X_CLIENT__MANAGE = _selinux.X_CLIENT__MANAGE +X_DEVICE__GETATTR = _selinux.X_DEVICE__GETATTR +X_DEVICE__SETATTR = _selinux.X_DEVICE__SETATTR +X_DEVICE__USE = _selinux.X_DEVICE__USE +X_DEVICE__READ = _selinux.X_DEVICE__READ +X_DEVICE__WRITE = _selinux.X_DEVICE__WRITE +X_DEVICE__GETFOCUS = _selinux.X_DEVICE__GETFOCUS +X_DEVICE__SETFOCUS = _selinux.X_DEVICE__SETFOCUS +X_DEVICE__BELL = _selinux.X_DEVICE__BELL +X_DEVICE__FORCE_CURSOR = _selinux.X_DEVICE__FORCE_CURSOR +X_DEVICE__FREEZE = _selinux.X_DEVICE__FREEZE +X_DEVICE__GRAB = _selinux.X_DEVICE__GRAB +X_DEVICE__MANAGE = _selinux.X_DEVICE__MANAGE +X_SERVER__GETATTR = _selinux.X_SERVER__GETATTR +X_SERVER__SETATTR = _selinux.X_SERVER__SETATTR +X_SERVER__RECORD = _selinux.X_SERVER__RECORD +X_SERVER__DEBUG = _selinux.X_SERVER__DEBUG +X_SERVER__GRAB = _selinux.X_SERVER__GRAB +X_SERVER__MANAGE = _selinux.X_SERVER__MANAGE +X_EXTENSION__QUERY = _selinux.X_EXTENSION__QUERY +X_EXTENSION__USE = _selinux.X_EXTENSION__USE +X_RESOURCE__READ = _selinux.X_RESOURCE__READ +X_RESOURCE__WRITE = _selinux.X_RESOURCE__WRITE +X_EVENT__SEND = _selinux.X_EVENT__SEND +X_EVENT__RECEIVE = _selinux.X_EVENT__RECEIVE +X_SYNTHETIC_EVENT__SEND = _selinux.X_SYNTHETIC_EVENT__SEND +X_SYNTHETIC_EVENT__RECEIVE = _selinux.X_SYNTHETIC_EVENT__RECEIVE +NETLINK_ROUTE_SOCKET__IOCTL = _selinux.NETLINK_ROUTE_SOCKET__IOCTL +NETLINK_ROUTE_SOCKET__READ = _selinux.NETLINK_ROUTE_SOCKET__READ +NETLINK_ROUTE_SOCKET__WRITE = _selinux.NETLINK_ROUTE_SOCKET__WRITE +NETLINK_ROUTE_SOCKET__CREATE = _selinux.NETLINK_ROUTE_SOCKET__CREATE +NETLINK_ROUTE_SOCKET__GETATTR = _selinux.NETLINK_ROUTE_SOCKET__GETATTR +NETLINK_ROUTE_SOCKET__SETATTR = _selinux.NETLINK_ROUTE_SOCKET__SETATTR +NETLINK_ROUTE_SOCKET__LOCK = _selinux.NETLINK_ROUTE_SOCKET__LOCK +NETLINK_ROUTE_SOCKET__RELABELFROM = _selinux.NETLINK_ROUTE_SOCKET__RELABELFROM +NETLINK_ROUTE_SOCKET__RELABELTO = _selinux.NETLINK_ROUTE_SOCKET__RELABELTO +NETLINK_ROUTE_SOCKET__APPEND = _selinux.NETLINK_ROUTE_SOCKET__APPEND +NETLINK_ROUTE_SOCKET__BIND = _selinux.NETLINK_ROUTE_SOCKET__BIND +NETLINK_ROUTE_SOCKET__CONNECT = _selinux.NETLINK_ROUTE_SOCKET__CONNECT +NETLINK_ROUTE_SOCKET__LISTEN = _selinux.NETLINK_ROUTE_SOCKET__LISTEN +NETLINK_ROUTE_SOCKET__ACCEPT = _selinux.NETLINK_ROUTE_SOCKET__ACCEPT +NETLINK_ROUTE_SOCKET__GETOPT = _selinux.NETLINK_ROUTE_SOCKET__GETOPT +NETLINK_ROUTE_SOCKET__SETOPT = _selinux.NETLINK_ROUTE_SOCKET__SETOPT +NETLINK_ROUTE_SOCKET__SHUTDOWN = _selinux.NETLINK_ROUTE_SOCKET__SHUTDOWN +NETLINK_ROUTE_SOCKET__RECVFROM = _selinux.NETLINK_ROUTE_SOCKET__RECVFROM +NETLINK_ROUTE_SOCKET__SENDTO = _selinux.NETLINK_ROUTE_SOCKET__SENDTO +NETLINK_ROUTE_SOCKET__RECV_MSG = _selinux.NETLINK_ROUTE_SOCKET__RECV_MSG +NETLINK_ROUTE_SOCKET__SEND_MSG = _selinux.NETLINK_ROUTE_SOCKET__SEND_MSG +NETLINK_ROUTE_SOCKET__NAME_BIND = _selinux.NETLINK_ROUTE_SOCKET__NAME_BIND +NETLINK_ROUTE_SOCKET__NLMSG_READ = _selinux.NETLINK_ROUTE_SOCKET__NLMSG_READ +NETLINK_ROUTE_SOCKET__NLMSG_WRITE = _selinux.NETLINK_ROUTE_SOCKET__NLMSG_WRITE +NETLINK_FIREWALL_SOCKET__IOCTL = _selinux.NETLINK_FIREWALL_SOCKET__IOCTL +NETLINK_FIREWALL_SOCKET__READ = _selinux.NETLINK_FIREWALL_SOCKET__READ +NETLINK_FIREWALL_SOCKET__WRITE = _selinux.NETLINK_FIREWALL_SOCKET__WRITE +NETLINK_FIREWALL_SOCKET__CREATE = _selinux.NETLINK_FIREWALL_SOCKET__CREATE +NETLINK_FIREWALL_SOCKET__GETATTR = _selinux.NETLINK_FIREWALL_SOCKET__GETATTR +NETLINK_FIREWALL_SOCKET__SETATTR = _selinux.NETLINK_FIREWALL_SOCKET__SETATTR +NETLINK_FIREWALL_SOCKET__LOCK = _selinux.NETLINK_FIREWALL_SOCKET__LOCK +NETLINK_FIREWALL_SOCKET__RELABELFROM = _selinux.NETLINK_FIREWALL_SOCKET__RELABELFROM +NETLINK_FIREWALL_SOCKET__RELABELTO = _selinux.NETLINK_FIREWALL_SOCKET__RELABELTO +NETLINK_FIREWALL_SOCKET__APPEND = _selinux.NETLINK_FIREWALL_SOCKET__APPEND +NETLINK_FIREWALL_SOCKET__BIND = _selinux.NETLINK_FIREWALL_SOCKET__BIND +NETLINK_FIREWALL_SOCKET__CONNECT = _selinux.NETLINK_FIREWALL_SOCKET__CONNECT +NETLINK_FIREWALL_SOCKET__LISTEN = _selinux.NETLINK_FIREWALL_SOCKET__LISTEN +NETLINK_FIREWALL_SOCKET__ACCEPT = _selinux.NETLINK_FIREWALL_SOCKET__ACCEPT +NETLINK_FIREWALL_SOCKET__GETOPT = _selinux.NETLINK_FIREWALL_SOCKET__GETOPT +NETLINK_FIREWALL_SOCKET__SETOPT = _selinux.NETLINK_FIREWALL_SOCKET__SETOPT +NETLINK_FIREWALL_SOCKET__SHUTDOWN = _selinux.NETLINK_FIREWALL_SOCKET__SHUTDOWN +NETLINK_FIREWALL_SOCKET__RECVFROM = _selinux.NETLINK_FIREWALL_SOCKET__RECVFROM +NETLINK_FIREWALL_SOCKET__SENDTO = _selinux.NETLINK_FIREWALL_SOCKET__SENDTO +NETLINK_FIREWALL_SOCKET__RECV_MSG = _selinux.NETLINK_FIREWALL_SOCKET__RECV_MSG +NETLINK_FIREWALL_SOCKET__SEND_MSG = _selinux.NETLINK_FIREWALL_SOCKET__SEND_MSG +NETLINK_FIREWALL_SOCKET__NAME_BIND = _selinux.NETLINK_FIREWALL_SOCKET__NAME_BIND +NETLINK_FIREWALL_SOCKET__NLMSG_READ = _selinux.NETLINK_FIREWALL_SOCKET__NLMSG_READ +NETLINK_FIREWALL_SOCKET__NLMSG_WRITE = _selinux.NETLINK_FIREWALL_SOCKET__NLMSG_WRITE +NETLINK_TCPDIAG_SOCKET__IOCTL = _selinux.NETLINK_TCPDIAG_SOCKET__IOCTL +NETLINK_TCPDIAG_SOCKET__READ = _selinux.NETLINK_TCPDIAG_SOCKET__READ +NETLINK_TCPDIAG_SOCKET__WRITE = _selinux.NETLINK_TCPDIAG_SOCKET__WRITE +NETLINK_TCPDIAG_SOCKET__CREATE = _selinux.NETLINK_TCPDIAG_SOCKET__CREATE +NETLINK_TCPDIAG_SOCKET__GETATTR = _selinux.NETLINK_TCPDIAG_SOCKET__GETATTR +NETLINK_TCPDIAG_SOCKET__SETATTR = _selinux.NETLINK_TCPDIAG_SOCKET__SETATTR +NETLINK_TCPDIAG_SOCKET__LOCK = _selinux.NETLINK_TCPDIAG_SOCKET__LOCK +NETLINK_TCPDIAG_SOCKET__RELABELFROM = _selinux.NETLINK_TCPDIAG_SOCKET__RELABELFROM +NETLINK_TCPDIAG_SOCKET__RELABELTO = _selinux.NETLINK_TCPDIAG_SOCKET__RELABELTO +NETLINK_TCPDIAG_SOCKET__APPEND = _selinux.NETLINK_TCPDIAG_SOCKET__APPEND +NETLINK_TCPDIAG_SOCKET__BIND = _selinux.NETLINK_TCPDIAG_SOCKET__BIND +NETLINK_TCPDIAG_SOCKET__CONNECT = _selinux.NETLINK_TCPDIAG_SOCKET__CONNECT +NETLINK_TCPDIAG_SOCKET__LISTEN = _selinux.NETLINK_TCPDIAG_SOCKET__LISTEN +NETLINK_TCPDIAG_SOCKET__ACCEPT = _selinux.NETLINK_TCPDIAG_SOCKET__ACCEPT +NETLINK_TCPDIAG_SOCKET__GETOPT = _selinux.NETLINK_TCPDIAG_SOCKET__GETOPT +NETLINK_TCPDIAG_SOCKET__SETOPT = _selinux.NETLINK_TCPDIAG_SOCKET__SETOPT +NETLINK_TCPDIAG_SOCKET__SHUTDOWN = _selinux.NETLINK_TCPDIAG_SOCKET__SHUTDOWN +NETLINK_TCPDIAG_SOCKET__RECVFROM = _selinux.NETLINK_TCPDIAG_SOCKET__RECVFROM +NETLINK_TCPDIAG_SOCKET__SENDTO = _selinux.NETLINK_TCPDIAG_SOCKET__SENDTO +NETLINK_TCPDIAG_SOCKET__RECV_MSG = _selinux.NETLINK_TCPDIAG_SOCKET__RECV_MSG +NETLINK_TCPDIAG_SOCKET__SEND_MSG = _selinux.NETLINK_TCPDIAG_SOCKET__SEND_MSG +NETLINK_TCPDIAG_SOCKET__NAME_BIND = _selinux.NETLINK_TCPDIAG_SOCKET__NAME_BIND +NETLINK_TCPDIAG_SOCKET__NLMSG_READ = _selinux.NETLINK_TCPDIAG_SOCKET__NLMSG_READ +NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE = _selinux.NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE +NETLINK_NFLOG_SOCKET__IOCTL = _selinux.NETLINK_NFLOG_SOCKET__IOCTL +NETLINK_NFLOG_SOCKET__READ = _selinux.NETLINK_NFLOG_SOCKET__READ +NETLINK_NFLOG_SOCKET__WRITE = _selinux.NETLINK_NFLOG_SOCKET__WRITE +NETLINK_NFLOG_SOCKET__CREATE = _selinux.NETLINK_NFLOG_SOCKET__CREATE +NETLINK_NFLOG_SOCKET__GETATTR = _selinux.NETLINK_NFLOG_SOCKET__GETATTR +NETLINK_NFLOG_SOCKET__SETATTR = _selinux.NETLINK_NFLOG_SOCKET__SETATTR +NETLINK_NFLOG_SOCKET__LOCK = _selinux.NETLINK_NFLOG_SOCKET__LOCK +NETLINK_NFLOG_SOCKET__RELABELFROM = _selinux.NETLINK_NFLOG_SOCKET__RELABELFROM +NETLINK_NFLOG_SOCKET__RELABELTO = _selinux.NETLINK_NFLOG_SOCKET__RELABELTO +NETLINK_NFLOG_SOCKET__APPEND = _selinux.NETLINK_NFLOG_SOCKET__APPEND +NETLINK_NFLOG_SOCKET__BIND = _selinux.NETLINK_NFLOG_SOCKET__BIND +NETLINK_NFLOG_SOCKET__CONNECT = _selinux.NETLINK_NFLOG_SOCKET__CONNECT +NETLINK_NFLOG_SOCKET__LISTEN = _selinux.NETLINK_NFLOG_SOCKET__LISTEN +NETLINK_NFLOG_SOCKET__ACCEPT = _selinux.NETLINK_NFLOG_SOCKET__ACCEPT +NETLINK_NFLOG_SOCKET__GETOPT = _selinux.NETLINK_NFLOG_SOCKET__GETOPT +NETLINK_NFLOG_SOCKET__SETOPT = _selinux.NETLINK_NFLOG_SOCKET__SETOPT +NETLINK_NFLOG_SOCKET__SHUTDOWN = _selinux.NETLINK_NFLOG_SOCKET__SHUTDOWN +NETLINK_NFLOG_SOCKET__RECVFROM = _selinux.NETLINK_NFLOG_SOCKET__RECVFROM +NETLINK_NFLOG_SOCKET__SENDTO = _selinux.NETLINK_NFLOG_SOCKET__SENDTO +NETLINK_NFLOG_SOCKET__RECV_MSG = _selinux.NETLINK_NFLOG_SOCKET__RECV_MSG +NETLINK_NFLOG_SOCKET__SEND_MSG = _selinux.NETLINK_NFLOG_SOCKET__SEND_MSG +NETLINK_NFLOG_SOCKET__NAME_BIND = _selinux.NETLINK_NFLOG_SOCKET__NAME_BIND +NETLINK_XFRM_SOCKET__IOCTL = _selinux.NETLINK_XFRM_SOCKET__IOCTL +NETLINK_XFRM_SOCKET__READ = _selinux.NETLINK_XFRM_SOCKET__READ +NETLINK_XFRM_SOCKET__WRITE = _selinux.NETLINK_XFRM_SOCKET__WRITE +NETLINK_XFRM_SOCKET__CREATE = _selinux.NETLINK_XFRM_SOCKET__CREATE +NETLINK_XFRM_SOCKET__GETATTR = _selinux.NETLINK_XFRM_SOCKET__GETATTR +NETLINK_XFRM_SOCKET__SETATTR = _selinux.NETLINK_XFRM_SOCKET__SETATTR +NETLINK_XFRM_SOCKET__LOCK = _selinux.NETLINK_XFRM_SOCKET__LOCK +NETLINK_XFRM_SOCKET__RELABELFROM = _selinux.NETLINK_XFRM_SOCKET__RELABELFROM +NETLINK_XFRM_SOCKET__RELABELTO = _selinux.NETLINK_XFRM_SOCKET__RELABELTO +NETLINK_XFRM_SOCKET__APPEND = _selinux.NETLINK_XFRM_SOCKET__APPEND +NETLINK_XFRM_SOCKET__BIND = _selinux.NETLINK_XFRM_SOCKET__BIND +NETLINK_XFRM_SOCKET__CONNECT = _selinux.NETLINK_XFRM_SOCKET__CONNECT +NETLINK_XFRM_SOCKET__LISTEN = _selinux.NETLINK_XFRM_SOCKET__LISTEN +NETLINK_XFRM_SOCKET__ACCEPT = _selinux.NETLINK_XFRM_SOCKET__ACCEPT +NETLINK_XFRM_SOCKET__GETOPT = _selinux.NETLINK_XFRM_SOCKET__GETOPT +NETLINK_XFRM_SOCKET__SETOPT = _selinux.NETLINK_XFRM_SOCKET__SETOPT +NETLINK_XFRM_SOCKET__SHUTDOWN = _selinux.NETLINK_XFRM_SOCKET__SHUTDOWN +NETLINK_XFRM_SOCKET__RECVFROM = _selinux.NETLINK_XFRM_SOCKET__RECVFROM +NETLINK_XFRM_SOCKET__SENDTO = _selinux.NETLINK_XFRM_SOCKET__SENDTO +NETLINK_XFRM_SOCKET__RECV_MSG = _selinux.NETLINK_XFRM_SOCKET__RECV_MSG +NETLINK_XFRM_SOCKET__SEND_MSG = _selinux.NETLINK_XFRM_SOCKET__SEND_MSG +NETLINK_XFRM_SOCKET__NAME_BIND = _selinux.NETLINK_XFRM_SOCKET__NAME_BIND +NETLINK_XFRM_SOCKET__NLMSG_READ = _selinux.NETLINK_XFRM_SOCKET__NLMSG_READ +NETLINK_XFRM_SOCKET__NLMSG_WRITE = _selinux.NETLINK_XFRM_SOCKET__NLMSG_WRITE +NETLINK_SELINUX_SOCKET__IOCTL = _selinux.NETLINK_SELINUX_SOCKET__IOCTL +NETLINK_SELINUX_SOCKET__READ = _selinux.NETLINK_SELINUX_SOCKET__READ +NETLINK_SELINUX_SOCKET__WRITE = _selinux.NETLINK_SELINUX_SOCKET__WRITE +NETLINK_SELINUX_SOCKET__CREATE = _selinux.NETLINK_SELINUX_SOCKET__CREATE +NETLINK_SELINUX_SOCKET__GETATTR = _selinux.NETLINK_SELINUX_SOCKET__GETATTR +NETLINK_SELINUX_SOCKET__SETATTR = _selinux.NETLINK_SELINUX_SOCKET__SETATTR +NETLINK_SELINUX_SOCKET__LOCK = _selinux.NETLINK_SELINUX_SOCKET__LOCK +NETLINK_SELINUX_SOCKET__RELABELFROM = _selinux.NETLINK_SELINUX_SOCKET__RELABELFROM +NETLINK_SELINUX_SOCKET__RELABELTO = _selinux.NETLINK_SELINUX_SOCKET__RELABELTO +NETLINK_SELINUX_SOCKET__APPEND = _selinux.NETLINK_SELINUX_SOCKET__APPEND +NETLINK_SELINUX_SOCKET__BIND = _selinux.NETLINK_SELINUX_SOCKET__BIND +NETLINK_SELINUX_SOCKET__CONNECT = _selinux.NETLINK_SELINUX_SOCKET__CONNECT +NETLINK_SELINUX_SOCKET__LISTEN = _selinux.NETLINK_SELINUX_SOCKET__LISTEN +NETLINK_SELINUX_SOCKET__ACCEPT = _selinux.NETLINK_SELINUX_SOCKET__ACCEPT +NETLINK_SELINUX_SOCKET__GETOPT = _selinux.NETLINK_SELINUX_SOCKET__GETOPT +NETLINK_SELINUX_SOCKET__SETOPT = _selinux.NETLINK_SELINUX_SOCKET__SETOPT +NETLINK_SELINUX_SOCKET__SHUTDOWN = _selinux.NETLINK_SELINUX_SOCKET__SHUTDOWN +NETLINK_SELINUX_SOCKET__RECVFROM = _selinux.NETLINK_SELINUX_SOCKET__RECVFROM +NETLINK_SELINUX_SOCKET__SENDTO = _selinux.NETLINK_SELINUX_SOCKET__SENDTO +NETLINK_SELINUX_SOCKET__RECV_MSG = _selinux.NETLINK_SELINUX_SOCKET__RECV_MSG +NETLINK_SELINUX_SOCKET__SEND_MSG = _selinux.NETLINK_SELINUX_SOCKET__SEND_MSG +NETLINK_SELINUX_SOCKET__NAME_BIND = _selinux.NETLINK_SELINUX_SOCKET__NAME_BIND +NETLINK_AUDIT_SOCKET__IOCTL = _selinux.NETLINK_AUDIT_SOCKET__IOCTL +NETLINK_AUDIT_SOCKET__READ = _selinux.NETLINK_AUDIT_SOCKET__READ +NETLINK_AUDIT_SOCKET__WRITE = _selinux.NETLINK_AUDIT_SOCKET__WRITE +NETLINK_AUDIT_SOCKET__CREATE = _selinux.NETLINK_AUDIT_SOCKET__CREATE +NETLINK_AUDIT_SOCKET__GETATTR = _selinux.NETLINK_AUDIT_SOCKET__GETATTR +NETLINK_AUDIT_SOCKET__SETATTR = _selinux.NETLINK_AUDIT_SOCKET__SETATTR +NETLINK_AUDIT_SOCKET__LOCK = _selinux.NETLINK_AUDIT_SOCKET__LOCK +NETLINK_AUDIT_SOCKET__RELABELFROM = _selinux.NETLINK_AUDIT_SOCKET__RELABELFROM +NETLINK_AUDIT_SOCKET__RELABELTO = _selinux.NETLINK_AUDIT_SOCKET__RELABELTO +NETLINK_AUDIT_SOCKET__APPEND = _selinux.NETLINK_AUDIT_SOCKET__APPEND +NETLINK_AUDIT_SOCKET__BIND = _selinux.NETLINK_AUDIT_SOCKET__BIND +NETLINK_AUDIT_SOCKET__CONNECT = _selinux.NETLINK_AUDIT_SOCKET__CONNECT +NETLINK_AUDIT_SOCKET__LISTEN = _selinux.NETLINK_AUDIT_SOCKET__LISTEN +NETLINK_AUDIT_SOCKET__ACCEPT = _selinux.NETLINK_AUDIT_SOCKET__ACCEPT +NETLINK_AUDIT_SOCKET__GETOPT = _selinux.NETLINK_AUDIT_SOCKET__GETOPT +NETLINK_AUDIT_SOCKET__SETOPT = _selinux.NETLINK_AUDIT_SOCKET__SETOPT +NETLINK_AUDIT_SOCKET__SHUTDOWN = _selinux.NETLINK_AUDIT_SOCKET__SHUTDOWN +NETLINK_AUDIT_SOCKET__RECVFROM = _selinux.NETLINK_AUDIT_SOCKET__RECVFROM +NETLINK_AUDIT_SOCKET__SENDTO = _selinux.NETLINK_AUDIT_SOCKET__SENDTO +NETLINK_AUDIT_SOCKET__RECV_MSG = _selinux.NETLINK_AUDIT_SOCKET__RECV_MSG +NETLINK_AUDIT_SOCKET__SEND_MSG = _selinux.NETLINK_AUDIT_SOCKET__SEND_MSG +NETLINK_AUDIT_SOCKET__NAME_BIND = _selinux.NETLINK_AUDIT_SOCKET__NAME_BIND +NETLINK_AUDIT_SOCKET__NLMSG_READ = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_READ +NETLINK_AUDIT_SOCKET__NLMSG_WRITE = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_WRITE +NETLINK_AUDIT_SOCKET__NLMSG_RELAY = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_RELAY +NETLINK_AUDIT_SOCKET__NLMSG_READPRIV = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_READPRIV +NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT +NETLINK_IP6FW_SOCKET__IOCTL = _selinux.NETLINK_IP6FW_SOCKET__IOCTL +NETLINK_IP6FW_SOCKET__READ = _selinux.NETLINK_IP6FW_SOCKET__READ +NETLINK_IP6FW_SOCKET__WRITE = _selinux.NETLINK_IP6FW_SOCKET__WRITE +NETLINK_IP6FW_SOCKET__CREATE = _selinux.NETLINK_IP6FW_SOCKET__CREATE +NETLINK_IP6FW_SOCKET__GETATTR = _selinux.NETLINK_IP6FW_SOCKET__GETATTR +NETLINK_IP6FW_SOCKET__SETATTR = _selinux.NETLINK_IP6FW_SOCKET__SETATTR +NETLINK_IP6FW_SOCKET__LOCK = _selinux.NETLINK_IP6FW_SOCKET__LOCK +NETLINK_IP6FW_SOCKET__RELABELFROM = _selinux.NETLINK_IP6FW_SOCKET__RELABELFROM +NETLINK_IP6FW_SOCKET__RELABELTO = _selinux.NETLINK_IP6FW_SOCKET__RELABELTO +NETLINK_IP6FW_SOCKET__APPEND = _selinux.NETLINK_IP6FW_SOCKET__APPEND +NETLINK_IP6FW_SOCKET__BIND = _selinux.NETLINK_IP6FW_SOCKET__BIND +NETLINK_IP6FW_SOCKET__CONNECT = _selinux.NETLINK_IP6FW_SOCKET__CONNECT +NETLINK_IP6FW_SOCKET__LISTEN = _selinux.NETLINK_IP6FW_SOCKET__LISTEN +NETLINK_IP6FW_SOCKET__ACCEPT = _selinux.NETLINK_IP6FW_SOCKET__ACCEPT +NETLINK_IP6FW_SOCKET__GETOPT = _selinux.NETLINK_IP6FW_SOCKET__GETOPT +NETLINK_IP6FW_SOCKET__SETOPT = _selinux.NETLINK_IP6FW_SOCKET__SETOPT +NETLINK_IP6FW_SOCKET__SHUTDOWN = _selinux.NETLINK_IP6FW_SOCKET__SHUTDOWN +NETLINK_IP6FW_SOCKET__RECVFROM = _selinux.NETLINK_IP6FW_SOCKET__RECVFROM +NETLINK_IP6FW_SOCKET__SENDTO = _selinux.NETLINK_IP6FW_SOCKET__SENDTO +NETLINK_IP6FW_SOCKET__RECV_MSG = _selinux.NETLINK_IP6FW_SOCKET__RECV_MSG +NETLINK_IP6FW_SOCKET__SEND_MSG = _selinux.NETLINK_IP6FW_SOCKET__SEND_MSG +NETLINK_IP6FW_SOCKET__NAME_BIND = _selinux.NETLINK_IP6FW_SOCKET__NAME_BIND +NETLINK_IP6FW_SOCKET__NLMSG_READ = _selinux.NETLINK_IP6FW_SOCKET__NLMSG_READ +NETLINK_IP6FW_SOCKET__NLMSG_WRITE = _selinux.NETLINK_IP6FW_SOCKET__NLMSG_WRITE +NETLINK_DNRT_SOCKET__IOCTL = _selinux.NETLINK_DNRT_SOCKET__IOCTL +NETLINK_DNRT_SOCKET__READ = _selinux.NETLINK_DNRT_SOCKET__READ +NETLINK_DNRT_SOCKET__WRITE = _selinux.NETLINK_DNRT_SOCKET__WRITE +NETLINK_DNRT_SOCKET__CREATE = _selinux.NETLINK_DNRT_SOCKET__CREATE +NETLINK_DNRT_SOCKET__GETATTR = _selinux.NETLINK_DNRT_SOCKET__GETATTR +NETLINK_DNRT_SOCKET__SETATTR = _selinux.NETLINK_DNRT_SOCKET__SETATTR +NETLINK_DNRT_SOCKET__LOCK = _selinux.NETLINK_DNRT_SOCKET__LOCK +NETLINK_DNRT_SOCKET__RELABELFROM = _selinux.NETLINK_DNRT_SOCKET__RELABELFROM +NETLINK_DNRT_SOCKET__RELABELTO = _selinux.NETLINK_DNRT_SOCKET__RELABELTO +NETLINK_DNRT_SOCKET__APPEND = _selinux.NETLINK_DNRT_SOCKET__APPEND +NETLINK_DNRT_SOCKET__BIND = _selinux.NETLINK_DNRT_SOCKET__BIND +NETLINK_DNRT_SOCKET__CONNECT = _selinux.NETLINK_DNRT_SOCKET__CONNECT +NETLINK_DNRT_SOCKET__LISTEN = _selinux.NETLINK_DNRT_SOCKET__LISTEN +NETLINK_DNRT_SOCKET__ACCEPT = _selinux.NETLINK_DNRT_SOCKET__ACCEPT +NETLINK_DNRT_SOCKET__GETOPT = _selinux.NETLINK_DNRT_SOCKET__GETOPT +NETLINK_DNRT_SOCKET__SETOPT = _selinux.NETLINK_DNRT_SOCKET__SETOPT +NETLINK_DNRT_SOCKET__SHUTDOWN = _selinux.NETLINK_DNRT_SOCKET__SHUTDOWN +NETLINK_DNRT_SOCKET__RECVFROM = _selinux.NETLINK_DNRT_SOCKET__RECVFROM +NETLINK_DNRT_SOCKET__SENDTO = _selinux.NETLINK_DNRT_SOCKET__SENDTO +NETLINK_DNRT_SOCKET__RECV_MSG = _selinux.NETLINK_DNRT_SOCKET__RECV_MSG +NETLINK_DNRT_SOCKET__SEND_MSG = _selinux.NETLINK_DNRT_SOCKET__SEND_MSG +NETLINK_DNRT_SOCKET__NAME_BIND = _selinux.NETLINK_DNRT_SOCKET__NAME_BIND +DBUS__ACQUIRE_SVC = _selinux.DBUS__ACQUIRE_SVC +DBUS__SEND_MSG = _selinux.DBUS__SEND_MSG +NSCD__GETPWD = _selinux.NSCD__GETPWD +NSCD__GETGRP = _selinux.NSCD__GETGRP +NSCD__GETHOST = _selinux.NSCD__GETHOST +NSCD__GETSTAT = _selinux.NSCD__GETSTAT +NSCD__ADMIN = _selinux.NSCD__ADMIN +NSCD__SHMEMPWD = _selinux.NSCD__SHMEMPWD +NSCD__SHMEMGRP = _selinux.NSCD__SHMEMGRP +NSCD__SHMEMHOST = _selinux.NSCD__SHMEMHOST +NSCD__GETSERV = _selinux.NSCD__GETSERV +NSCD__SHMEMSERV = _selinux.NSCD__SHMEMSERV +ASSOCIATION__SENDTO = _selinux.ASSOCIATION__SENDTO +ASSOCIATION__RECVFROM = _selinux.ASSOCIATION__RECVFROM +ASSOCIATION__SETCONTEXT = _selinux.ASSOCIATION__SETCONTEXT +ASSOCIATION__POLMATCH = _selinux.ASSOCIATION__POLMATCH +NETLINK_KOBJECT_UEVENT_SOCKET__IOCTL = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__IOCTL +NETLINK_KOBJECT_UEVENT_SOCKET__READ = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__READ +NETLINK_KOBJECT_UEVENT_SOCKET__WRITE = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__WRITE +NETLINK_KOBJECT_UEVENT_SOCKET__CREATE = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__CREATE +NETLINK_KOBJECT_UEVENT_SOCKET__GETATTR = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__GETATTR +NETLINK_KOBJECT_UEVENT_SOCKET__SETATTR = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SETATTR +NETLINK_KOBJECT_UEVENT_SOCKET__LOCK = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__LOCK +NETLINK_KOBJECT_UEVENT_SOCKET__RELABELFROM = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RELABELFROM +NETLINK_KOBJECT_UEVENT_SOCKET__RELABELTO = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RELABELTO +NETLINK_KOBJECT_UEVENT_SOCKET__APPEND = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__APPEND +NETLINK_KOBJECT_UEVENT_SOCKET__BIND = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__BIND +NETLINK_KOBJECT_UEVENT_SOCKET__CONNECT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__CONNECT +NETLINK_KOBJECT_UEVENT_SOCKET__LISTEN = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__LISTEN +NETLINK_KOBJECT_UEVENT_SOCKET__ACCEPT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__ACCEPT +NETLINK_KOBJECT_UEVENT_SOCKET__GETOPT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__GETOPT +NETLINK_KOBJECT_UEVENT_SOCKET__SETOPT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SETOPT +NETLINK_KOBJECT_UEVENT_SOCKET__SHUTDOWN = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SHUTDOWN +NETLINK_KOBJECT_UEVENT_SOCKET__RECVFROM = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RECVFROM +NETLINK_KOBJECT_UEVENT_SOCKET__SENDTO = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SENDTO +NETLINK_KOBJECT_UEVENT_SOCKET__RECV_MSG = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RECV_MSG +NETLINK_KOBJECT_UEVENT_SOCKET__SEND_MSG = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SEND_MSG +NETLINK_KOBJECT_UEVENT_SOCKET__NAME_BIND = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__NAME_BIND +APPLETALK_SOCKET__IOCTL = _selinux.APPLETALK_SOCKET__IOCTL +APPLETALK_SOCKET__READ = _selinux.APPLETALK_SOCKET__READ +APPLETALK_SOCKET__WRITE = _selinux.APPLETALK_SOCKET__WRITE +APPLETALK_SOCKET__CREATE = _selinux.APPLETALK_SOCKET__CREATE +APPLETALK_SOCKET__GETATTR = _selinux.APPLETALK_SOCKET__GETATTR +APPLETALK_SOCKET__SETATTR = _selinux.APPLETALK_SOCKET__SETATTR +APPLETALK_SOCKET__LOCK = _selinux.APPLETALK_SOCKET__LOCK +APPLETALK_SOCKET__RELABELFROM = _selinux.APPLETALK_SOCKET__RELABELFROM +APPLETALK_SOCKET__RELABELTO = _selinux.APPLETALK_SOCKET__RELABELTO +APPLETALK_SOCKET__APPEND = _selinux.APPLETALK_SOCKET__APPEND +APPLETALK_SOCKET__BIND = _selinux.APPLETALK_SOCKET__BIND +APPLETALK_SOCKET__CONNECT = _selinux.APPLETALK_SOCKET__CONNECT +APPLETALK_SOCKET__LISTEN = _selinux.APPLETALK_SOCKET__LISTEN +APPLETALK_SOCKET__ACCEPT = _selinux.APPLETALK_SOCKET__ACCEPT +APPLETALK_SOCKET__GETOPT = _selinux.APPLETALK_SOCKET__GETOPT +APPLETALK_SOCKET__SETOPT = _selinux.APPLETALK_SOCKET__SETOPT +APPLETALK_SOCKET__SHUTDOWN = _selinux.APPLETALK_SOCKET__SHUTDOWN +APPLETALK_SOCKET__RECVFROM = _selinux.APPLETALK_SOCKET__RECVFROM +APPLETALK_SOCKET__SENDTO = _selinux.APPLETALK_SOCKET__SENDTO +APPLETALK_SOCKET__RECV_MSG = _selinux.APPLETALK_SOCKET__RECV_MSG +APPLETALK_SOCKET__SEND_MSG = _selinux.APPLETALK_SOCKET__SEND_MSG +APPLETALK_SOCKET__NAME_BIND = _selinux.APPLETALK_SOCKET__NAME_BIND +PACKET__SEND = _selinux.PACKET__SEND +PACKET__RECV = _selinux.PACKET__RECV +PACKET__RELABELTO = _selinux.PACKET__RELABELTO +PACKET__FLOW_IN = _selinux.PACKET__FLOW_IN +PACKET__FLOW_OUT = _selinux.PACKET__FLOW_OUT +PACKET__FORWARD_IN = _selinux.PACKET__FORWARD_IN +PACKET__FORWARD_OUT = _selinux.PACKET__FORWARD_OUT +KEY__VIEW = _selinux.KEY__VIEW +KEY__READ = _selinux.KEY__READ +KEY__WRITE = _selinux.KEY__WRITE +KEY__SEARCH = _selinux.KEY__SEARCH +KEY__LINK = _selinux.KEY__LINK +KEY__SETATTR = _selinux.KEY__SETATTR +KEY__CREATE = _selinux.KEY__CREATE +CONTEXT__TRANSLATE = _selinux.CONTEXT__TRANSLATE +CONTEXT__CONTAINS = _selinux.CONTEXT__CONTAINS +DCCP_SOCKET__IOCTL = _selinux.DCCP_SOCKET__IOCTL +DCCP_SOCKET__READ = _selinux.DCCP_SOCKET__READ +DCCP_SOCKET__WRITE = _selinux.DCCP_SOCKET__WRITE +DCCP_SOCKET__CREATE = _selinux.DCCP_SOCKET__CREATE +DCCP_SOCKET__GETATTR = _selinux.DCCP_SOCKET__GETATTR +DCCP_SOCKET__SETATTR = _selinux.DCCP_SOCKET__SETATTR +DCCP_SOCKET__LOCK = _selinux.DCCP_SOCKET__LOCK +DCCP_SOCKET__RELABELFROM = _selinux.DCCP_SOCKET__RELABELFROM +DCCP_SOCKET__RELABELTO = _selinux.DCCP_SOCKET__RELABELTO +DCCP_SOCKET__APPEND = _selinux.DCCP_SOCKET__APPEND +DCCP_SOCKET__BIND = _selinux.DCCP_SOCKET__BIND +DCCP_SOCKET__CONNECT = _selinux.DCCP_SOCKET__CONNECT +DCCP_SOCKET__LISTEN = _selinux.DCCP_SOCKET__LISTEN +DCCP_SOCKET__ACCEPT = _selinux.DCCP_SOCKET__ACCEPT +DCCP_SOCKET__GETOPT = _selinux.DCCP_SOCKET__GETOPT +DCCP_SOCKET__SETOPT = _selinux.DCCP_SOCKET__SETOPT +DCCP_SOCKET__SHUTDOWN = _selinux.DCCP_SOCKET__SHUTDOWN +DCCP_SOCKET__RECVFROM = _selinux.DCCP_SOCKET__RECVFROM +DCCP_SOCKET__SENDTO = _selinux.DCCP_SOCKET__SENDTO +DCCP_SOCKET__RECV_MSG = _selinux.DCCP_SOCKET__RECV_MSG +DCCP_SOCKET__SEND_MSG = _selinux.DCCP_SOCKET__SEND_MSG +DCCP_SOCKET__NAME_BIND = _selinux.DCCP_SOCKET__NAME_BIND +DCCP_SOCKET__NODE_BIND = _selinux.DCCP_SOCKET__NODE_BIND +DCCP_SOCKET__NAME_CONNECT = _selinux.DCCP_SOCKET__NAME_CONNECT +MEMPROTECT__MMAP_ZERO = _selinux.MEMPROTECT__MMAP_ZERO +DB_DATABASE__CREATE = _selinux.DB_DATABASE__CREATE +DB_DATABASE__DROP = _selinux.DB_DATABASE__DROP +DB_DATABASE__GETATTR = _selinux.DB_DATABASE__GETATTR +DB_DATABASE__SETATTR = _selinux.DB_DATABASE__SETATTR +DB_DATABASE__RELABELFROM = _selinux.DB_DATABASE__RELABELFROM +DB_DATABASE__RELABELTO = _selinux.DB_DATABASE__RELABELTO +DB_DATABASE__ACCESS = _selinux.DB_DATABASE__ACCESS +DB_DATABASE__INSTALL_MODULE = _selinux.DB_DATABASE__INSTALL_MODULE +DB_DATABASE__LOAD_MODULE = _selinux.DB_DATABASE__LOAD_MODULE +DB_DATABASE__GET_PARAM = _selinux.DB_DATABASE__GET_PARAM +DB_DATABASE__SET_PARAM = _selinux.DB_DATABASE__SET_PARAM +DB_TABLE__CREATE = _selinux.DB_TABLE__CREATE +DB_TABLE__DROP = _selinux.DB_TABLE__DROP +DB_TABLE__GETATTR = _selinux.DB_TABLE__GETATTR +DB_TABLE__SETATTR = _selinux.DB_TABLE__SETATTR +DB_TABLE__RELABELFROM = _selinux.DB_TABLE__RELABELFROM +DB_TABLE__RELABELTO = _selinux.DB_TABLE__RELABELTO +DB_TABLE__USE = _selinux.DB_TABLE__USE +DB_TABLE__SELECT = _selinux.DB_TABLE__SELECT +DB_TABLE__UPDATE = _selinux.DB_TABLE__UPDATE +DB_TABLE__INSERT = _selinux.DB_TABLE__INSERT +DB_TABLE__DELETE = _selinux.DB_TABLE__DELETE +DB_TABLE__LOCK = _selinux.DB_TABLE__LOCK +DB_PROCEDURE__CREATE = _selinux.DB_PROCEDURE__CREATE +DB_PROCEDURE__DROP = _selinux.DB_PROCEDURE__DROP +DB_PROCEDURE__GETATTR = _selinux.DB_PROCEDURE__GETATTR +DB_PROCEDURE__SETATTR = _selinux.DB_PROCEDURE__SETATTR +DB_PROCEDURE__RELABELFROM = _selinux.DB_PROCEDURE__RELABELFROM +DB_PROCEDURE__RELABELTO = _selinux.DB_PROCEDURE__RELABELTO +DB_PROCEDURE__EXECUTE = _selinux.DB_PROCEDURE__EXECUTE +DB_PROCEDURE__ENTRYPOINT = _selinux.DB_PROCEDURE__ENTRYPOINT +DB_COLUMN__CREATE = _selinux.DB_COLUMN__CREATE +DB_COLUMN__DROP = _selinux.DB_COLUMN__DROP +DB_COLUMN__GETATTR = _selinux.DB_COLUMN__GETATTR +DB_COLUMN__SETATTR = _selinux.DB_COLUMN__SETATTR +DB_COLUMN__RELABELFROM = _selinux.DB_COLUMN__RELABELFROM +DB_COLUMN__RELABELTO = _selinux.DB_COLUMN__RELABELTO +DB_COLUMN__USE = _selinux.DB_COLUMN__USE +DB_COLUMN__SELECT = _selinux.DB_COLUMN__SELECT +DB_COLUMN__UPDATE = _selinux.DB_COLUMN__UPDATE +DB_COLUMN__INSERT = _selinux.DB_COLUMN__INSERT +DB_TUPLE__RELABELFROM = _selinux.DB_TUPLE__RELABELFROM +DB_TUPLE__RELABELTO = _selinux.DB_TUPLE__RELABELTO +DB_TUPLE__USE = _selinux.DB_TUPLE__USE +DB_TUPLE__SELECT = _selinux.DB_TUPLE__SELECT +DB_TUPLE__UPDATE = _selinux.DB_TUPLE__UPDATE +DB_TUPLE__INSERT = _selinux.DB_TUPLE__INSERT +DB_TUPLE__DELETE = _selinux.DB_TUPLE__DELETE +DB_BLOB__CREATE = _selinux.DB_BLOB__CREATE +DB_BLOB__DROP = _selinux.DB_BLOB__DROP +DB_BLOB__GETATTR = _selinux.DB_BLOB__GETATTR +DB_BLOB__SETATTR = _selinux.DB_BLOB__SETATTR +DB_BLOB__RELABELFROM = _selinux.DB_BLOB__RELABELFROM +DB_BLOB__RELABELTO = _selinux.DB_BLOB__RELABELTO +DB_BLOB__READ = _selinux.DB_BLOB__READ +DB_BLOB__WRITE = _selinux.DB_BLOB__WRITE +DB_BLOB__IMPORT = _selinux.DB_BLOB__IMPORT +DB_BLOB__EXPORT = _selinux.DB_BLOB__EXPORT +PEER__RECV = _selinux.PEER__RECV +X_APPLICATION_DATA__PASTE = _selinux.X_APPLICATION_DATA__PASTE +X_APPLICATION_DATA__PASTE_AFTER_CONFIRM = _selinux.X_APPLICATION_DATA__PASTE_AFTER_CONFIRM +X_APPLICATION_DATA__COPY = _selinux.X_APPLICATION_DATA__COPY +class context_s_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, context_s_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, context_s_t, name) + __repr__ = _swig_repr + __swig_setmethods__["ptr"] = _selinux.context_s_t_ptr_set + __swig_getmethods__["ptr"] = _selinux.context_s_t_ptr_get + if _newclass:ptr = _swig_property(_selinux.context_s_t_ptr_get, _selinux.context_s_t_ptr_set) + def __init__(self): + this = _selinux.new_context_s_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_context_s_t + __del__ = lambda self : None; +context_s_t_swigregister = _selinux.context_s_t_swigregister +context_s_t_swigregister(context_s_t) + + +def context_new(*args): + return _selinux.context_new(*args) +context_new = _selinux.context_new + +def context_str(*args): + return _selinux.context_str(*args) +context_str = _selinux.context_str + +def context_free(*args): + return _selinux.context_free(*args) +context_free = _selinux.context_free + +def context_type_get(*args): + return _selinux.context_type_get(*args) +context_type_get = _selinux.context_type_get + +def context_range_get(*args): + return _selinux.context_range_get(*args) +context_range_get = _selinux.context_range_get + +def context_role_get(*args): + return _selinux.context_role_get(*args) +context_role_get = _selinux.context_role_get + +def context_user_get(*args): + return _selinux.context_user_get(*args) +context_user_get = _selinux.context_user_get + +def context_type_set(*args): + return _selinux.context_type_set(*args) +context_type_set = _selinux.context_type_set + +def context_range_set(*args): + return _selinux.context_range_set(*args) +context_range_set = _selinux.context_range_set + +def context_role_set(*args): + return _selinux.context_role_set(*args) +context_role_set = _selinux.context_role_set + +def context_user_set(*args): + return _selinux.context_user_set(*args) +context_user_set = _selinux.context_user_set +SECCLASS_SECURITY = _selinux.SECCLASS_SECURITY +SECCLASS_PROCESS = _selinux.SECCLASS_PROCESS +SECCLASS_SYSTEM = _selinux.SECCLASS_SYSTEM +SECCLASS_CAPABILITY = _selinux.SECCLASS_CAPABILITY +SECCLASS_FILESYSTEM = _selinux.SECCLASS_FILESYSTEM +SECCLASS_FILE = _selinux.SECCLASS_FILE +SECCLASS_DIR = _selinux.SECCLASS_DIR +SECCLASS_FD = _selinux.SECCLASS_FD +SECCLASS_LNK_FILE = _selinux.SECCLASS_LNK_FILE +SECCLASS_CHR_FILE = _selinux.SECCLASS_CHR_FILE +SECCLASS_BLK_FILE = _selinux.SECCLASS_BLK_FILE +SECCLASS_SOCK_FILE = _selinux.SECCLASS_SOCK_FILE +SECCLASS_FIFO_FILE = _selinux.SECCLASS_FIFO_FILE +SECCLASS_SOCKET = _selinux.SECCLASS_SOCKET +SECCLASS_TCP_SOCKET = _selinux.SECCLASS_TCP_SOCKET +SECCLASS_UDP_SOCKET = _selinux.SECCLASS_UDP_SOCKET +SECCLASS_RAWIP_SOCKET = _selinux.SECCLASS_RAWIP_SOCKET +SECCLASS_NODE = _selinux.SECCLASS_NODE +SECCLASS_NETIF = _selinux.SECCLASS_NETIF +SECCLASS_NETLINK_SOCKET = _selinux.SECCLASS_NETLINK_SOCKET +SECCLASS_PACKET_SOCKET = _selinux.SECCLASS_PACKET_SOCKET +SECCLASS_KEY_SOCKET = _selinux.SECCLASS_KEY_SOCKET +SECCLASS_UNIX_STREAM_SOCKET = _selinux.SECCLASS_UNIX_STREAM_SOCKET +SECCLASS_UNIX_DGRAM_SOCKET = _selinux.SECCLASS_UNIX_DGRAM_SOCKET +SECCLASS_SEM = _selinux.SECCLASS_SEM +SECCLASS_MSG = _selinux.SECCLASS_MSG +SECCLASS_MSGQ = _selinux.SECCLASS_MSGQ +SECCLASS_SHM = _selinux.SECCLASS_SHM +SECCLASS_IPC = _selinux.SECCLASS_IPC +SECCLASS_PASSWD = _selinux.SECCLASS_PASSWD +SECCLASS_X_DRAWABLE = _selinux.SECCLASS_X_DRAWABLE +SECCLASS_X_SCREEN = _selinux.SECCLASS_X_SCREEN +SECCLASS_X_GC = _selinux.SECCLASS_X_GC +SECCLASS_X_FONT = _selinux.SECCLASS_X_FONT +SECCLASS_X_COLORMAP = _selinux.SECCLASS_X_COLORMAP +SECCLASS_X_PROPERTY = _selinux.SECCLASS_X_PROPERTY +SECCLASS_X_SELECTION = _selinux.SECCLASS_X_SELECTION +SECCLASS_X_CURSOR = _selinux.SECCLASS_X_CURSOR +SECCLASS_X_CLIENT = _selinux.SECCLASS_X_CLIENT +SECCLASS_X_DEVICE = _selinux.SECCLASS_X_DEVICE +SECCLASS_X_SERVER = _selinux.SECCLASS_X_SERVER +SECCLASS_X_EXTENSION = _selinux.SECCLASS_X_EXTENSION +SECCLASS_NETLINK_ROUTE_SOCKET = _selinux.SECCLASS_NETLINK_ROUTE_SOCKET +SECCLASS_NETLINK_FIREWALL_SOCKET = _selinux.SECCLASS_NETLINK_FIREWALL_SOCKET +SECCLASS_NETLINK_TCPDIAG_SOCKET = _selinux.SECCLASS_NETLINK_TCPDIAG_SOCKET +SECCLASS_NETLINK_NFLOG_SOCKET = _selinux.SECCLASS_NETLINK_NFLOG_SOCKET +SECCLASS_NETLINK_XFRM_SOCKET = _selinux.SECCLASS_NETLINK_XFRM_SOCKET +SECCLASS_NETLINK_SELINUX_SOCKET = _selinux.SECCLASS_NETLINK_SELINUX_SOCKET +SECCLASS_NETLINK_AUDIT_SOCKET = _selinux.SECCLASS_NETLINK_AUDIT_SOCKET +SECCLASS_NETLINK_IP6FW_SOCKET = _selinux.SECCLASS_NETLINK_IP6FW_SOCKET +SECCLASS_NETLINK_DNRT_SOCKET = _selinux.SECCLASS_NETLINK_DNRT_SOCKET +SECCLASS_DBUS = _selinux.SECCLASS_DBUS +SECCLASS_NSCD = _selinux.SECCLASS_NSCD +SECCLASS_ASSOCIATION = _selinux.SECCLASS_ASSOCIATION +SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET = _selinux.SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET +SECCLASS_APPLETALK_SOCKET = _selinux.SECCLASS_APPLETALK_SOCKET +SECCLASS_PACKET = _selinux.SECCLASS_PACKET +SECCLASS_KEY = _selinux.SECCLASS_KEY +SECCLASS_CONTEXT = _selinux.SECCLASS_CONTEXT +SECCLASS_DCCP_SOCKET = _selinux.SECCLASS_DCCP_SOCKET +SECCLASS_MEMPROTECT = _selinux.SECCLASS_MEMPROTECT +SECCLASS_DB_DATABASE = _selinux.SECCLASS_DB_DATABASE +SECCLASS_DB_TABLE = _selinux.SECCLASS_DB_TABLE +SECCLASS_DB_PROCEDURE = _selinux.SECCLASS_DB_PROCEDURE +SECCLASS_DB_COLUMN = _selinux.SECCLASS_DB_COLUMN +SECCLASS_DB_TUPLE = _selinux.SECCLASS_DB_TUPLE +SECCLASS_DB_BLOB = _selinux.SECCLASS_DB_BLOB +SECCLASS_PEER = _selinux.SECCLASS_PEER +SECCLASS_CAPABILITY2 = _selinux.SECCLASS_CAPABILITY2 +SECCLASS_X_RESOURCE = _selinux.SECCLASS_X_RESOURCE +SECCLASS_X_EVENT = _selinux.SECCLASS_X_EVENT +SECCLASS_X_SYNTHETIC_EVENT = _selinux.SECCLASS_X_SYNTHETIC_EVENT +SECCLASS_X_APPLICATION_DATA = _selinux.SECCLASS_X_APPLICATION_DATA +SECINITSID_KERNEL = _selinux.SECINITSID_KERNEL +SECINITSID_SECURITY = _selinux.SECINITSID_SECURITY +SECINITSID_UNLABELED = _selinux.SECINITSID_UNLABELED +SECINITSID_FS = _selinux.SECINITSID_FS +SECINITSID_FILE = _selinux.SECINITSID_FILE +SECINITSID_FILE_LABELS = _selinux.SECINITSID_FILE_LABELS +SECINITSID_INIT = _selinux.SECINITSID_INIT +SECINITSID_ANY_SOCKET = _selinux.SECINITSID_ANY_SOCKET +SECINITSID_PORT = _selinux.SECINITSID_PORT +SECINITSID_NETIF = _selinux.SECINITSID_NETIF +SECINITSID_NETMSG = _selinux.SECINITSID_NETMSG +SECINITSID_NODE = _selinux.SECINITSID_NODE +SECINITSID_IGMP_PACKET = _selinux.SECINITSID_IGMP_PACKET +SECINITSID_ICMP_SOCKET = _selinux.SECINITSID_ICMP_SOCKET +SECINITSID_TCP_SOCKET = _selinux.SECINITSID_TCP_SOCKET +SECINITSID_SYSCTL_MODPROBE = _selinux.SECINITSID_SYSCTL_MODPROBE +SECINITSID_SYSCTL = _selinux.SECINITSID_SYSCTL +SECINITSID_SYSCTL_FS = _selinux.SECINITSID_SYSCTL_FS +SECINITSID_SYSCTL_KERNEL = _selinux.SECINITSID_SYSCTL_KERNEL +SECINITSID_SYSCTL_NET = _selinux.SECINITSID_SYSCTL_NET +SECINITSID_SYSCTL_NET_UNIX = _selinux.SECINITSID_SYSCTL_NET_UNIX +SECINITSID_SYSCTL_VM = _selinux.SECINITSID_SYSCTL_VM +SECINITSID_SYSCTL_DEV = _selinux.SECINITSID_SYSCTL_DEV +SECINITSID_KMOD = _selinux.SECINITSID_KMOD +SECINITSID_POLICY = _selinux.SECINITSID_POLICY +SECINITSID_SCMP_PACKET = _selinux.SECINITSID_SCMP_PACKET +SECINITSID_DEVNULL = _selinux.SECINITSID_DEVNULL +SECINITSID_NUM = _selinux.SECINITSID_NUM +SELINUX_DEFAULTUSER = _selinux.SELINUX_DEFAULTUSER + +def get_ordered_context_list(*args): + return _selinux.get_ordered_context_list(*args) +get_ordered_context_list = _selinux.get_ordered_context_list + +def get_ordered_context_list_with_level(*args): + return _selinux.get_ordered_context_list_with_level(*args) +get_ordered_context_list_with_level = _selinux.get_ordered_context_list_with_level + +def get_default_context(*args): + return _selinux.get_default_context(*args) +get_default_context = _selinux.get_default_context + +def get_default_context_with_level(*args): + return _selinux.get_default_context_with_level(*args) +get_default_context_with_level = _selinux.get_default_context_with_level + +def get_default_context_with_role(*args): + return _selinux.get_default_context_with_role(*args) +get_default_context_with_role = _selinux.get_default_context_with_role + +def get_default_context_with_rolelevel(*args): + return _selinux.get_default_context_with_rolelevel(*args) +get_default_context_with_rolelevel = _selinux.get_default_context_with_rolelevel + +def query_user_context(): + return _selinux.query_user_context() +query_user_context = _selinux.query_user_context + +def manual_user_enter_context(*args): + return _selinux.manual_user_enter_context(*args) +manual_user_enter_context = _selinux.manual_user_enter_context + +def selinux_default_type_path(): + return _selinux.selinux_default_type_path() +selinux_default_type_path = _selinux.selinux_default_type_path + +def get_default_type(*args): + return _selinux.get_default_type(*args) +get_default_type = _selinux.get_default_type +SELABEL_CTX_FILE = _selinux.SELABEL_CTX_FILE +SELABEL_CTX_MEDIA = _selinux.SELABEL_CTX_MEDIA +SELABEL_CTX_X = _selinux.SELABEL_CTX_X +SELABEL_CTX_DB = _selinux.SELABEL_CTX_DB +SELABEL_CTX_ANDROID_PROP = _selinux.SELABEL_CTX_ANDROID_PROP +SELABEL_OPT_UNUSED = _selinux.SELABEL_OPT_UNUSED +SELABEL_OPT_VALIDATE = _selinux.SELABEL_OPT_VALIDATE +SELABEL_OPT_BASEONLY = _selinux.SELABEL_OPT_BASEONLY +SELABEL_OPT_PATH = _selinux.SELABEL_OPT_PATH +SELABEL_OPT_SUBSET = _selinux.SELABEL_OPT_SUBSET +SELABEL_NOPT = _selinux.SELABEL_NOPT + +def selabel_open(*args): + return _selinux.selabel_open(*args) +selabel_open = _selinux.selabel_open + +def selabel_close(*args): + return _selinux.selabel_close(*args) +selabel_close = _selinux.selabel_close + +def selabel_lookup(*args): + return _selinux.selabel_lookup(*args) +selabel_lookup = _selinux.selabel_lookup + +def selabel_lookup_raw(*args): + return _selinux.selabel_lookup_raw(*args) +selabel_lookup_raw = _selinux.selabel_lookup_raw + +def selabel_partial_match(*args): + return _selinux.selabel_partial_match(*args) +selabel_partial_match = _selinux.selabel_partial_match + +def selabel_lookup_best_match(*args): + return _selinux.selabel_lookup_best_match(*args) +selabel_lookup_best_match = _selinux.selabel_lookup_best_match + +def selabel_lookup_best_match_raw(*args): + return _selinux.selabel_lookup_best_match_raw(*args) +selabel_lookup_best_match_raw = _selinux.selabel_lookup_best_match_raw + +def selabel_stats(*args): + return _selinux.selabel_stats(*args) +selabel_stats = _selinux.selabel_stats +SELABEL_X_PROP = _selinux.SELABEL_X_PROP +SELABEL_X_EXT = _selinux.SELABEL_X_EXT +SELABEL_X_CLIENT = _selinux.SELABEL_X_CLIENT +SELABEL_X_EVENT = _selinux.SELABEL_X_EVENT +SELABEL_X_SELN = _selinux.SELABEL_X_SELN +SELABEL_X_POLYPROP = _selinux.SELABEL_X_POLYPROP +SELABEL_X_POLYSELN = _selinux.SELABEL_X_POLYSELN +SELABEL_DB_DATABASE = _selinux.SELABEL_DB_DATABASE +SELABEL_DB_SCHEMA = _selinux.SELABEL_DB_SCHEMA +SELABEL_DB_TABLE = _selinux.SELABEL_DB_TABLE +SELABEL_DB_COLUMN = _selinux.SELABEL_DB_COLUMN +SELABEL_DB_SEQUENCE = _selinux.SELABEL_DB_SEQUENCE +SELABEL_DB_VIEW = _selinux.SELABEL_DB_VIEW +SELABEL_DB_PROCEDURE = _selinux.SELABEL_DB_PROCEDURE +SELABEL_DB_BLOB = _selinux.SELABEL_DB_BLOB +SELABEL_DB_TUPLE = _selinux.SELABEL_DB_TUPLE +SELABEL_DB_LANGUAGE = _selinux.SELABEL_DB_LANGUAGE +SELABEL_DB_EXCEPTION = _selinux.SELABEL_DB_EXCEPTION +SELABEL_DB_DATATYPE = _selinux.SELABEL_DB_DATATYPE + +def is_selinux_enabled(): + return _selinux.is_selinux_enabled() +is_selinux_enabled = _selinux.is_selinux_enabled + +def is_selinux_mls_enabled(): + return _selinux.is_selinux_mls_enabled() +is_selinux_mls_enabled = _selinux.is_selinux_mls_enabled + +def getcon(): + return _selinux.getcon() +getcon = _selinux.getcon + +def getcon_raw(): + return _selinux.getcon_raw() +getcon_raw = _selinux.getcon_raw + +def setcon(*args): + return _selinux.setcon(*args) +setcon = _selinux.setcon + +def setcon_raw(*args): + return _selinux.setcon_raw(*args) +setcon_raw = _selinux.setcon_raw + +def getpidcon(*args): + return _selinux.getpidcon(*args) +getpidcon = _selinux.getpidcon + +def getpidcon_raw(*args): + return _selinux.getpidcon_raw(*args) +getpidcon_raw = _selinux.getpidcon_raw + +def getprevcon(): + return _selinux.getprevcon() +getprevcon = _selinux.getprevcon + +def getprevcon_raw(): + return _selinux.getprevcon_raw() +getprevcon_raw = _selinux.getprevcon_raw + +def getexeccon(): + return _selinux.getexeccon() +getexeccon = _selinux.getexeccon + +def getexeccon_raw(): + return _selinux.getexeccon_raw() +getexeccon_raw = _selinux.getexeccon_raw + +def setexeccon(*args): + return _selinux.setexeccon(*args) +setexeccon = _selinux.setexeccon + +def setexeccon_raw(*args): + return _selinux.setexeccon_raw(*args) +setexeccon_raw = _selinux.setexeccon_raw + +def getfscreatecon(): + return _selinux.getfscreatecon() +getfscreatecon = _selinux.getfscreatecon + +def getfscreatecon_raw(): + return _selinux.getfscreatecon_raw() +getfscreatecon_raw = _selinux.getfscreatecon_raw + +def setfscreatecon(*args): + return _selinux.setfscreatecon(*args) +setfscreatecon = _selinux.setfscreatecon + +def setfscreatecon_raw(*args): + return _selinux.setfscreatecon_raw(*args) +setfscreatecon_raw = _selinux.setfscreatecon_raw + +def getkeycreatecon(): + return _selinux.getkeycreatecon() +getkeycreatecon = _selinux.getkeycreatecon + +def getkeycreatecon_raw(): + return _selinux.getkeycreatecon_raw() +getkeycreatecon_raw = _selinux.getkeycreatecon_raw + +def setkeycreatecon(*args): + return _selinux.setkeycreatecon(*args) +setkeycreatecon = _selinux.setkeycreatecon + +def setkeycreatecon_raw(*args): + return _selinux.setkeycreatecon_raw(*args) +setkeycreatecon_raw = _selinux.setkeycreatecon_raw + +def getsockcreatecon(): + return _selinux.getsockcreatecon() +getsockcreatecon = _selinux.getsockcreatecon + +def getsockcreatecon_raw(): + return _selinux.getsockcreatecon_raw() +getsockcreatecon_raw = _selinux.getsockcreatecon_raw + +def setsockcreatecon(*args): + return _selinux.setsockcreatecon(*args) +setsockcreatecon = _selinux.setsockcreatecon + +def setsockcreatecon_raw(*args): + return _selinux.setsockcreatecon_raw(*args) +setsockcreatecon_raw = _selinux.setsockcreatecon_raw + +def getfilecon(*args): + return _selinux.getfilecon(*args) +getfilecon = _selinux.getfilecon + +def getfilecon_raw(*args): + return _selinux.getfilecon_raw(*args) +getfilecon_raw = _selinux.getfilecon_raw + +def lgetfilecon(*args): + return _selinux.lgetfilecon(*args) +lgetfilecon = _selinux.lgetfilecon + +def lgetfilecon_raw(*args): + return _selinux.lgetfilecon_raw(*args) +lgetfilecon_raw = _selinux.lgetfilecon_raw + +def fgetfilecon(*args): + return _selinux.fgetfilecon(*args) +fgetfilecon = _selinux.fgetfilecon + +def fgetfilecon_raw(*args): + return _selinux.fgetfilecon_raw(*args) +fgetfilecon_raw = _selinux.fgetfilecon_raw + +def setfilecon(*args): + return _selinux.setfilecon(*args) +setfilecon = _selinux.setfilecon + +def setfilecon_raw(*args): + return _selinux.setfilecon_raw(*args) +setfilecon_raw = _selinux.setfilecon_raw + +def lsetfilecon(*args): + return _selinux.lsetfilecon(*args) +lsetfilecon = _selinux.lsetfilecon + +def lsetfilecon_raw(*args): + return _selinux.lsetfilecon_raw(*args) +lsetfilecon_raw = _selinux.lsetfilecon_raw + +def fsetfilecon(*args): + return _selinux.fsetfilecon(*args) +fsetfilecon = _selinux.fsetfilecon + +def fsetfilecon_raw(*args): + return _selinux.fsetfilecon_raw(*args) +fsetfilecon_raw = _selinux.fsetfilecon_raw + +def getpeercon(*args): + return _selinux.getpeercon(*args) +getpeercon = _selinux.getpeercon + +def getpeercon_raw(*args): + return _selinux.getpeercon_raw(*args) +getpeercon_raw = _selinux.getpeercon_raw +class av_decision(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, av_decision, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, av_decision, name) + __repr__ = _swig_repr + __swig_setmethods__["allowed"] = _selinux.av_decision_allowed_set + __swig_getmethods__["allowed"] = _selinux.av_decision_allowed_get + if _newclass:allowed = _swig_property(_selinux.av_decision_allowed_get, _selinux.av_decision_allowed_set) + __swig_setmethods__["decided"] = _selinux.av_decision_decided_set + __swig_getmethods__["decided"] = _selinux.av_decision_decided_get + if _newclass:decided = _swig_property(_selinux.av_decision_decided_get, _selinux.av_decision_decided_set) + __swig_setmethods__["auditallow"] = _selinux.av_decision_auditallow_set + __swig_getmethods__["auditallow"] = _selinux.av_decision_auditallow_get + if _newclass:auditallow = _swig_property(_selinux.av_decision_auditallow_get, _selinux.av_decision_auditallow_set) + __swig_setmethods__["auditdeny"] = _selinux.av_decision_auditdeny_set + __swig_getmethods__["auditdeny"] = _selinux.av_decision_auditdeny_get + if _newclass:auditdeny = _swig_property(_selinux.av_decision_auditdeny_get, _selinux.av_decision_auditdeny_set) + __swig_setmethods__["seqno"] = _selinux.av_decision_seqno_set + __swig_getmethods__["seqno"] = _selinux.av_decision_seqno_get + if _newclass:seqno = _swig_property(_selinux.av_decision_seqno_get, _selinux.av_decision_seqno_set) + __swig_setmethods__["flags"] = _selinux.av_decision_flags_set + __swig_getmethods__["flags"] = _selinux.av_decision_flags_get + if _newclass:flags = _swig_property(_selinux.av_decision_flags_get, _selinux.av_decision_flags_set) + def __init__(self): + this = _selinux.new_av_decision() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_av_decision + __del__ = lambda self : None; +av_decision_swigregister = _selinux.av_decision_swigregister +av_decision_swigregister(av_decision) + +SELINUX_AVD_FLAGS_PERMISSIVE = _selinux.SELINUX_AVD_FLAGS_PERMISSIVE +class selinux_opt(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, selinux_opt, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, selinux_opt, name) + __repr__ = _swig_repr + __swig_setmethods__["type"] = _selinux.selinux_opt_type_set + __swig_getmethods__["type"] = _selinux.selinux_opt_type_get + if _newclass:type = _swig_property(_selinux.selinux_opt_type_get, _selinux.selinux_opt_type_set) + __swig_setmethods__["value"] = _selinux.selinux_opt_value_set + __swig_getmethods__["value"] = _selinux.selinux_opt_value_get + if _newclass:value = _swig_property(_selinux.selinux_opt_value_get, _selinux.selinux_opt_value_set) + def __init__(self): + this = _selinux.new_selinux_opt() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_selinux_opt + __del__ = lambda self : None; +selinux_opt_swigregister = _selinux.selinux_opt_swigregister +selinux_opt_swigregister(selinux_opt) + +class selinux_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, selinux_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, selinux_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_log"] = _selinux.selinux_callback_func_log_set + __swig_getmethods__["func_log"] = _selinux.selinux_callback_func_log_get + if _newclass:func_log = _swig_property(_selinux.selinux_callback_func_log_get, _selinux.selinux_callback_func_log_set) + __swig_setmethods__["func_audit"] = _selinux.selinux_callback_func_audit_set + __swig_getmethods__["func_audit"] = _selinux.selinux_callback_func_audit_get + if _newclass:func_audit = _swig_property(_selinux.selinux_callback_func_audit_get, _selinux.selinux_callback_func_audit_set) + __swig_setmethods__["func_validate"] = _selinux.selinux_callback_func_validate_set + __swig_getmethods__["func_validate"] = _selinux.selinux_callback_func_validate_get + if _newclass:func_validate = _swig_property(_selinux.selinux_callback_func_validate_get, _selinux.selinux_callback_func_validate_set) + __swig_setmethods__["func_setenforce"] = _selinux.selinux_callback_func_setenforce_set + __swig_getmethods__["func_setenforce"] = _selinux.selinux_callback_func_setenforce_get + if _newclass:func_setenforce = _swig_property(_selinux.selinux_callback_func_setenforce_get, _selinux.selinux_callback_func_setenforce_set) + __swig_setmethods__["func_policyload"] = _selinux.selinux_callback_func_policyload_set + __swig_getmethods__["func_policyload"] = _selinux.selinux_callback_func_policyload_get + if _newclass:func_policyload = _swig_property(_selinux.selinux_callback_func_policyload_get, _selinux.selinux_callback_func_policyload_set) + def __init__(self): + this = _selinux.new_selinux_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_selinux_callback + __del__ = lambda self : None; +selinux_callback_swigregister = _selinux.selinux_callback_swigregister +selinux_callback_swigregister(selinux_callback) + +SELINUX_CB_LOG = _selinux.SELINUX_CB_LOG +SELINUX_CB_AUDIT = _selinux.SELINUX_CB_AUDIT +SELINUX_CB_VALIDATE = _selinux.SELINUX_CB_VALIDATE +SELINUX_CB_SETENFORCE = _selinux.SELINUX_CB_SETENFORCE +SELINUX_CB_POLICYLOAD = _selinux.SELINUX_CB_POLICYLOAD + +def selinux_get_callback(*args): + return _selinux.selinux_get_callback(*args) +selinux_get_callback = _selinux.selinux_get_callback + +def selinux_set_callback(*args): + return _selinux.selinux_set_callback(*args) +selinux_set_callback = _selinux.selinux_set_callback +SELINUX_ERROR = _selinux.SELINUX_ERROR +SELINUX_WARNING = _selinux.SELINUX_WARNING +SELINUX_INFO = _selinux.SELINUX_INFO +SELINUX_AVC = _selinux.SELINUX_AVC +SELINUX_TRANS_DIR = _selinux.SELINUX_TRANS_DIR + +def security_compute_av(*args): + return _selinux.security_compute_av(*args) +security_compute_av = _selinux.security_compute_av + +def security_compute_av_raw(*args): + return _selinux.security_compute_av_raw(*args) +security_compute_av_raw = _selinux.security_compute_av_raw + +def security_compute_av_flags(*args): + return _selinux.security_compute_av_flags(*args) +security_compute_av_flags = _selinux.security_compute_av_flags + +def security_compute_av_flags_raw(*args): + return _selinux.security_compute_av_flags_raw(*args) +security_compute_av_flags_raw = _selinux.security_compute_av_flags_raw + +def security_compute_create(*args): + return _selinux.security_compute_create(*args) +security_compute_create = _selinux.security_compute_create + +def security_compute_create_raw(*args): + return _selinux.security_compute_create_raw(*args) +security_compute_create_raw = _selinux.security_compute_create_raw + +def security_compute_create_name(*args): + return _selinux.security_compute_create_name(*args) +security_compute_create_name = _selinux.security_compute_create_name + +def security_compute_create_name_raw(*args): + return _selinux.security_compute_create_name_raw(*args) +security_compute_create_name_raw = _selinux.security_compute_create_name_raw + +def security_compute_relabel(*args): + return _selinux.security_compute_relabel(*args) +security_compute_relabel = _selinux.security_compute_relabel + +def security_compute_relabel_raw(*args): + return _selinux.security_compute_relabel_raw(*args) +security_compute_relabel_raw = _selinux.security_compute_relabel_raw + +def security_compute_member(*args): + return _selinux.security_compute_member(*args) +security_compute_member = _selinux.security_compute_member + +def security_compute_member_raw(*args): + return _selinux.security_compute_member_raw(*args) +security_compute_member_raw = _selinux.security_compute_member_raw + +def security_compute_user(*args): + return _selinux.security_compute_user(*args) +security_compute_user = _selinux.security_compute_user + +def security_compute_user_raw(*args): + return _selinux.security_compute_user_raw(*args) +security_compute_user_raw = _selinux.security_compute_user_raw + +def security_load_policy(*args): + return _selinux.security_load_policy(*args) +security_load_policy = _selinux.security_load_policy + +def security_get_initial_context(*args): + return _selinux.security_get_initial_context(*args) +security_get_initial_context = _selinux.security_get_initial_context + +def security_get_initial_context_raw(*args): + return _selinux.security_get_initial_context_raw(*args) +security_get_initial_context_raw = _selinux.security_get_initial_context_raw + +def selinux_mkload_policy(*args): + return _selinux.selinux_mkload_policy(*args) +selinux_mkload_policy = _selinux.selinux_mkload_policy + +def selinux_init_load_policy(): + return _selinux.selinux_init_load_policy() +selinux_init_load_policy = _selinux.selinux_init_load_policy +class SELboolean(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, SELboolean, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, SELboolean, name) + __repr__ = _swig_repr + __swig_setmethods__["name"] = _selinux.SELboolean_name_set + __swig_getmethods__["name"] = _selinux.SELboolean_name_get + if _newclass:name = _swig_property(_selinux.SELboolean_name_get, _selinux.SELboolean_name_set) + __swig_setmethods__["value"] = _selinux.SELboolean_value_set + __swig_getmethods__["value"] = _selinux.SELboolean_value_get + if _newclass:value = _swig_property(_selinux.SELboolean_value_get, _selinux.SELboolean_value_set) + def __init__(self): + this = _selinux.new_SELboolean() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_SELboolean + __del__ = lambda self : None; +SELboolean_swigregister = _selinux.SELboolean_swigregister +SELboolean_swigregister(SELboolean) + + +def security_set_boolean_list(*args): + return _selinux.security_set_boolean_list(*args) +security_set_boolean_list = _selinux.security_set_boolean_list + +def security_load_booleans(*args): + return _selinux.security_load_booleans(*args) +security_load_booleans = _selinux.security_load_booleans + +def security_check_context(*args): + return _selinux.security_check_context(*args) +security_check_context = _selinux.security_check_context + +def security_check_context_raw(*args): + return _selinux.security_check_context_raw(*args) +security_check_context_raw = _selinux.security_check_context_raw + +def security_canonicalize_context(*args): + return _selinux.security_canonicalize_context(*args) +security_canonicalize_context = _selinux.security_canonicalize_context + +def security_canonicalize_context_raw(*args): + return _selinux.security_canonicalize_context_raw(*args) +security_canonicalize_context_raw = _selinux.security_canonicalize_context_raw + +def security_getenforce(): + return _selinux.security_getenforce() +security_getenforce = _selinux.security_getenforce + +def security_setenforce(*args): + return _selinux.security_setenforce(*args) +security_setenforce = _selinux.security_setenforce + +def security_deny_unknown(): + return _selinux.security_deny_unknown() +security_deny_unknown = _selinux.security_deny_unknown + +def security_disable(): + return _selinux.security_disable() +security_disable = _selinux.security_disable + +def security_policyvers(): + return _selinux.security_policyvers() +security_policyvers = _selinux.security_policyvers + +def security_get_boolean_names(): + return _selinux.security_get_boolean_names() +security_get_boolean_names = _selinux.security_get_boolean_names + +def security_get_boolean_pending(*args): + return _selinux.security_get_boolean_pending(*args) +security_get_boolean_pending = _selinux.security_get_boolean_pending + +def security_get_boolean_active(*args): + return _selinux.security_get_boolean_active(*args) +security_get_boolean_active = _selinux.security_get_boolean_active + +def security_set_boolean(*args): + return _selinux.security_set_boolean(*args) +security_set_boolean = _selinux.security_set_boolean + +def security_commit_booleans(): + return _selinux.security_commit_booleans() +security_commit_booleans = _selinux.security_commit_booleans +class security_class_mapping(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, security_class_mapping, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, security_class_mapping, name) + __repr__ = _swig_repr + __swig_setmethods__["name"] = _selinux.security_class_mapping_name_set + __swig_getmethods__["name"] = _selinux.security_class_mapping_name_get + if _newclass:name = _swig_property(_selinux.security_class_mapping_name_get, _selinux.security_class_mapping_name_set) + __swig_setmethods__["perms"] = _selinux.security_class_mapping_perms_set + __swig_getmethods__["perms"] = _selinux.security_class_mapping_perms_get + if _newclass:perms = _swig_property(_selinux.security_class_mapping_perms_get, _selinux.security_class_mapping_perms_set) + def __init__(self): + this = _selinux.new_security_class_mapping() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_security_class_mapping + __del__ = lambda self : None; +security_class_mapping_swigregister = _selinux.security_class_mapping_swigregister +security_class_mapping_swigregister(security_class_mapping) + + +def selinux_set_mapping(*args): + return _selinux.selinux_set_mapping(*args) +selinux_set_mapping = _selinux.selinux_set_mapping + +def mode_to_security_class(*args): + return _selinux.mode_to_security_class(*args) +mode_to_security_class = _selinux.mode_to_security_class + +def string_to_security_class(*args): + return _selinux.string_to_security_class(*args) +string_to_security_class = _selinux.string_to_security_class + +def security_class_to_string(*args): + return _selinux.security_class_to_string(*args) +security_class_to_string = _selinux.security_class_to_string + +def security_av_perm_to_string(*args): + return _selinux.security_av_perm_to_string(*args) +security_av_perm_to_string = _selinux.security_av_perm_to_string + +def string_to_av_perm(*args): + return _selinux.string_to_av_perm(*args) +string_to_av_perm = _selinux.string_to_av_perm + +def security_av_string(*args): + return _selinux.security_av_string(*args) +security_av_string = _selinux.security_av_string + +def print_access_vector(*args): + return _selinux.print_access_vector(*args) +print_access_vector = _selinux.print_access_vector +MATCHPATHCON_BASEONLY = _selinux.MATCHPATHCON_BASEONLY +MATCHPATHCON_NOTRANS = _selinux.MATCHPATHCON_NOTRANS +MATCHPATHCON_VALIDATE = _selinux.MATCHPATHCON_VALIDATE + +def set_matchpathcon_flags(*args): + return _selinux.set_matchpathcon_flags(*args) +set_matchpathcon_flags = _selinux.set_matchpathcon_flags + +def matchpathcon_init(*args): + return _selinux.matchpathcon_init(*args) +matchpathcon_init = _selinux.matchpathcon_init + +def matchpathcon_init_prefix(*args): + return _selinux.matchpathcon_init_prefix(*args) +matchpathcon_init_prefix = _selinux.matchpathcon_init_prefix + +def matchpathcon_fini(): + return _selinux.matchpathcon_fini() +matchpathcon_fini = _selinux.matchpathcon_fini + +def realpath_not_final(*args): + return _selinux.realpath_not_final(*args) +realpath_not_final = _selinux.realpath_not_final + +def matchpathcon(*args): + return _selinux.matchpathcon(*args) +matchpathcon = _selinux.matchpathcon + +def matchpathcon_index(*args): + return _selinux.matchpathcon_index(*args) +matchpathcon_index = _selinux.matchpathcon_index + +def matchpathcon_filespec_add(*args): + return _selinux.matchpathcon_filespec_add(*args) +matchpathcon_filespec_add = _selinux.matchpathcon_filespec_add + +def matchpathcon_filespec_destroy(): + return _selinux.matchpathcon_filespec_destroy() +matchpathcon_filespec_destroy = _selinux.matchpathcon_filespec_destroy + +def matchpathcon_filespec_eval(): + return _selinux.matchpathcon_filespec_eval() +matchpathcon_filespec_eval = _selinux.matchpathcon_filespec_eval + +def matchpathcon_checkmatches(*args): + return _selinux.matchpathcon_checkmatches(*args) +matchpathcon_checkmatches = _selinux.matchpathcon_checkmatches + +def matchmediacon(*args): + return _selinux.matchmediacon(*args) +matchmediacon = _selinux.matchmediacon + +def selinux_getenforcemode(): + return _selinux.selinux_getenforcemode() +selinux_getenforcemode = _selinux.selinux_getenforcemode + +def selinux_boolean_sub(*args): + return _selinux.selinux_boolean_sub(*args) +selinux_boolean_sub = _selinux.selinux_boolean_sub + +def selinux_getpolicytype(): + return _selinux.selinux_getpolicytype() +selinux_getpolicytype = _selinux.selinux_getpolicytype + +def selinux_policy_root(): + return _selinux.selinux_policy_root() +selinux_policy_root = _selinux.selinux_policy_root + +def selinux_set_policy_root(*args): + return _selinux.selinux_set_policy_root(*args) +selinux_set_policy_root = _selinux.selinux_set_policy_root + +def selinux_current_policy_path(): + return _selinux.selinux_current_policy_path() +selinux_current_policy_path = _selinux.selinux_current_policy_path + +def selinux_binary_policy_path(): + return _selinux.selinux_binary_policy_path() +selinux_binary_policy_path = _selinux.selinux_binary_policy_path + +def selinux_failsafe_context_path(): + return _selinux.selinux_failsafe_context_path() +selinux_failsafe_context_path = _selinux.selinux_failsafe_context_path + +def selinux_removable_context_path(): + return _selinux.selinux_removable_context_path() +selinux_removable_context_path = _selinux.selinux_removable_context_path + +def selinux_default_context_path(): + return _selinux.selinux_default_context_path() +selinux_default_context_path = _selinux.selinux_default_context_path + +def selinux_user_contexts_path(): + return _selinux.selinux_user_contexts_path() +selinux_user_contexts_path = _selinux.selinux_user_contexts_path + +def selinux_file_context_path(): + return _selinux.selinux_file_context_path() +selinux_file_context_path = _selinux.selinux_file_context_path + +def selinux_file_context_homedir_path(): + return _selinux.selinux_file_context_homedir_path() +selinux_file_context_homedir_path = _selinux.selinux_file_context_homedir_path + +def selinux_file_context_local_path(): + return _selinux.selinux_file_context_local_path() +selinux_file_context_local_path = _selinux.selinux_file_context_local_path + +def selinux_file_context_subs_path(): + return _selinux.selinux_file_context_subs_path() +selinux_file_context_subs_path = _selinux.selinux_file_context_subs_path + +def selinux_file_context_subs_dist_path(): + return _selinux.selinux_file_context_subs_dist_path() +selinux_file_context_subs_dist_path = _selinux.selinux_file_context_subs_dist_path + +def selinux_homedir_context_path(): + return _selinux.selinux_homedir_context_path() +selinux_homedir_context_path = _selinux.selinux_homedir_context_path + +def selinux_media_context_path(): + return _selinux.selinux_media_context_path() +selinux_media_context_path = _selinux.selinux_media_context_path + +def selinux_virtual_domain_context_path(): + return _selinux.selinux_virtual_domain_context_path() +selinux_virtual_domain_context_path = _selinux.selinux_virtual_domain_context_path + +def selinux_virtual_image_context_path(): + return _selinux.selinux_virtual_image_context_path() +selinux_virtual_image_context_path = _selinux.selinux_virtual_image_context_path + +def selinux_lxc_contexts_path(): + return _selinux.selinux_lxc_contexts_path() +selinux_lxc_contexts_path = _selinux.selinux_lxc_contexts_path + +def selinux_x_context_path(): + return _selinux.selinux_x_context_path() +selinux_x_context_path = _selinux.selinux_x_context_path + +def selinux_sepgsql_context_path(): + return _selinux.selinux_sepgsql_context_path() +selinux_sepgsql_context_path = _selinux.selinux_sepgsql_context_path + +def selinux_systemd_contexts_path(): + return _selinux.selinux_systemd_contexts_path() +selinux_systemd_contexts_path = _selinux.selinux_systemd_contexts_path + +def selinux_contexts_path(): + return _selinux.selinux_contexts_path() +selinux_contexts_path = _selinux.selinux_contexts_path + +def selinux_securetty_types_path(): + return _selinux.selinux_securetty_types_path() +selinux_securetty_types_path = _selinux.selinux_securetty_types_path + +def selinux_booleans_subs_path(): + return _selinux.selinux_booleans_subs_path() +selinux_booleans_subs_path = _selinux.selinux_booleans_subs_path + +def selinux_booleans_path(): + return _selinux.selinux_booleans_path() +selinux_booleans_path = _selinux.selinux_booleans_path + +def selinux_customizable_types_path(): + return _selinux.selinux_customizable_types_path() +selinux_customizable_types_path = _selinux.selinux_customizable_types_path + +def selinux_users_path(): + return _selinux.selinux_users_path() +selinux_users_path = _selinux.selinux_users_path + +def selinux_usersconf_path(): + return _selinux.selinux_usersconf_path() +selinux_usersconf_path = _selinux.selinux_usersconf_path + +def selinux_translations_path(): + return _selinux.selinux_translations_path() +selinux_translations_path = _selinux.selinux_translations_path + +def selinux_colors_path(): + return _selinux.selinux_colors_path() +selinux_colors_path = _selinux.selinux_colors_path + +def selinux_netfilter_context_path(): + return _selinux.selinux_netfilter_context_path() +selinux_netfilter_context_path = _selinux.selinux_netfilter_context_path + +def selinux_path(): + return _selinux.selinux_path() +selinux_path = _selinux.selinux_path + +def selinux_check_access(*args): + return _selinux.selinux_check_access(*args) +selinux_check_access = _selinux.selinux_check_access + +def selinux_check_passwd_access(*args): + return _selinux.selinux_check_passwd_access(*args) +selinux_check_passwd_access = _selinux.selinux_check_passwd_access + +def checkPasswdAccess(*args): + return _selinux.checkPasswdAccess(*args) +checkPasswdAccess = _selinux.checkPasswdAccess + +def selinux_check_securetty_context(*args): + return _selinux.selinux_check_securetty_context(*args) +selinux_check_securetty_context = _selinux.selinux_check_securetty_context + +def set_selinuxmnt(*args): + return _selinux.set_selinuxmnt(*args) +set_selinuxmnt = _selinux.set_selinuxmnt + +def selinuxfs_exists(): + return _selinux.selinuxfs_exists() +selinuxfs_exists = _selinux.selinuxfs_exists + +def fini_selinuxmnt(): + return _selinux.fini_selinuxmnt() +fini_selinuxmnt = _selinux.fini_selinuxmnt + +def setexecfilecon(*args): + return _selinux.setexecfilecon(*args) +setexecfilecon = _selinux.setexecfilecon + +def rpm_execcon(*args): + return _selinux.rpm_execcon(*args) +rpm_execcon = _selinux.rpm_execcon + +def is_context_customizable(*args): + return _selinux.is_context_customizable(*args) +is_context_customizable = _selinux.is_context_customizable + +def selinux_trans_to_raw_context(*args): + return _selinux.selinux_trans_to_raw_context(*args) +selinux_trans_to_raw_context = _selinux.selinux_trans_to_raw_context + +def selinux_raw_to_trans_context(*args): + return _selinux.selinux_raw_to_trans_context(*args) +selinux_raw_to_trans_context = _selinux.selinux_raw_to_trans_context + +def selinux_raw_context_to_color(*args): + return _selinux.selinux_raw_context_to_color(*args) +selinux_raw_context_to_color = _selinux.selinux_raw_context_to_color + +def getseuserbyname(*args): + return _selinux.getseuserbyname(*args) +getseuserbyname = _selinux.getseuserbyname + +def getseuser(*args): + return _selinux.getseuser(*args) +getseuser = _selinux.getseuser + +def selinux_file_context_cmp(*args): + return _selinux.selinux_file_context_cmp(*args) +selinux_file_context_cmp = _selinux.selinux_file_context_cmp + +def selinux_file_context_verify(*args): + return _selinux.selinux_file_context_verify(*args) +selinux_file_context_verify = _selinux.selinux_file_context_verify + +def selinux_lsetfilecon_default(*args): + return _selinux.selinux_lsetfilecon_default(*args) +selinux_lsetfilecon_default = _selinux.selinux_lsetfilecon_default + +def selinux_reset_config(): + return _selinux.selinux_reset_config() +selinux_reset_config = _selinux.selinux_reset_config +# This file is compatible with both classic and new-style classes. + + diff --git a/lib/python2.7/site-packages/selinux/_selinux.so b/lib/python2.7/site-packages/selinux/_selinux.so new file mode 100755 index 0000000000000000000000000000000000000000..f4a045f31dd8fa1c83b213d2134fa277fc220b64 GIT binary patch literal 333725 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 literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/sepolgen/__init__.py b/lib/python2.7/site-packages/sepolgen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/python2.7/site-packages/sepolgen/access.py b/lib/python2.7/site-packages/sepolgen/access.py new file mode 100644 index 0000000..cf13210 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/access.py @@ -0,0 +1,331 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes representing basic access. + +SELinux - at the most basic level - represents access as +the 4-tuple subject (type or context), target (type or context), +object class, permission. The policy language elaborates this basic +access to faciliate more concise rules (e.g., allow rules can have multiple +source or target types - see refpolicy for more information). + +This module has objects for representing the most basic access (AccessVector) +and sets of that access (AccessVectorSet). These objects are used in Madison +in a variety of ways, but they are the fundamental representation of access. +""" + +import refpolicy +from selinux import audit2why + +def is_idparam(id): + """Determine if an id is a paramater in the form $N, where N is + an integer. + + Returns: + True if the id is a paramater + False if the id is not a paramater + """ + if len(id) > 1 and id[0] == '$': + try: + int(id[1:]) + except ValueError: + return False + return True + else: + return False + +class AccessVector: + """ + An access vector is the basic unit of access in SELinux. + + Access vectors are the most basic representation of access within + SELinux. It represents the access a source type has to a target + type in terms of an object class and a set of permissions. + + Access vectors are distinct from AVRules in that they can only + store a single source type, target type, and object class. The + simplicity of AccessVectors makes them useful for storing access + in a form that is easy to search and compare. + + The source, target, and object are stored as string. No checking + done to verify that the strings are valid SELinux identifiers. + Identifiers in the form $N (where N is an integer) are reserved as + interface parameters and are treated as wild cards in many + circumstances. + + Properties: + .src_type - The source type allowed access. [String or None] + .tgt_type - The target type to which access is allowed. [String or None] + .obj_class - The object class to which access is allowed. [String or None] + .perms - The permissions allowed to the object class. [IdSet] + .audit_msgs - The audit messages that generated this access vector [List of strings] + """ + def __init__(self, init_list=None): + if init_list: + self.from_list(init_list) + else: + self.src_type = None + self.tgt_type = None + self.obj_class = None + self.perms = refpolicy.IdSet() + self.audit_msgs = [] + self.type = audit2why.TERULE + self.data = [] + + # The direction of the information flow represented by this + # access vector - used for matching + self.info_flow_dir = None + + def from_list(self, list): + """Initialize an access vector from a list. + + Initialize an access vector from a list treating the list as + positional arguments - i.e., 0 = src_type, 1 = tgt_type, etc. + All of the list elements 3 and greater are treated as perms. + For example, the list ['foo_t', 'bar_t', 'file', 'read', 'write'] + would create an access vector list with the source type 'foo_t', + target type 'bar_t', object class 'file', and permissions 'read' + and 'write'. + + This format is useful for very simple storage to strings or disc + (see to_list) and for initializing access vectors. + """ + if len(list) < 4: + raise ValueError("List must contain at least four elements %s" % str(list)) + self.src_type = list[0] + self.tgt_type = list[1] + self.obj_class = list[2] + self.perms = refpolicy.IdSet(list[3:]) + + def to_list(self): + """ + Convert an access vector to a list. + + Convert an access vector to a list treating the list as positional + values. See from_list for more information on how an access vector + is represented in a list. + """ + l = [self.src_type, self.tgt_type, self.obj_class] + l.extend(self.perms) + return l + + def __str__(self): + return self.to_string() + + def to_string(self): + return "allow %s %s:%s %s;" % (self.src_type, self.tgt_type, + self.obj_class, self.perms.to_space_str()) + + def __cmp__(self, other): + if self.src_type != other.src_type: + return cmp(self.src_type, other.src_type) + if self.tgt_type != other.tgt_type: + return cmp(self.tgt_type, other.tgt_type) + if self.obj_class != self.obj_class: + return cmp(self.obj_class, other.obj_class) + if len(self.perms) != len(other.perms): + return cmp(len(self.perms), len(other.perms)) + x = list(self.perms) + x.sort() + y = list(other.perms) + y.sort() + for pa, pb in zip(x, y): + if pa != pb: + return cmp(pa, pb) + return 0 + +def avrule_to_access_vectors(avrule): + """Convert an avrule into a list of access vectors. + + AccessVectors and AVRules are similary, but differ in that + an AVRule can more than one source type, target type, and + object class. This function expands a single avrule into a + list of one or more AccessVectors representing the access + defined in the AVRule. + + + """ + if isinstance(avrule, AccessVector): + return [avrule] + a = [] + for src_type in avrule.src_types: + for tgt_type in avrule.tgt_types: + for obj_class in avrule.obj_classes: + access = AccessVector() + access.src_type = src_type + access.tgt_type = tgt_type + access.obj_class = obj_class + access.perms = avrule.perms.copy() + a.append(access) + return a + +class AccessVectorSet: + """A non-overlapping set of access vectors. + + An AccessVectorSet is designed to store one or more access vectors + that are non-overlapping. Access can be added to the set + incrementally and access vectors will be added or merged as + necessary. For example, adding the following access vectors using + add_av: + allow $1 etc_t : read; + allow $1 etc_t : write; + allow $1 var_log_t : read; + Would result in an access vector set with the access vectors: + allow $1 etc_t : { read write}; + allow $1 var_log_t : read; + """ + def __init__(self): + """Initialize an access vector set. + """ + self.src = {} + # The information flow direction of this access vector + # set - see objectmodel.py for more information. This + # stored here to speed up searching - see matching.py. + self.info_dir = None + + def __iter__(self): + """Iterate over all of the unique access vectors in the set.""" + for tgts in self.src.values(): + for objs in tgts.values(): + for av in objs.values(): + yield av + + def __len__(self): + """Return the number of unique access vectors in the set. + + Because of the inernal representation of the access vector set, + __len__ is not a constant time operation. Worst case is O(N) + where N is the number of unique access vectors, but the common + case is probably better. + """ + l = 0 + for tgts in self.src.values(): + for objs in tgts.values(): + l += len(objs) + return l + + def to_list(self): + """Return the unique access vectors in the set as a list. + + The format of the returned list is a set of nested lists, + each access vector represented by a list. This format is + designed to be simply serializable to a file. + + For example, consider an access vector set with the following + access vectors: + allow $1 user_t : file read; + allow $1 etc_t : file { read write}; + to_list would return the following: + [[$1, user_t, file, read] + [$1, etc_t, file, read, write]] + + See AccessVector.to_list for more information. + """ + l = [] + for av in self: + l.append(av.to_list()) + + return l + + def from_list(self, l): + """Add access vectors stored in a list. + + See to list for more information on the list format that this + method accepts. + + This will add all of the access from the list. Any existing + access vectors in the set will be retained. + """ + for av in l: + self.add_av(AccessVector(av)) + + def add(self, src_type, tgt_type, obj_class, perms, audit_msg=None, avc_type=audit2why.TERULE, data=[]): + """Add an access vector to the set. + """ + tgt = self.src.setdefault(src_type, { }) + cls = tgt.setdefault(tgt_type, { }) + + if cls.has_key((obj_class, avc_type)): + access = cls[obj_class, avc_type] + else: + access = AccessVector() + access.src_type = src_type + access.tgt_type = tgt_type + access.obj_class = obj_class + access.data = data + access.type = avc_type + cls[obj_class, avc_type] = access + + access.perms.update(perms) + if audit_msg: + access.audit_msgs.append(audit_msg) + + def add_av(self, av, audit_msg=None): + """Add an access vector to the set.""" + self.add(av.src_type, av.tgt_type, av.obj_class, av.perms) + + +def avs_extract_types(avs): + types = refpolicy.IdSet() + for av in avs: + types.add(av.src_type) + types.add(av.tgt_type) + + return types + +def avs_extract_obj_perms(avs): + perms = { } + for av in avs: + if perms.has_key(av.obj_class): + s = perms[av.obj_class] + else: + s = refpolicy.IdSet() + perms[av.obj_class] = s + s.update(av.perms) + return perms + +class RoleTypeSet: + """A non-overlapping set of role type statements. + + This clas allows the incremental addition of role type statements and + maintains a non-overlapping list of statements. + """ + def __init__(self): + """Initialize an access vector set.""" + self.role_types = {} + + def __iter__(self): + """Iterate over all of the unique role allows statements in the set.""" + for role_type in self.role_types.values(): + yield role_type + + def __len__(self): + """Return the unique number of role allow statements.""" + return len(self.role_types.keys()) + + def add(self, role, type): + if self.role_types.has_key(role): + role_type = self.role_types[role] + else: + role_type = refpolicy.RoleType() + role_type.role = role + self.role_types[role] = role_type + + role_type.types.add(type) diff --git a/lib/python2.7/site-packages/sepolgen/audit.py b/lib/python2.7/site-packages/sepolgen/audit.py new file mode 100644 index 0000000..56919be --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/audit.py @@ -0,0 +1,549 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +import refpolicy +import access +import re +import sys + +# Convenience functions + +def get_audit_boot_msgs(): + """Obtain all of the avc and policy load messages from the audit + log. This function uses ausearch and requires that the current + process have sufficient rights to run ausearch. + + Returns: + string contain all of the audit messages returned by ausearch. + """ + import subprocess + import time + fd=open("/proc/uptime", "r") + off=float(fd.read().split()[0]) + fd.close + s = time.localtime(time.time() - off) + bootdate = time.strftime("%x", s) + boottime = time.strftime("%X", s) + output = subprocess.Popen(["/sbin/ausearch", "-m", "AVC,USER_AVC,MAC_POLICY_LOAD,DAEMON_START,SELINUX_ERR", "-ts", bootdate, boottime], + stdout=subprocess.PIPE).communicate()[0] + return output + +def get_audit_msgs(): + """Obtain all of the avc and policy load messages from the audit + log. This function uses ausearch and requires that the current + process have sufficient rights to run ausearch. + + Returns: + string contain all of the audit messages returned by ausearch. + """ + import subprocess + output = subprocess.Popen(["/sbin/ausearch", "-m", "AVC,USER_AVC,MAC_POLICY_LOAD,DAEMON_START,SELINUX_ERR"], + stdout=subprocess.PIPE).communicate()[0] + return output + +def get_dmesg_msgs(): + """Obtain all of the avc and policy load messages from /bin/dmesg. + + Returns: + string contain all of the audit messages returned by dmesg. + """ + import subprocess + output = subprocess.Popen(["/bin/dmesg"], + stdout=subprocess.PIPE).communicate()[0] + return output + +# Classes representing audit messages + +class AuditMessage: + """Base class for all objects representing audit messages. + + AuditMessage is a base class for all audit messages and only + provides storage for the raw message (as a string) and a + parsing function that does nothing. + """ + def __init__(self, message): + self.message = message + self.header = "" + + def from_split_string(self, recs): + """Parse a string that has been split into records by space into + an audit message. + + This method should be overridden by subclasses. Error reporting + should be done by raise ValueError exceptions. + """ + for msg in recs: + fields = msg.split("=") + if len(fields) != 2: + if msg[:6] == "audit(": + self.header = msg + return + else: + continue + + if fields[0] == "msg": + self.header = fields[1] + return + + +class InvalidMessage(AuditMessage): + """Class representing invalid audit messages. This is used to differentiate + between audit messages that aren't recognized (that should return None from + the audit message parser) and a message that is recognized but is malformed + in some way. + """ + def __init__(self, message): + AuditMessage.__init__(self, message) + +class PathMessage(AuditMessage): + """Class representing a path message""" + def __init__(self, message): + AuditMessage.__init__(self, message) + self.path = "" + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + + for msg in recs: + fields = msg.split("=") + if len(fields) != 2: + continue + if fields[0] == "path": + self.path = fields[1][1:-1] + return +import selinux.audit2why as audit2why + +avcdict = {} + +class AVCMessage(AuditMessage): + """AVC message representing an access denial or granted message. + + This is a very basic class and does not represent all possible fields + in an avc message. Currently the fields are: + scontext - context for the source (process) that generated the message + tcontext - context for the target + tclass - object class for the target (only one) + comm - the process name + exe - the on-disc binary + path - the path of the target + access - list of accesses that were allowed or denied + denial - boolean indicating whether this was a denial (True) or granted + (False) message. + + An example audit message generated from the audit daemon looks like (line breaks + added): + 'type=AVC msg=audit(1155568085.407:10877): avc: denied { search } for + pid=677 comm="python" name="modules" dev=dm-0 ino=13716388 + scontext=user_u:system_r:setroubleshootd_t:s0 + tcontext=system_u:object_r:modules_object_t:s0 tclass=dir' + + An example audit message stored in syslog (not processed by the audit daemon - line + breaks added): + 'Sep 12 08:26:43 dhcp83-5 kernel: audit(1158064002.046:4): avc: denied { read } + for pid=2 496 comm="bluez-pin" name=".gdm1K3IFT" dev=dm-0 ino=3601333 + scontext=user_u:system_r:bluetooth_helper_t:s0-s0:c0 + tcontext=system_u:object_r:xdm_tmp_t:s0 tclass=file + """ + def __init__(self, message): + AuditMessage.__init__(self, message) + self.scontext = refpolicy.SecurityContext() + self.tcontext = refpolicy.SecurityContext() + self.tclass = "" + self.comm = "" + self.exe = "" + self.path = "" + self.name = "" + self.accesses = [] + self.denial = True + self.type = audit2why.TERULE + + def __parse_access(self, recs, start): + # This is kind of sucky - the access that is in a space separated + # list like '{ read write }'. This doesn't fit particularly well with splitting + # the string on spaces. This function takes the list of recs and a starting + # position one beyond the open brace. It then adds the accesses until it finds + # the close brace or the end of the list (which is an error if reached without + # seeing a close brace). + found_close = False + i = start + if i == (len(recs) - 1): + raise ValueError("AVC message in invalid format [%s]\n" % self.message) + while i < len(recs): + if recs[i] == "}": + found_close = True + break + self.accesses.append(recs[i]) + i = i + 1 + if not found_close: + raise ValueError("AVC message in invalid format [%s]\n" % self.message) + return i + 1 + + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + # FUTURE - fully parse avc messages and store all possible fields + # Required fields + found_src = False + found_tgt = False + found_class = False + found_access = False + + for i in range(len(recs)): + if recs[i] == "{": + i = self.__parse_access(recs, i + 1) + found_access = True + continue + elif recs[i] == "granted": + self.denial = False + + fields = recs[i].split("=") + if len(fields) != 2: + continue + if fields[0] == "scontext": + self.scontext = refpolicy.SecurityContext(fields[1]) + found_src = True + elif fields[0] == "tcontext": + self.tcontext = refpolicy.SecurityContext(fields[1]) + found_tgt = True + elif fields[0] == "tclass": + self.tclass = fields[1] + found_class = True + elif fields[0] == "comm": + self.comm = fields[1][1:-1] + elif fields[0] == "exe": + self.exe = fields[1][1:-1] + elif fields[0] == "name": + self.name = fields[1][1:-1] + + if not found_src or not found_tgt or not found_class or not found_access: + raise ValueError("AVC message in invalid format [%s]\n" % self.message) + self.analyze() + + def analyze(self): + tcontext = self.tcontext.to_string() + scontext = self.scontext.to_string() + access_tuple = tuple( self.accesses) + self.data = [] + + if (scontext, tcontext, self.tclass, access_tuple) in avcdict.keys(): + self.type, self.data = avcdict[(scontext, tcontext, self.tclass, access_tuple)] + else: + self.type, self.data = audit2why.analyze(scontext, tcontext, self.tclass, self.accesses); + if self.type == audit2why.NOPOLICY: + self.type = audit2why.TERULE + if self.type == audit2why.BADTCON: + raise ValueError("Invalid Target Context %s\n" % tcontext) + if self.type == audit2why.BADSCON: + raise ValueError("Invalid Source Context %s\n" % scontext) + if self.type == audit2why.BADSCON: + raise ValueError("Invalid Type Class %s\n" % self.tclass) + if self.type == audit2why.BADPERM: + raise ValueError("Invalid permission %s\n" % " ".join(self.accesses)) + if self.type == audit2why.BADCOMPUTE: + raise ValueError("Error during access vector computation") + + if self.type == audit2why.CONSTRAINT: + self.data = [ self.data ] + if self.scontext.user != self.tcontext.user: + self.data.append(("user (%s)" % self.scontext.user, 'user (%s)' % self.tcontext.user)) + if self.scontext.role != self.tcontext.role and self.tcontext.role != "object_r": + self.data.append(("role (%s)" % self.scontext.role, 'role (%s)' % self.tcontext.role)) + if self.scontext.level != self.tcontext.level: + self.data.append(("level (%s)" % self.scontext.level, 'level (%s)' % self.tcontext.level)) + + avcdict[(scontext, tcontext, self.tclass, access_tuple)] = (self.type, self.data) + +class PolicyLoadMessage(AuditMessage): + """Audit message indicating that the policy was reloaded.""" + def __init__(self, message): + AuditMessage.__init__(self, message) + +class DaemonStartMessage(AuditMessage): + """Audit message indicating that a daemon was started.""" + def __init__(self, message): + AuditMessage.__init__(self, message) + self.auditd = False + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + if "auditd" in recs: + self.auditd = True + + +class ComputeSidMessage(AuditMessage): + """Audit message indicating that a sid was not valid. + + Compute sid messages are generated on attempting to create a security + context that is not valid. Security contexts are invalid if the role is + not authorized for the user or the type is not authorized for the role. + + This class does not store all of the fields from the compute sid message - + just the type and role. + """ + def __init__(self, message): + AuditMessage.__init__(self, message) + self.invalid_context = refpolicy.SecurityContext() + self.scontext = refpolicy.SecurityContext() + self.tcontext = refpolicy.SecurityContext() + self.tclass = "" + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + if len(recs) < 10: + raise ValueError("Split string does not represent a valid compute sid message") + + try: + self.invalid_context = refpolicy.SecurityContext(recs[5]) + self.scontext = refpolicy.SecurityContext(recs[7].split("=")[1]) + self.tcontext = refpolicy.SecurityContext(recs[8].split("=")[1]) + self.tclass = recs[9].split("=")[1] + except: + raise ValueError("Split string does not represent a valid compute sid message") + def output(self): + return "role %s types %s;\n" % (self.role, self.type) + +# Parser for audit messages + +class AuditParser: + """Parser for audit messages. + + This class parses audit messages and stores them according to their message + type. This is not a general purpose audit message parser - it only extracts + selinux related messages. + + Each audit messages are stored in one of four lists: + avc_msgs - avc denial or granted messages. Messages are stored in + AVCMessage objects. + comput_sid_messages - invalid sid messages. Messages are stored in + ComputSidMessage objects. + invalid_msgs - selinux related messages that are not valid. Messages + are stored in InvalidMessageObjects. + policy_load_messages - policy load messages. Messages are stored in + PolicyLoadMessage objects. + + These lists will be reset when a policy load message is seen if + AuditParser.last_load_only is set to true. It is assumed that messages + are fed to the parser in chronological order - time stamps are not + parsed. + """ + def __init__(self, last_load_only=False): + self.__initialize() + self.last_load_only = last_load_only + + def __initialize(self): + self.avc_msgs = [] + self.compute_sid_msgs = [] + self.invalid_msgs = [] + self.policy_load_msgs = [] + self.path_msgs = [] + self.by_header = { } + self.check_input_file = False + + # Low-level parsing function - tries to determine if this audit + # message is an SELinux related message and then parses it into + # the appropriate AuditMessage subclass. This function deliberately + # does not impose policy (e.g., on policy load message) or store + # messages to make as simple and reusable as possible. + # + # Return values: + # None - no recognized audit message found in this line + # + # InvalidMessage - a recognized but invalid message was found. + # + # AuditMessage (or subclass) - object representing a parsed + # and valid audit message. + def __parse_line(self, line): + rec = line.split() + for i in rec: + found = False + if i == "avc:" or i == "message=avc:" or i == "msg='avc:": + msg = AVCMessage(line) + found = True + elif i == "security_compute_sid:": + msg = ComputeSidMessage(line) + found = True + elif i == "type=MAC_POLICY_LOAD" or i == "type=1403": + msg = PolicyLoadMessage(line) + found = True + elif i == "type=AVC_PATH": + msg = PathMessage(line) + found = True + elif i == "type=DAEMON_START": + msg = DaemonStartMessage(list) + found = True + + if found: + self.check_input_file = True + try: + msg.from_split_string(rec) + except ValueError: + msg = InvalidMessage(line) + return msg + return None + + # Higher-level parse function - take a line, parse it into an + # AuditMessage object, and store it in the appropriate list. + # This function will optionally reset all of the lists when + # it sees a load policy message depending on the value of + # self.last_load_only. + def __parse(self, line): + msg = self.__parse_line(line) + if msg is None: + return + + # Append to the correct list + if isinstance(msg, PolicyLoadMessage): + if self.last_load_only: + self.__initialize() + elif isinstance(msg, DaemonStartMessage): + # We initialize every time the auditd is started. This + # is less than ideal, but unfortunately it is the only + # way to catch reboots since the initial policy load + # by init is not stored in the audit log. + if msg.auditd and self.last_load_only: + self.__initialize() + self.policy_load_msgs.append(msg) + elif isinstance(msg, AVCMessage): + self.avc_msgs.append(msg) + elif isinstance(msg, ComputeSidMessage): + self.compute_sid_msgs.append(msg) + elif isinstance(msg, InvalidMessage): + self.invalid_msgs.append(msg) + elif isinstance(msg, PathMessage): + self.path_msgs.append(msg) + + # Group by audit header + if msg.header != "": + if self.by_header.has_key(msg.header): + self.by_header[msg.header].append(msg) + else: + self.by_header[msg.header] = [msg] + + + # Post processing will add additional information from AVC messages + # from related messages - only works on messages generated by + # the audit system. + def __post_process(self): + for value in self.by_header.values(): + avc = [] + path = None + for msg in value: + if isinstance(msg, PathMessage): + path = msg + elif isinstance(msg, AVCMessage): + avc.append(msg) + if len(avc) > 0 and path: + for a in avc: + a.path = path.path + + def parse_file(self, input): + """Parse the contents of a file object. This method can be called + multiple times (along with parse_string).""" + line = input.readline() + while line: + self.__parse(line) + line = input.readline() + if not self.check_input_file: + sys.stderr.write("Nothing to do\n") + sys.exit(0) + self.__post_process() + + def parse_string(self, input): + """Parse a string containing audit messages - messages should + be separated by new lines. This method can be called multiple + times (along with parse_file).""" + lines = input.split('\n') + for l in lines: + self.__parse(l) + self.__post_process() + + def to_role(self, role_filter=None): + """Return RoleAllowSet statements matching the specified filter + + Filter out types that match the filer, or all roles + + Params: + role_filter - [optional] Filter object used to filter the + output. + Returns: + Access vector set representing the denied access in the + audit logs parsed by this object. + """ + role_types = access.RoleTypeSet() + for cs in self.compute_sid_msgs: + if not role_filter or role_filter.filter(cs): + role_types.add(cs.invalid_context.role, cs.invalid_context.type) + + return role_types + + def to_access(self, avc_filter=None, only_denials=True): + """Convert the audit logs access into a an access vector set. + + Convert the audit logs into an access vector set, optionally + filtering the restults with the passed in filter object. + + Filter objects are object instances with a .filter method + that takes and access vector and returns True if the message + should be included in the final output and False otherwise. + + Params: + avc_filter - [optional] Filter object used to filter the + output. + Returns: + Access vector set representing the denied access in the + audit logs parsed by this object. + """ + av_set = access.AccessVectorSet() + for avc in self.avc_msgs: + if avc.denial != True and only_denials: + continue + if avc_filter: + if avc_filter.filter(avc): + av_set.add(avc.scontext.type, avc.tcontext.type, avc.tclass, + avc.accesses, avc, avc_type=avc.type, data=avc.data) + else: + av_set.add(avc.scontext.type, avc.tcontext.type, avc.tclass, + avc.accesses, avc, avc_type=avc.type, data=avc.data) + return av_set + +class AVCTypeFilter: + def __init__(self, regex): + self.regex = re.compile(regex) + + def filter(self, avc): + if self.regex.match(avc.scontext.type): + return True + if self.regex.match(avc.tcontext.type): + return True + return False + +class ComputeSidTypeFilter: + def __init__(self, regex): + self.regex = re.compile(regex) + + def filter(self, avc): + if self.regex.match(avc.invalid_context.type): + return True + if self.regex.match(avc.scontext.type): + return True + if self.regex.match(avc.tcontext.type): + return True + return False + + diff --git a/lib/python2.7/site-packages/sepolgen/classperms.py b/lib/python2.7/site-packages/sepolgen/classperms.py new file mode 100644 index 0000000..c925dee --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/classperms.py @@ -0,0 +1,116 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# +import sys + +tokens = ('DEFINE', + 'NAME', + 'TICK', + 'SQUOTE', + 'OBRACE', + 'CBRACE', + 'SEMI', + 'OPAREN', + 'CPAREN', + 'COMMA') + +reserved = { + 'define' : 'DEFINE' } + +t_TICK = r'\`' +t_SQUOTE = r'\'' +t_OBRACE = r'\{' +t_CBRACE = r'\}' +t_SEMI = r'\;' +t_OPAREN = r'\(' +t_CPAREN = r'\)' +t_COMMA = r'\,' + +t_ignore = " \t\n" + +def t_NAME(t): + r'[a-zA-Z_][a-zA-Z0-9_]*' + t.type = reserved.get(t.value,'NAME') + return t + +def t_error(t): + print "Illegal character '%s'" % t.value[0] + t.skip(1) + +import lex +lex.lex() + +def p_statements(p): + '''statements : define_stmt + | define_stmt statements + ''' + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = [p[1]] + [p[2]] + +def p_define_stmt(p): + # This sucks - corresponds to 'define(`foo',`{ read write }') + '''define_stmt : DEFINE OPAREN TICK NAME SQUOTE COMMA TICK list SQUOTE CPAREN + ''' + + p[0] = [p[4], p[8]] + +def p_list(p): + '''list : NAME + | OBRACE names CBRACE + ''' + if p[1] == "{": + p[0] = p[2] + else: + p[0] = [p[1]] + +def p_names(p): + '''names : NAME + | NAME names + ''' + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = [p[1]] + p[2] + +def p_error(p): + print "Syntax error on line %d %s [type=%s]" % (p.lineno, p.value, p.type) + +import yacc +yacc.yacc() + + +f = open("all_perms.spt") +txt = f.read() +f.close() + +#lex.input(txt) +#while 1: +# tok = lex.token() +# if not tok: +# break +# print tok + +test = "define(`foo',`{ read write append }')" +test2 = """define(`all_filesystem_perms',`{ mount remount unmount getattr relabelfrom relabelto transition associate quotamod quotaget }') +define(`all_security_perms',`{ compute_av compute_create compute_member check_context load_policy compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot }') +""" +result = yacc.parse(txt) +print result + diff --git a/lib/python2.7/site-packages/sepolgen/defaults.py b/lib/python2.7/site-packages/sepolgen/defaults.py new file mode 100644 index 0000000..218bc7c --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/defaults.py @@ -0,0 +1,77 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +import os +import re + +# Select the correct location for the development files based on a +# path variable (optionally read from a configuration file) +class PathChoooser(object): + def __init__(self, pathname): + self.config = dict() + if not os.path.exists(pathname): + self.config_pathname = "(defaults)" + self.config["SELINUX_DEVEL_PATH"] = "/usr/share/selinux/default:/usr/share/selinux/mls:/usr/share/selinux/devel" + return + self.config_pathname = pathname + ignore = re.compile(r"^\s*(?:#.+)?$") + consider = re.compile(r"^\s*(\w+)\s*=\s*(.+?)\s*$") + for lineno, line in enumerate(open(pathname)): + if ignore.match(line): continue + mo = consider.match(line) + if not mo: + raise ValueError, "%s:%d: line is not in key = value format" % (pathname, lineno+1) + self.config[mo.group(1)] = mo.group(2) + + # We're only exporting one useful function, so why not be a function + def __call__(self, testfilename, pathset="SELINUX_DEVEL_PATH"): + paths = self.config.get(pathset, None) + if paths is None: + raise ValueError, "%s was not in %s" % (pathset, self.config_pathname) + paths = paths.split(":") + for p in paths: + target = os.path.join(p, testfilename) + if os.path.exists(target): return target + return os.path.join(paths[0], testfilename) + + +""" +Various default settings, including file and directory locations. +""" + +def data_dir(): + return "/var/lib/sepolgen" + +def perm_map(): + return data_dir() + "/perm_map" + +def interface_info(): + return data_dir() + "/interface_info" + +def attribute_info(): + return data_dir() + "/attribute_info" + +def refpolicy_makefile(): + chooser = PathChoooser("/etc/selinux/sepolgen.conf") + return chooser("Makefile") + +def headers(): + chooser = PathChoooser("/etc/selinux/sepolgen.conf") + return chooser("include") + diff --git a/lib/python2.7/site-packages/sepolgen/interfaces.py b/lib/python2.7/site-packages/sepolgen/interfaces.py new file mode 100644 index 0000000..88a6dc3 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/interfaces.py @@ -0,0 +1,509 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes for representing and manipulating interfaces. +""" + +import access +import refpolicy +import itertools +import objectmodel +import matching + +from sepolgeni18n import _ + +import copy + +class Param: + """ + Object representing a paramater for an interface. + """ + def __init__(self): + self.__name = "" + self.type = refpolicy.SRC_TYPE + self.obj_classes = refpolicy.IdSet() + self.required = True + + def set_name(self, name): + if not access.is_idparam(name): + raise ValueError("Name [%s] is not a param" % name) + self.__name = name + + def get_name(self): + return self.__name + + name = property(get_name, set_name) + + num = property(fget=lambda self: int(self.name[1:])) + + def __repr__(self): + return "" % \ + (self.name, refpolicy.field_to_str[self.type], " ".join(self.obj_classes)) + + +# Helper for extract perms +def __param_insert(name, type, av, params): + ret = 0 + if name in params: + p = params[name] + # The entries are identical - we're done + if type == p.type: + return + # Hanldle implicitly typed objects (like process) + if (type == refpolicy.SRC_TYPE or type == refpolicy.TGT_TYPE) and \ + (p.type == refpolicy.TGT_TYPE or p.type == refpolicy.SRC_TYPE): + #print name, refpolicy.field_to_str[p.type] + # If the object is not implicitly typed, tell the + # caller there is a likely conflict. + ret = 1 + if av: + avobjs = [av.obj_class] + else: + avobjs = [] + for obj in itertools.chain(p.obj_classes, avobjs): + if obj in objectmodel.implicitly_typed_objects: + ret = 0 + break + # "Promote" to a SRC_TYPE as this is the likely usage. + # We do this even if the above test fails on purpose + # as there is really no sane way to resolve the conflict + # here. The caller can take other actions if needed. + p.type = refpolicy.SRC_TYPE + else: + # There is some conflict - no way to resolve it really + # so we just leave the first entry and tell the caller + # there was a conflict. + ret = 1 + else: + p = Param() + p.name = name + p.type = type + params[p.name] = p + + if av: + p.obj_classes.add(av.obj_class) + return ret + + + +def av_extract_params(av, params): + """Extract the paramaters from an access vector. + + Extract the paramaters (in the form $N) from an access + vector, storing them as Param objects in a dictionary. + Some attempt is made at resolving conflicts with other + entries in the dict, but if an unresolvable conflict is + found it is reported to the caller. + + The goal here is to figure out how interface paramaters are + actually used in the interface - e.g., that $1 is a domain used as + a SRC_TYPE. In general an interface will look like this: + + interface(`foo', ` + allow $1 foo : file read; + ') + + This is simple to figure out - $1 is a SRC_TYPE. A few interfaces + are more complex, for example: + + interface(`foo_trans',` + domain_auto_trans($1,fingerd_exec_t,fingerd_t) + + allow $1 fingerd_t:fd use; + allow fingerd_t $1:fd use; + allow fingerd_t $1:fifo_file rw_file_perms; + allow fingerd_t $1:process sigchld; + ') + + Here the usage seems ambigious, but it is not. $1 is still domain + and therefore should be returned as a SRC_TYPE. + + Returns: + 0 - success + 1 - conflict found + """ + ret = 0 + found_src = False + if access.is_idparam(av.src_type): + if __param_insert(av.src_type, refpolicy.SRC_TYPE, av, params) == 1: + ret = 1 + + if access.is_idparam(av.tgt_type): + if __param_insert(av.tgt_type, refpolicy.TGT_TYPE, av, params) == 1: + ret = 1 + + if access.is_idparam(av.obj_class): + if __param_insert(av.obj_class, refpolicy.OBJ_CLASS, av, params) == 1: + ret = 1 + + for perm in av.perms: + if access.is_idparam(perm): + if __param_insert(perm, PERM) == 1: + ret = 1 + + return ret + +def role_extract_params(role, params): + if access.is_idparam(role.role): + return __param_insert(role.role, refpolicy.ROLE, None, params) + +def type_rule_extract_params(rule, params): + def extract_from_set(set, type): + ret = 0 + for x in set: + if access.is_idparam(x): + if __param_insert(x, type, None, params): + ret = 1 + return ret + + ret = 0 + if extract_from_set(rule.src_types, refpolicy.SRC_TYPE): + ret = 1 + + if extract_from_set(rule.tgt_types, refpolicy.TGT_TYPE): + ret = 1 + + if extract_from_set(rule.obj_classes, refpolicy.OBJ_CLASS): + ret = 1 + + if access.is_idparam(rule.dest_type): + if __param_insert(rule.dest_type, refpolicy.DEST_TYPE, None, params): + ret = 1 + + return ret + +def ifcall_extract_params(ifcall, params): + ret = 0 + for arg in ifcall.args: + if access.is_idparam(arg): + # Assume interface arguments are source types. Fairly safe + # assumption for most interfaces + if __param_insert(arg, refpolicy.SRC_TYPE, None, params): + ret = 1 + + return ret + +class AttributeVector: + def __init__(self): + self.name = "" + self.access = access.AccessVectorSet() + + def add_av(self, av): + self.access.add_av(av) + +class AttributeSet: + def __init__(self): + self.attributes = { } + + def add_attr(self, attr): + self.attributes[attr.name] = attr + + def from_file(self, fd): + def parse_attr(line): + fields = line[1:-1].split() + if len(fields) != 2 or fields[0] != "Attribute": + raise SyntaxError("Syntax error Attribute statement %s" % line) + a = AttributeVector() + a.name = fields[1] + + return a + + a = None + for line in fd: + line = line[:-1] + if line[0] == "[": + if a: + self.add_attr(a) + a = parse_attr(line) + elif a: + l = line.split(",") + av = access.AccessVector(l) + a.add_av(av) + if a: + self.add_attr(a) + +class InterfaceVector: + def __init__(self, interface=None, attributes={}): + # Enabled is a loose concept currently - we are essentially + # not enabling interfaces that we can't handle currently. + # See InterfaceVector.add_ifv for more information. + self.enabled = True + self.name = "" + # The access that is enabled by this interface - eventually + # this will include indirect access from typeattribute + # statements. + self.access = access.AccessVectorSet() + # Paramaters are stored in a dictionary (key: param name + # value: Param object). + self.params = { } + if interface: + self.from_interface(interface, attributes) + self.expanded = False + + def from_interface(self, interface, attributes={}): + self.name = interface.name + + # Add allow rules + for avrule in interface.avrules(): + if avrule.rule_type != refpolicy.AVRule.ALLOW: + continue + # Handle some policy bugs + if "dontaudit" in interface.name: + #print "allow rule in interface: %s" % interface + continue + avs = access.avrule_to_access_vectors(avrule) + for av in avs: + self.add_av(av) + + # Add typeattribute access + if attributes: + for typeattribute in interface.typeattributes(): + for attr in typeattribute.attributes: + if not attributes.attributes.has_key(attr): + # print "missing attribute " + attr + continue + attr_vec = attributes.attributes[attr] + for a in attr_vec.access: + av = copy.copy(a) + if av.src_type == attr_vec.name: + av.src_type = typeattribute.type + if av.tgt_type == attr_vec.name: + av.tgt_type = typeattribute.type + self.add_av(av) + + + # Extract paramaters from roles + for role in interface.roles(): + if role_extract_params(role, self.params): + pass + #print "found conflicting role param %s for interface %s" % \ + # (role.name, interface.name) + # Extract paramaters from type rules + for rule in interface.typerules(): + if type_rule_extract_params(rule, self.params): + pass + #print "found conflicting params in rule %s in interface %s" % \ + # (str(rule), interface.name) + + for ifcall in interface.interface_calls(): + if ifcall_extract_params(ifcall, self.params): + pass + #print "found conflicting params in ifcall %s in interface %s" % \ + # (str(ifcall), interface.name) + + + def add_av(self, av): + if av_extract_params(av, self.params) == 1: + pass + #print "found conflicting perms [%s]" % str(av) + self.access.add_av(av) + + def to_string(self): + s = [] + s.append("[InterfaceVector %s]" % self.name) + for av in self.access: + s.append(str(av)) + return "\n".join(s) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "" % (self.name, self.enabled) + + +class InterfaceSet: + def __init__(self, output=None): + self.interfaces = { } + self.tgt_type_map = { } + self.tgt_type_all = [] + self.output = output + + def o(self, str): + if self.output: + self.output.write(str + "\n") + + def to_file(self, fd): + for iv in self.interfaces.values(): + fd.write("[InterfaceVector %s " % iv.name) + for param in iv.params.values(): + fd.write("%s:%s " % (param.name, refpolicy.field_to_str[param.type])) + fd.write("]\n") + avl = iv.access.to_list() + for av in avl: + fd.write(",".join(av)) + fd.write("\n") + + def from_file(self, fd): + def parse_ifv(line): + fields = line[1:-1].split() + if len(fields) < 2 or fields[0] != "InterfaceVector": + raise SyntaxError("Syntax error InterfaceVector statement %s" % line) + ifv = InterfaceVector() + ifv.name = fields[1] + if len(fields) == 2: + return + for field in fields[2:]: + p = field.split(":") + if len(p) != 2: + raise SyntaxError("Invalid param in InterfaceVector statement %s" % line) + param = Param() + param.name = p[0] + param.type = refpolicy.str_to_field[p[1]] + ifv.params[param.name] = param + return ifv + + ifv = None + for line in fd: + line = line[:-1] + if line[0] == "[": + if ifv: + self.add_ifv(ifv) + ifv = parse_ifv(line) + elif ifv: + l = line.split(",") + av = access.AccessVector(l) + ifv.add_av(av) + if ifv: + self.add_ifv(ifv) + + self.index() + + def add_ifv(self, ifv): + self.interfaces[ifv.name] = ifv + + def index(self): + for ifv in self.interfaces.values(): + tgt_types = set() + for av in ifv.access: + if access.is_idparam(av.tgt_type): + self.tgt_type_all.append(ifv) + tgt_types = set() + break + tgt_types.add(av.tgt_type) + + for type in tgt_types: + l = self.tgt_type_map.setdefault(type, []) + l.append(ifv) + + def add(self, interface, attributes={}): + ifv = InterfaceVector(interface, attributes) + self.add_ifv(ifv) + + def add_headers(self, headers, output=None, attributes={}): + for i in itertools.chain(headers.interfaces(), headers.templates()): + self.add(i, attributes) + + self.expand_ifcalls(headers) + self.index() + + def map_param(self, id, ifcall): + if access.is_idparam(id): + num = int(id[1:]) + if num > len(ifcall.args): + # Tell caller to drop this because it must have + # been generated from an optional param. + return None + else: + arg = ifcall.args[num - 1] + if isinstance(arg, list): + return arg + else: + return [arg] + else: + return [id] + + def map_add_av(self, ifv, av, ifcall): + src_types = self.map_param(av.src_type, ifcall) + if src_types is None: + return + + tgt_types = self.map_param(av.tgt_type, ifcall) + if tgt_types is None: + return + + obj_classes = self.map_param(av.obj_class, ifcall) + if obj_classes is None: + return + + new_perms = refpolicy.IdSet() + for perm in av.perms: + p = self.map_param(perm, ifcall) + if p is None: + continue + else: + new_perms.update(p) + if len(new_perms) == 0: + return + + for src_type in src_types: + for tgt_type in tgt_types: + for obj_class in obj_classes: + ifv.access.add(src_type, tgt_type, obj_class, new_perms) + + def do_expand_ifcalls(self, interface, if_by_name): + # Descend an interface call tree adding the access + # from each interface. This is a depth first walk + # of the tree. + + stack = [(interface, None)] + ifv = self.interfaces[interface.name] + ifv.expanded = True + + while len(stack) > 0: + cur, cur_ifcall = stack.pop(-1) + + cur_ifv = self.interfaces[cur.name] + if cur != interface: + + for av in cur_ifv.access: + self.map_add_av(ifv, av, cur_ifcall) + + # If we have already fully expanded this interface + # there is no reason to descend further. + if cur_ifv.expanded: + continue + + for ifcall in cur.interface_calls(): + if ifcall.ifname == interface.name: + self.o(_("Found circular interface class")) + return + try: + newif = if_by_name[ifcall.ifname] + except KeyError: + self.o(_("Missing interface definition for %s" % ifcall.ifname)) + continue + + stack.append((newif, ifcall)) + + + def expand_ifcalls(self, headers): + # Create a map of interface names to interfaces - + # this mirrors the interface vector map we already + # have. + if_by_name = { } + + for i in itertools.chain(headers.interfaces(), headers.templates()): + if_by_name[i.name] = i + + + for interface in itertools.chain(headers.interfaces(), headers.templates()): + self.do_expand_ifcalls(interface, if_by_name) + diff --git a/lib/python2.7/site-packages/sepolgen/lex.py b/lib/python2.7/site-packages/sepolgen/lex.py new file mode 100644 index 0000000..c149366 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/lex.py @@ -0,0 +1,866 @@ +#----------------------------------------------------------------------------- +# ply: lex.py +# +# Author: David M. Beazley (dave@dabeaz.com) +# +# Copyright (C) 2001-2006, David M. Beazley +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See the file COPYING for a complete copy of the LGPL. +#----------------------------------------------------------------------------- + +__version__ = "2.2" + +import re, sys, types + +# Regular expression used to match valid token names +_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') + +# Available instance types. This is used when lexers are defined by a class. +# It's a little funky because I want to preserve backwards compatibility +# with Python 2.0 where types.ObjectType is undefined. + +try: + _INSTANCETYPE = (types.InstanceType, types.ObjectType) +except AttributeError: + _INSTANCETYPE = types.InstanceType + class object: pass # Note: needed if no new-style classes present + +# Exception thrown when invalid token encountered and no default error +# handler is defined. +class LexError(Exception): + def __init__(self,message,s): + self.args = (message,) + self.text = s + +# Token class +class LexToken(object): + def __str__(self): + return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos) + def __repr__(self): + return str(self) + def skip(self,n): + self.lexer.skip(n) + +# ----------------------------------------------------------------------------- +# Lexer class +# +# This class encapsulates all of the methods and data associated with a lexer. +# +# input() - Store a new string in the lexer +# token() - Get the next token +# ----------------------------------------------------------------------------- + +class Lexer: + def __init__(self): + self.lexre = None # Master regular expression. This is a list of + # tuples (re,findex) where re is a compiled + # regular expression and findex is a list + # mapping regex group numbers to rules + self.lexretext = None # Current regular expression strings + self.lexstatere = {} # Dictionary mapping lexer states to master regexs + self.lexstateretext = {} # Dictionary mapping lexer states to regex strings + self.lexstate = "INITIAL" # Current lexer state + self.lexstatestack = [] # Stack of lexer states + self.lexstateinfo = None # State information + self.lexstateignore = {} # Dictionary of ignored characters for each state + self.lexstateerrorf = {} # Dictionary of error functions for each state + self.lexreflags = 0 # Optional re compile flags + self.lexdata = None # Actual input data (as a string) + self.lexpos = 0 # Current position in input text + self.lexlen = 0 # Length of the input text + self.lexerrorf = None # Error rule (if any) + self.lextokens = None # List of valid tokens + self.lexignore = "" # Ignored characters + self.lexliterals = "" # Literal characters that can be passed through + self.lexmodule = None # Module + self.lineno = 1 # Current line number + self.lexdebug = 0 # Debugging mode + self.lexoptimize = 0 # Optimized mode + + def clone(self,object=None): + c = Lexer() + c.lexstatere = self.lexstatere + c.lexstateinfo = self.lexstateinfo + c.lexstateretext = self.lexstateretext + c.lexstate = self.lexstate + c.lexstatestack = self.lexstatestack + c.lexstateignore = self.lexstateignore + c.lexstateerrorf = self.lexstateerrorf + c.lexreflags = self.lexreflags + c.lexdata = self.lexdata + c.lexpos = self.lexpos + c.lexlen = self.lexlen + c.lextokens = self.lextokens + c.lexdebug = self.lexdebug + c.lineno = self.lineno + c.lexoptimize = self.lexoptimize + c.lexliterals = self.lexliterals + c.lexmodule = self.lexmodule + + # If the object parameter has been supplied, it means we are attaching the + # lexer to a new object. In this case, we have to rebind all methods in + # the lexstatere and lexstateerrorf tables. + + if object: + newtab = { } + for key, ritem in self.lexstatere.items(): + newre = [] + for cre, findex in ritem: + newfindex = [] + for f in findex: + if not f or not f[0]: + newfindex.append(f) + continue + newfindex.append((getattr(object,f[0].__name__),f[1])) + newre.append((cre,newfindex)) + newtab[key] = newre + c.lexstatere = newtab + c.lexstateerrorf = { } + for key, ef in self.lexstateerrorf.items(): + c.lexstateerrorf[key] = getattr(object,ef.__name__) + c.lexmodule = object + + # Set up other attributes + c.begin(c.lexstate) + return c + + # ------------------------------------------------------------ + # writetab() - Write lexer information to a table file + # ------------------------------------------------------------ + def writetab(self,tabfile): + tf = open(tabfile+".py","w") + tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__)) + tf.write("_lextokens = %s\n" % repr(self.lextokens)) + tf.write("_lexreflags = %s\n" % repr(self.lexreflags)) + tf.write("_lexliterals = %s\n" % repr(self.lexliterals)) + tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo)) + + tabre = { } + for key, lre in self.lexstatere.items(): + titem = [] + for i in range(len(lre)): + titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1]))) + tabre[key] = titem + + tf.write("_lexstatere = %s\n" % repr(tabre)) + tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore)) + + taberr = { } + for key, ef in self.lexstateerrorf.items(): + if ef: + taberr[key] = ef.__name__ + else: + taberr[key] = None + tf.write("_lexstateerrorf = %s\n" % repr(taberr)) + tf.close() + + # ------------------------------------------------------------ + # readtab() - Read lexer information from a tab file + # ------------------------------------------------------------ + def readtab(self,tabfile,fdict): + exec "import %s as lextab" % tabfile + self.lextokens = lextab._lextokens + self.lexreflags = lextab._lexreflags + self.lexliterals = lextab._lexliterals + self.lexstateinfo = lextab._lexstateinfo + self.lexstateignore = lextab._lexstateignore + self.lexstatere = { } + self.lexstateretext = { } + for key,lre in lextab._lexstatere.items(): + titem = [] + txtitem = [] + for i in range(len(lre)): + titem.append((re.compile(lre[i][0],lextab._lexreflags),_names_to_funcs(lre[i][1],fdict))) + txtitem.append(lre[i][0]) + self.lexstatere[key] = titem + self.lexstateretext[key] = txtitem + self.lexstateerrorf = { } + for key,ef in lextab._lexstateerrorf.items(): + self.lexstateerrorf[key] = fdict[ef] + self.begin('INITIAL') + + # ------------------------------------------------------------ + # input() - Push a new string into the lexer + # ------------------------------------------------------------ + def input(self,s): + if not (isinstance(s,types.StringType) or isinstance(s,types.UnicodeType)): + raise ValueError, "Expected a string" + self.lexdata = s + self.lexpos = 0 + self.lexlen = len(s) + + # ------------------------------------------------------------ + # begin() - Changes the lexing state + # ------------------------------------------------------------ + def begin(self,state): + if not self.lexstatere.has_key(state): + raise ValueError, "Undefined state" + self.lexre = self.lexstatere[state] + self.lexretext = self.lexstateretext[state] + self.lexignore = self.lexstateignore.get(state,"") + self.lexerrorf = self.lexstateerrorf.get(state,None) + self.lexstate = state + + # ------------------------------------------------------------ + # push_state() - Changes the lexing state and saves old on stack + # ------------------------------------------------------------ + def push_state(self,state): + self.lexstatestack.append(self.lexstate) + self.begin(state) + + # ------------------------------------------------------------ + # pop_state() - Restores the previous state + # ------------------------------------------------------------ + def pop_state(self): + self.begin(self.lexstatestack.pop()) + + # ------------------------------------------------------------ + # current_state() - Returns the current lexing state + # ------------------------------------------------------------ + def current_state(self): + return self.lexstate + + # ------------------------------------------------------------ + # skip() - Skip ahead n characters + # ------------------------------------------------------------ + def skip(self,n): + self.lexpos += n + + # ------------------------------------------------------------ + # token() - Return the next token from the Lexer + # + # Note: This function has been carefully implemented to be as fast + # as possible. Don't make changes unless you really know what + # you are doing + # ------------------------------------------------------------ + def token(self): + # Make local copies of frequently referenced attributes + lexpos = self.lexpos + lexlen = self.lexlen + lexignore = self.lexignore + lexdata = self.lexdata + + while lexpos < lexlen: + # This code provides some short-circuit code for whitespace, tabs, and other ignored characters + if lexdata[lexpos] in lexignore: + lexpos += 1 + continue + + # Look for a regular expression match + for lexre,lexindexfunc in self.lexre: + m = lexre.match(lexdata,lexpos) + if not m: continue + + # Set last match in lexer so that rules can access it if they want + self.lexmatch = m + + # Create a token for return + tok = LexToken() + tok.value = m.group() + tok.lineno = self.lineno + tok.lexpos = lexpos + tok.lexer = self + + lexpos = m.end() + i = m.lastindex + func,tok.type = lexindexfunc[i] + self.lexpos = lexpos + + if not func: + # If no token type was set, it's an ignored token + if tok.type: return tok + break + + # if func not callable, it means it's an ignored token + if not callable(func): + break + + # If token is processed by a function, call it + newtok = func(tok) + + # Every function must return a token, if nothing, we just move to next token + if not newtok: + lexpos = self.lexpos # This is here in case user has updated lexpos. + break + + # Verify type of the token. If not in the token map, raise an error + if not self.lexoptimize: + if not self.lextokens.has_key(newtok.type): + raise LexError, ("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( + func.func_code.co_filename, func.func_code.co_firstlineno, + func.__name__, newtok.type),lexdata[lexpos:]) + + return newtok + else: + # No match, see if in literals + if lexdata[lexpos] in self.lexliterals: + tok = LexToken() + tok.value = lexdata[lexpos] + tok.lineno = self.lineno + tok.lexer = self + tok.type = tok.value + tok.lexpos = lexpos + self.lexpos = lexpos + 1 + return tok + + # No match. Call t_error() if defined. + if self.lexerrorf: + tok = LexToken() + tok.value = self.lexdata[lexpos:] + tok.lineno = self.lineno + tok.type = "error" + tok.lexer = self + tok.lexpos = lexpos + self.lexpos = lexpos + newtok = self.lexerrorf(tok) + if lexpos == self.lexpos: + # Error method didn't change text position at all. This is an error. + raise LexError, ("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + lexpos = self.lexpos + if not newtok: continue + return newtok + + self.lexpos = lexpos + raise LexError, ("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:]) + + self.lexpos = lexpos + 1 + if self.lexdata is None: + raise RuntimeError, "No input string given with input()" + return None + +# ----------------------------------------------------------------------------- +# _validate_file() +# +# This checks to see if there are duplicated t_rulename() functions or strings +# in the parser input file. This is done using a simple regular expression +# match on each line in the filename. +# ----------------------------------------------------------------------------- + +def _validate_file(filename): + import os.path + base,ext = os.path.splitext(filename) + if ext != '.py': return 1 # No idea what the file is. Return OK + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + return 1 # Oh well + + fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') + sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') + counthash = { } + linen = 1 + noerror = 1 + for l in lines: + m = fre.match(l) + if not m: + m = sre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + print "%s:%d: Rule %s redefined. Previously defined on line %d" % (filename,linen,name,prev) + noerror = 0 + linen += 1 + return noerror + +# ----------------------------------------------------------------------------- +# _funcs_to_names() +# +# Given a list of regular expression functions, this converts it to a list +# suitable for output to a table file +# ----------------------------------------------------------------------------- + +def _funcs_to_names(funclist): + result = [] + for f in funclist: + if f and f[0]: + result.append((f[0].__name__,f[1])) + else: + result.append(f) + return result + +# ----------------------------------------------------------------------------- +# _names_to_funcs() +# +# Given a list of regular expression function names, this converts it back to +# functions. +# ----------------------------------------------------------------------------- + +def _names_to_funcs(namelist,fdict): + result = [] + for n in namelist: + if n and n[0]: + result.append((fdict[n[0]],n[1])) + else: + result.append(n) + return result + +# ----------------------------------------------------------------------------- +# _form_master_re() +# +# This function takes a list of all of the regex components and attempts to +# form the master regular expression. Given limitations in the Python re +# module, it may be necessary to break the master regex into separate expressions. +# ----------------------------------------------------------------------------- + +def _form_master_re(relist,reflags,ldict): + if not relist: return [] + regex = "|".join(relist) + try: + lexre = re.compile(regex,re.VERBOSE | reflags) + + # Build the index to function map for the matching engine + lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1) + for f,i in lexre.groupindex.items(): + handle = ldict.get(f,None) + if type(handle) in (types.FunctionType, types.MethodType): + lexindexfunc[i] = (handle,handle.__name__[2:]) + elif handle is not None: + # If rule was specified as a string, we build an anonymous + # callback function to carry out the action + if f.find("ignore_") > 0: + lexindexfunc[i] = (None,None) + print "IGNORE", f + else: + lexindexfunc[i] = (None, f[2:]) + + return [(lexre,lexindexfunc)],[regex] + except Exception,e: + m = int(len(relist)/2) + if m == 0: m = 1 + llist, lre = _form_master_re(relist[:m],reflags,ldict) + rlist, rre = _form_master_re(relist[m:],reflags,ldict) + return llist+rlist, lre+rre + +# ----------------------------------------------------------------------------- +# def _statetoken(s,names) +# +# Given a declaration name s of the form "t_" and a dictionary whose keys are +# state names, this function returns a tuple (states,tokenname) where states +# is a tuple of state names and tokenname is the name of the token. For example, +# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') +# ----------------------------------------------------------------------------- + +def _statetoken(s,names): + nonstate = 1 + parts = s.split("_") + for i in range(1,len(parts)): + if not names.has_key(parts[i]) and parts[i] != 'ANY': break + if i > 1: + states = tuple(parts[1:i]) + else: + states = ('INITIAL',) + + if 'ANY' in states: + states = tuple(names.keys()) + + tokenname = "_".join(parts[i:]) + return (states,tokenname) + +# ----------------------------------------------------------------------------- +# lex(module) +# +# Build all of the regular expression rules from definitions in the supplied module +# ----------------------------------------------------------------------------- +def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0): + global lexer + ldict = None + stateinfo = { 'INITIAL' : 'inclusive'} + error = 0 + files = { } + lexobj = Lexer() + lexobj.lexdebug = debug + lexobj.lexoptimize = optimize + global token,input + + if nowarn: warn = 0 + else: warn = 1 + + if object: module = object + + if module: + # User supplied a module object. + if isinstance(module, types.ModuleType): + ldict = module.__dict__ + elif isinstance(module, _INSTANCETYPE): + _items = [(k,getattr(module,k)) for k in dir(module)] + ldict = { } + for (i,v) in _items: + ldict[i] = v + else: + raise ValueError,"Expected a module or instance" + lexobj.lexmodule = module + + else: + # No module given. We might be able to get information from the caller. + try: + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + f = f.f_back # Walk out to our calling function + ldict = f.f_globals # Grab its globals dictionary + + if optimize and lextab: + try: + lexobj.readtab(lextab,ldict) + token = lexobj.token + input = lexobj.input + lexer = lexobj + return lexobj + + except ImportError: + pass + + # Get the tokens, states, and literals variables (if any) + if (module and isinstance(module,_INSTANCETYPE)): + tokens = getattr(module,"tokens",None) + states = getattr(module,"states",None) + literals = getattr(module,"literals","") + else: + tokens = ldict.get("tokens",None) + states = ldict.get("states",None) + literals = ldict.get("literals","") + + if not tokens: + raise SyntaxError,"lex: module does not define 'tokens'" + if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)): + raise SyntaxError,"lex: tokens must be a list or tuple." + + # Build a dictionary of valid token names + lexobj.lextokens = { } + if not optimize: + for n in tokens: + if not _is_identifier.match(n): + print "lex: Bad token name '%s'" % n + error = 1 + if warn and lexobj.lextokens.has_key(n): + print "lex: Warning. Token '%s' multiply defined." % n + lexobj.lextokens[n] = None + else: + for n in tokens: lexobj.lextokens[n] = None + + if debug: + print "lex: tokens = '%s'" % lexobj.lextokens.keys() + + try: + for c in literals: + if not (isinstance(c,types.StringType) or isinstance(c,types.UnicodeType)) or len(c) > 1: + print "lex: Invalid literal %s. Must be a single character" % repr(c) + error = 1 + continue + + except TypeError: + print "lex: Invalid literals specification. literals must be a sequence of characters." + error = 1 + + lexobj.lexliterals = literals + + # Build statemap + if states: + if not (isinstance(states,types.TupleType) or isinstance(states,types.ListType)): + print "lex: states must be defined as a tuple or list." + error = 1 + else: + for s in states: + if not isinstance(s,types.TupleType) or len(s) != 2: + print "lex: invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')" % repr(s) + error = 1 + continue + name, statetype = s + if not isinstance(name,types.StringType): + print "lex: state name %s must be a string" % repr(name) + error = 1 + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + print "lex: state type for state %s must be 'inclusive' or 'exclusive'" % name + error = 1 + continue + if stateinfo.has_key(name): + print "lex: state '%s' already defined." % name + error = 1 + continue + stateinfo[name] = statetype + + # Get a list of symbols with the t_ or s_ prefix + tsymbols = [f for f in ldict.keys() if f[:2] == 't_' ] + + # Now build up a list of functions and a list of strings + + funcsym = { } # Symbols defined as functions + strsym = { } # Symbols defined as strings + toknames = { } # Mapping of symbols to token names + + for s in stateinfo.keys(): + funcsym[s] = [] + strsym[s] = [] + + ignore = { } # Ignore strings by state + errorf = { } # Error functions by state + + if len(tsymbols) == 0: + raise SyntaxError,"lex: no rules of the form t_rulename are defined." + + for f in tsymbols: + t = ldict[f] + states, tokname = _statetoken(f,stateinfo) + toknames[f] = tokname + + if callable(t): + for s in states: funcsym[s].append((f,t)) + elif (isinstance(t, types.StringType) or isinstance(t,types.UnicodeType)): + for s in states: strsym[s].append((f,t)) + else: + print "lex: %s not defined as a function or string" % f + error = 1 + + # Sort the functions by line number + for f in funcsym.values(): + f.sort(lambda x,y: cmp(x[1].func_code.co_firstlineno,y[1].func_code.co_firstlineno)) + + # Sort the strings by regular expression length + for s in strsym.values(): + s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) + + regexs = { } + + # Build the master regular expressions + for state in stateinfo.keys(): + regex_list = [] + + # Add rules defined by functions first + for fname, f in funcsym[state]: + line = f.func_code.co_firstlineno + file = f.func_code.co_filename + files[file] = None + tokname = toknames[fname] + + ismethod = isinstance(f, types.MethodType) + + if not optimize: + nargs = f.func_code.co_argcount + if ismethod: + reqargs = 2 + else: + reqargs = 1 + if nargs > reqargs: + print "%s:%d: Rule '%s' has too many arguments." % (file,line,f.__name__) + error = 1 + continue + + if nargs < reqargs: + print "%s:%d: Rule '%s' requires an argument." % (file,line,f.__name__) + error = 1 + continue + + if tokname == 'ignore': + print "%s:%d: Rule '%s' must be defined as a string." % (file,line,f.__name__) + error = 1 + continue + + if tokname == 'error': + errorf[state] = f + continue + + if f.__doc__: + if not optimize: + try: + c = re.compile("(?P<%s>%s)" % (f.__name__,f.__doc__), re.VERBOSE | reflags) + if c.match(""): + print "%s:%d: Regular expression for rule '%s' matches empty string." % (file,line,f.__name__) + error = 1 + continue + except re.error,e: + print "%s:%d: Invalid regular expression for rule '%s'. %s" % (file,line,f.__name__,e) + if '#' in f.__doc__: + print "%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'." % (file,line, f.__name__) + error = 1 + continue + + if debug: + print "lex: Adding rule %s -> '%s' (state '%s')" % (f.__name__,f.__doc__, state) + + # Okay. The regular expression seemed okay. Let's append it to the master regular + # expression we're building + + regex_list.append("(?P<%s>%s)" % (f.__name__,f.__doc__)) + else: + print "%s:%d: No regular expression defined for rule '%s'" % (file,line,f.__name__) + + # Now add all of the simple rules + for name,r in strsym[state]: + tokname = toknames[name] + + if tokname == 'ignore': + ignore[state] = r + continue + + if not optimize: + if tokname == 'error': + raise SyntaxError,"lex: Rule '%s' must be defined as a function" % name + error = 1 + continue + + if not lexobj.lextokens.has_key(tokname) and tokname.find("ignore_") < 0: + print "lex: Rule '%s' defined for an unspecified token %s." % (name,tokname) + error = 1 + continue + try: + c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | reflags) + if (c.match("")): + print "lex: Regular expression for rule '%s' matches empty string." % name + error = 1 + continue + except re.error,e: + print "lex: Invalid regular expression for rule '%s'. %s" % (name,e) + if '#' in r: + print "lex: Make sure '#' in rule '%s' is escaped with '\\#'." % name + + error = 1 + continue + if debug: + print "lex: Adding rule %s -> '%s' (state '%s')" % (name,r,state) + + regex_list.append("(?P<%s>%s)" % (name,r)) + + if not regex_list: + print "lex: No rules defined for state '%s'" % state + error = 1 + + regexs[state] = regex_list + + + if not optimize: + for f in files.keys(): + if not _validate_file(f): + error = 1 + + if error: + raise SyntaxError,"lex: Unable to build lexer." + + # From this point forward, we're reasonably confident that we can build the lexer. + # No more errors will be generated, but there might be some warning messages. + + # Build the master regular expressions + + for state in regexs.keys(): + lexre, re_text = _form_master_re(regexs[state],reflags,ldict) + lexobj.lexstatere[state] = lexre + lexobj.lexstateretext[state] = re_text + if debug: + for i in range(len(re_text)): + print "lex: state '%s'. regex[%d] = '%s'" % (state, i, re_text[i]) + + # For inclusive states, we need to add the INITIAL state + for state,type in stateinfo.items(): + if state != "INITIAL" and type == 'inclusive': + lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) + lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) + + lexobj.lexstateinfo = stateinfo + lexobj.lexre = lexobj.lexstatere["INITIAL"] + lexobj.lexretext = lexobj.lexstateretext["INITIAL"] + + # Set up ignore variables + lexobj.lexstateignore = ignore + lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","") + + # Set up error functions + lexobj.lexstateerrorf = errorf + lexobj.lexerrorf = errorf.get("INITIAL",None) + if warn and not lexobj.lexerrorf: + print "lex: Warning. no t_error rule is defined." + + # Check state information for ignore and error rules + for s,stype in stateinfo.items(): + if stype == 'exclusive': + if warn and not errorf.has_key(s): + print "lex: Warning. no error rule is defined for exclusive state '%s'" % s + if warn and not ignore.has_key(s) and lexobj.lexignore: + print "lex: Warning. no ignore rule is defined for exclusive state '%s'" % s + elif stype == 'inclusive': + if not errorf.has_key(s): + errorf[s] = errorf.get("INITIAL",None) + if not ignore.has_key(s): + ignore[s] = ignore.get("INITIAL","") + + + # Create global versions of the token() and input() functions + token = lexobj.token + input = lexobj.input + lexer = lexobj + + # If in optimize mode, we write the lextab + if lextab and optimize: + lexobj.writetab(lextab) + + return lexobj + +# ----------------------------------------------------------------------------- +# runmain() +# +# This runs the lexer as a main program +# ----------------------------------------------------------------------------- + +def runmain(lexer=None,data=None): + if not data: + try: + filename = sys.argv[1] + f = open(filename) + data = f.read() + f.close() + except IndexError: + print "Reading from standard input (type EOF to end):" + data = sys.stdin.read() + + if lexer: + _input = lexer.input + else: + _input = input + _input(data) + if lexer: + _token = lexer.token + else: + _token = token + + while 1: + tok = _token() + if not tok: break + print "(%s,%r,%d,%d)" % (tok.type, tok.value, tok.lineno,tok.lexpos) + + +# ----------------------------------------------------------------------------- +# @TOKEN(regex) +# +# This decorator function can be used to set the regex expression on a function +# when its docstring might need to be set in an alternative way +# ----------------------------------------------------------------------------- + +def TOKEN(r): + def set_doc(f): + f.__doc__ = r + return f + return set_doc + +# Alternative spelling of the TOKEN decorator +Token = TOKEN + diff --git a/lib/python2.7/site-packages/sepolgen/matching.py b/lib/python2.7/site-packages/sepolgen/matching.py new file mode 100644 index 0000000..d56dd92 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/matching.py @@ -0,0 +1,255 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes and algorithms for matching requested access to access vectors. +""" + +import access +import objectmodel +import itertools + +class Match: + def __init__(self, interface=None, dist=0): + self.interface = interface + self.dist = dist + self.info_dir_change = False + + def __cmp__(self, other): + if self.dist == other.dist: + if self.info_dir_change: + if other.info_dir_change: + return 0 + else: + return 1 + else: + if other.info_dir_change: + return -1 + else: + return 0 + else: + if self.dist < other.dist: + return -1 + else: + return 1 + +class MatchList: + DEFAULT_THRESHOLD = 150 + def __init__(self): + # Match objects that pass the threshold + self.children = [] + # Match objects over the threshold + self.bastards = [] + self.threshold = self.DEFAULT_THRESHOLD + self.allow_info_dir_change = False + self.av = None + + def best(self): + if len(self.children): + return self.children[0] + if len(self.bastards): + return self.bastards[0] + return None + + def __len__(self): + # Only return the length of the matches so + # that this can be used to test if there is + # a match. + return len(self.children) + len(self.bastards) + + def __iter__(self): + return iter(self.children) + + def all(self): + return itertools.chain(self.children, self.bastards) + + def append(self, match): + if match.dist <= self.threshold: + if not match.info_dir_change or self.allow_info_dir_change: + self.children.append(match) + else: + self.bastards.append(match) + else: + self.bastards.append(match) + + def sort(self): + self.children.sort() + self.bastards.sort() + + +class AccessMatcher: + def __init__(self, perm_maps=None): + self.type_penalty = 10 + self.obj_penalty = 10 + if perm_maps: + self.perm_maps = perm_maps + else: + self.perm_maps = objectmodel.PermMappings() + # We want a change in the information flow direction + # to be a strong penalty - stronger than access to + # a few unrelated types. + self.info_dir_penalty = 100 + + def type_distance(self, a, b): + if a == b or access.is_idparam(b): + return 0 + else: + return -self.type_penalty + + + def perm_distance(self, av_req, av_prov): + # First check that we have enough perms + diff = av_req.perms.difference(av_prov.perms) + + if len(diff) != 0: + total = self.perm_maps.getdefault_distance(av_req.obj_class, diff) + return -total + else: + diff = av_prov.perms.difference(av_req.perms) + return self.perm_maps.getdefault_distance(av_req.obj_class, diff) + + def av_distance(self, req, prov): + """Determine the 'distance' between 2 access vectors. + + This function is used to find an access vector that matches + a 'required' access. To do this we comput a signed numeric + value that indicates how close the req access is to the + 'provided' access vector. The closer the value is to 0 + the closer the match, with 0 being an exact match. + + A value over 0 indicates that the prov access vector provides more + access than the req (in practice, this means that the source type, + target type, and object class is the same and the perms in prov is + a superset of those in req. + + A value under 0 indicates that the prov access less - or unrelated + - access to the req access. A different type or object class will + result in a very low value. + + The values other than 0 should only be interpreted relative to + one another - they have no exact meaning and are likely to + change. + + Params: + req - [AccessVector] The access that is required. This is the + access being matched. + prov - [AccessVector] The access provided. This is the potential + match that is being evaluated for req. + Returns: + 0 : Exact match between the acess vectors. + + < 0 : The prov av does not provide all of the access in req. + A smaller value indicates that the access is further. + + > 0 : The prov av provides more access than req. The larger + the value the more access over req. + """ + # FUTURE - this is _very_ expensive and probably needs some + # thorough performance work. This version is meant to give + # meaningful results relatively simply. + dist = 0 + + # Get the difference between the types. The addition is safe + # here because type_distance only returns 0 or negative. + dist += self.type_distance(req.src_type, prov.src_type) + dist += self.type_distance(req.tgt_type, prov.tgt_type) + + # Object class distance + if req.obj_class != prov.obj_class and not access.is_idparam(prov.obj_class): + dist -= self.obj_penalty + + # Permission distance + + # If this av doesn't have a matching source type, target type, and object class + # count all of the permissions against it. Otherwise determine the perm + # distance and dir. + if dist < 0: + pdist = self.perm_maps.getdefault_distance(prov.obj_class, prov.perms) + else: + pdist = self.perm_distance(req, prov) + + # Combine the perm and other distance + if dist < 0: + if pdist < 0: + return dist + pdist + else: + return dist - pdist + elif dist >= 0: + if pdist < 0: + return pdist - dist + else: + return dist + pdist + + def av_set_match(self, av_set, av): + """ + + """ + dist = None + + # Get the distance for each access vector + for x in av_set: + tmp = self.av_distance(av, x) + if dist is None: + dist = tmp + elif tmp >= 0: + if dist >= 0: + dist += tmp + else: + dist = tmp + -dist + else: + if dist < 0: + dist += tmp + else: + dist -= tmp + + # Penalize for information flow - we want to prevent the + # addition of a write if the requested is read none. We are + # much less concerned about the reverse. + av_dir = self.perm_maps.getdefault_direction(av.obj_class, av.perms) + + if av_set.info_dir is None: + av_set.info_dir = objectmodel.FLOW_NONE + for x in av_set: + av_set.info_dir = av_set.info_dir | \ + self.perm_maps.getdefault_direction(x.obj_class, x.perms) + if (av_dir & objectmodel.FLOW_WRITE == 0) and (av_set.info_dir & objectmodel.FLOW_WRITE): + if dist < 0: + dist -= self.info_dir_penalty + else: + dist += self.info_dir_penalty + + return dist + + def search_ifs(self, ifset, av, match_list): + match_list.av = av + for iv in itertools.chain(ifset.tgt_type_all, + ifset.tgt_type_map.get(av.tgt_type, [])): + if not iv.enabled: + #print "iv %s not enabled" % iv.name + continue + + dist = self.av_set_match(iv.access, av) + if dist >= 0: + m = Match(iv, dist) + match_list.append(m) + + + match_list.sort() + + diff --git a/lib/python2.7/site-packages/sepolgen/module.py b/lib/python2.7/site-packages/sepolgen/module.py new file mode 100644 index 0000000..7fc9443 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/module.py @@ -0,0 +1,213 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Utilities for dealing with the compilation of modules and creation +of module tress. +""" + +import defaults + +import selinux + +import re +import tempfile +import commands +import os +import os.path +import subprocess +import shutil + +def is_valid_name(modname): + """Check that a module name is valid. + """ + m = re.findall("[^a-zA-Z0-9_\-\.]", modname) + if len(m) == 0 and modname[0].isalpha(): + return True + else: + return False + +class ModuleTree: + def __init__(self, modname): + self.modname = modname + self.dirname = None + + def dir_name(self): + return self.dirname + + def te_name(self): + return self.dirname + "/" + self.modname + ".te" + + def fc_name(self): + return self.dirname + "/" + self.modname + ".fc" + + def if_name(self): + return self.dirname + "/" + self.modname + ".if" + + def package_name(self): + return self.dirname + "/" + self.modname + ".pp" + + def makefile_name(self): + return self.dirname + "/Makefile" + + def create(self, parent_dirname, makefile_include=None): + self.dirname = parent_dirname + "/" + self.modname + os.mkdir(self.dirname) + fd = open(self.makefile_name(), "w") + if makefile_include: + fd.write("include " + makefile_include) + else: + fd.write("include " + defaults.refpolicy_makefile()) + fd.close() + + # Create empty files for the standard refpolicy + # module files + open(self.te_name(), "w").close() + open(self.fc_name(), "w").close() + open(self.if_name(), "w").close() + +def modname_from_sourcename(sourcename): + return os.path.splitext(os.path.split(sourcename)[1])[0] + +class ModuleCompiler: + """ModuleCompiler eases running of the module compiler. + + The ModuleCompiler class encapsulates running the commandline + module compiler (checkmodule) and module packager (semodule_package). + You are likely interested in the create_module_package method. + + Several options are controlled via paramaters (only effects the + non-refpol builds): + + .mls [boolean] Generate an MLS module (by passed -M to + checkmodule). True to generate an MLS module, false + otherwise. + + .module [boolean] Generate a module instead of a base module. + True to generate a module, false to generate a base. + + .checkmodule [string] Fully qualified path to the module compiler. + Default is /usr/bin/checkmodule. + + .semodule_package [string] Fully qualified path to the module + packager. Defaults to /usr/bin/semodule_package. + .output [file object] File object used to write verbose + output of the compililation and packaging process. + """ + def __init__(self, output=None): + """Create a ModuleCompiler instance, optionally with an + output file object for verbose output of the compilation process. + """ + self.mls = selinux.is_selinux_mls_enabled() + self.module = True + self.checkmodule = "/usr/bin/checkmodule" + self.semodule_package = "/usr/bin/semodule_package" + self.output = output + self.last_output = "" + self.refpol_makefile = defaults.refpolicy_makefile() + self.make = "/usr/bin/make" + + def o(self, str): + if self.output: + self.output.write(str + "\n") + self.last_output = str + + def run(self, command): + self.o(command) + rc, output = commands.getstatusoutput(command) + self.o(output) + + return rc + + def gen_filenames(self, sourcename): + """Generate the module and policy package filenames from + a source file name. The source file must be in the form + of "foo.te". This will generate "foo.mod" and "foo.pp". + + Returns a tuple with (modname, policypackage). + """ + splitname = sourcename.split(".") + if len(splitname) < 2: + raise RuntimeError("invalid sourcefile name %s (must end in .te)", sourcename) + # Handle other periods in the filename correctly + basename = ".".join(splitname[0:-1]) + modname = basename + ".mod" + packagename = basename + ".pp" + + return (modname, packagename) + + def create_module_package(self, sourcename, refpolicy=True): + """Create a module package saved in a packagename from a + sourcename. + + The create_module_package creates a module package saved in a + file named sourcename (.pp is the standard extension) from a + source file (.te is the standard extension). The source file + should contain SELinux policy statements appropriate for a + base or non-base module (depending on the setting of .module). + + Only file names are accepted, not open file objects or + descriptors because the command line SELinux tools are used. + + On error a RuntimeError will be raised with a descriptive + error message. + """ + if refpolicy: + self.refpol_build(sourcename) + else: + modname, packagename = self.gen_filenames(sourcename) + self.compile(sourcename, modname) + self.package(modname, packagename) + os.unlink(modname) + + def refpol_build(self, sourcename): + # Compile + command = self.make + " -f " + self.refpol_makefile + rc = self.run(command) + + # Raise an error if the process failed + if rc != 0: + raise RuntimeError("compilation failed:\n%s" % self.last_output) + + def compile(self, sourcename, modname): + s = [self.checkmodule] + if self.mls: + s.append("-M") + if self.module: + s.append("-m") + s.append("-o") + s.append(modname) + s.append(sourcename) + + rc = self.run(" ".join(s)) + if rc != 0: + raise RuntimeError("compilation failed:\n%s" % self.last_output) + + def package(self, modname, packagename): + s = [self.semodule_package] + s.append("-o") + s.append(packagename) + s.append("-m") + s.append(modname) + + rc = self.run(" ".join(s)) + if rc != 0: + raise RuntimeError("packaging failed [%s]" % self.last_output) + + diff --git a/lib/python2.7/site-packages/sepolgen/objectmodel.py b/lib/python2.7/site-packages/sepolgen/objectmodel.py new file mode 100644 index 0000000..88c8a1f --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/objectmodel.py @@ -0,0 +1,172 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +This module provides knowledge object classes and permissions. It should +be used to keep this knowledge from leaking into the more generic parts of +the policy generation. +""" + +# Objects that can be implicitly typed - these objects do +# not _have_ to be implicitly typed (e.g., sockets can be +# explicitly labeled), but they often are. +# +# File is in this list for /proc/self +# +# This list is useful when dealing with rules that have a +# type (or param) used as both a subject and object. For +# example: +# +# allow httpd_t httpd_t : socket read; +# +# This rule makes sense because the socket was (presumably) created +# by a process with the type httpd_t. +implicitly_typed_objects = ["socket", "fd", "process", "file", "lnk_file", "fifo_file", + "dbus", "capability", "unix_stream_socket"] + +#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +# +#Information Flow +# +# All of the permissions in SELinux can be described in terms of +# information flow. For example, a read of a file is a flow of +# information from that file to the process reading. Viewing +# permissions in these terms can be used to model a varity of +# security properties. +# +# Here we have some infrastructure for understanding permissions +# in terms of information flow +# +#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +# Information flow deals with information either flowing from a subject +# to and object ("write") or to a subject from an object ("read"). Read +# or write is described from the subject point-of-view. It is also possible +# for a permission to represent both a read and write (though the flow is +# typical asymettric in terms of bandwidth). It is also possible for +# permission to not flow information (meaning that the result is pure +# side-effect). +# +# The following constants are for representing the directionality +# of information flow. +FLOW_NONE = 0 +FLOW_READ = 1 +FLOW_WRITE = 2 +FLOW_BOTH = FLOW_READ | FLOW_WRITE + +# These are used by the parser and for nice disply of the directions +str_to_dir = { "n" : FLOW_NONE, "r" : FLOW_READ, "w" : FLOW_WRITE, "b" : FLOW_BOTH } +dir_to_str = { FLOW_NONE : "n", FLOW_READ : "r", FLOW_WRITE : "w", FLOW_BOTH : "b" } + +class PermMap: + """A mapping between a permission and its information flow properties. + + PermMap represents the information flow properties of a single permission + including the direction (read, write, etc.) and an abstract representation + of the bandwidth of the flow (weight). + """ + def __init__(self, perm, dir, weight): + self.perm = perm + self.dir = dir + self.weight = weight + + def __repr__(self): + return "" % (self.perm, + dir_to_str[self.dir], + self.weight) + +class PermMappings: + """The information flow properties of a set of object classes and permissions. + + PermMappings maps one or more classes and permissions to their PermMap objects + describing their information flow charecteristics. + """ + def __init__(self): + self.classes = { } + self.default_weight = 5 + self.default_dir = FLOW_BOTH + + def from_file(self, fd): + """Read the permission mappings from a file. This reads the format used + by Apol in the setools suite. + """ + # This parsing is deliberitely picky and bails at the least error. It + # is assumed that the permission map file will be shipped as part + # of sepolgen and not user modified, so this is a reasonable design + # choice. If user supplied permission mappings are needed the parser + # should be made a little more robust and give better error messages. + cur = None + for line in fd: + fields = line.split() + if len(fields) == 0 or len(fields) == 1 or fields[0] == "#": + continue + if fields[0] == "class": + c = fields[1] + if self.classes.has_key(c): + raise ValueError("duplicate class in perm map") + self.classes[c] = { } + cur = self.classes[c] + else: + if len(fields) != 3: + raise ValueError("error in object classs permissions") + if cur is None: + raise ValueError("permission outside of class") + pm = PermMap(fields[0], str_to_dir[fields[1]], int(fields[2])) + cur[pm.perm] = pm + + def get(self, obj, perm): + """Get the permission map for the object permission. + + Returns: + PermMap representing the permission + Raises: + KeyError if the object or permission is not defined + """ + return self.classes[obj][perm] + + def getdefault(self, obj, perm): + """Get the permission map for the object permission or a default. + + getdefault is the same as get except that a default PermMap is + returned if the object class or permission is not defined. The + default is FLOW_BOTH with a weight of 5. + """ + try: + pm = self.classes[obj][perm] + except KeyError: + return PermMap(perm, self.default_dir, self.default_weight) + return pm + + def getdefault_direction(self, obj, perms): + dir = FLOW_NONE + for perm in perms: + pm = self.getdefault(obj, perm) + dir = dir | pm.dir + return dir + + def getdefault_distance(self, obj, perms): + total = 0 + for perm in perms: + pm = self.getdefault(obj, perm) + total += pm.weight + + return total + + + diff --git a/lib/python2.7/site-packages/sepolgen/output.py b/lib/python2.7/site-packages/sepolgen/output.py new file mode 100644 index 0000000..739452d --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/output.py @@ -0,0 +1,173 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes and functions for the output of reference policy modules. + +This module takes a refpolicy.Module object and formats it for +output using the ModuleWriter object. By separating the output +in this way the other parts of Madison can focus solely on +generating policy. This keeps the semantic / syntactic issues +cleanly separated from the formatting issues. +""" + +import refpolicy +import util + +class ModuleWriter: + def __init__(self): + self.fd = None + self.module = None + self.sort = True + self.requires = True + + def write(self, module, fd): + self.module = module + + if self.sort: + sort_filter(self.module) + + # FIXME - make this handle nesting + for node, depth in refpolicy.walktree(self.module, showdepth=True): + fd.write("%s\n" % str(node)) + +# Helper functions for sort_filter - this is all done old school +# C style rather than with polymorphic methods because this sorting +# is specific to output. It is not necessarily the comparison you +# want generally. + +# Compare two IdSets - we could probably do something clever +# with different here, but this works. +def id_set_cmp(x, y): + xl = util.set_to_list(x) + xl.sort() + yl = util.set_to_list(y) + yl.sort() + + if len(xl) != len(yl): + return cmp(xl[0], yl[0]) + for v in zip(xl, yl): + if v[0] != v[1]: + return cmp(v[0], v[1]) + return 0 + +# Compare two avrules +def avrule_cmp(a, b): + ret = id_set_cmp(a.src_types, b.src_types) + if ret is not 0: + return ret + ret = id_set_cmp(a.tgt_types, b.tgt_types) + if ret is not 0: + return ret + ret = id_set_cmp(a.obj_classes, b.obj_classes) + if ret is not 0: + return ret + + # At this point, who cares - just return something + return cmp(len(a.perms), len(b.perms)) + +# Compare two interface calls +def ifcall_cmp(a, b): + if a.args[0] != b.args[0]: + return cmp(a.args[0], b.args[0]) + return cmp(a.ifname, b.ifname) + +# Compare an two avrules or interface calls +def rule_cmp(a, b): + if isinstance(a, refpolicy.InterfaceCall): + if isinstance(b, refpolicy.InterfaceCall): + return ifcall_cmp(a, b) + else: + return id_set_cmp([a.args[0]], b.src_types) + else: + if isinstance(b, refpolicy.AVRule): + return avrule_cmp(a,b) + else: + return id_set_cmp(a.src_types, [b.args[0]]) + +def role_type_cmp(a, b): + return cmp(a.role, b.role) + +def sort_filter(module): + """Sort and group the output for readability. + """ + def sort_node(node): + c = [] + + # Module statement + for mod in node.module_declarations(): + c.append(mod) + c.append(refpolicy.Comment()) + + # Requires + for require in node.requires(): + c.append(require) + c.append(refpolicy.Comment()) + + # Rules + # + # We are going to group output by source type (which + # we assume is the first argument for interfaces). + rules = [] + rules.extend(node.avrules()) + rules.extend(node.interface_calls()) + rules.sort(rule_cmp) + + cur = None + sep_rules = [] + for rule in rules: + if isinstance(rule, refpolicy.InterfaceCall): + x = rule.args[0] + else: + x = util.first(rule.src_types) + + if cur != x: + if cur: + sep_rules.append(refpolicy.Comment()) + cur = x + comment = refpolicy.Comment() + comment.lines.append("============= %s ==============" % cur) + sep_rules.append(comment) + sep_rules.append(rule) + + c.extend(sep_rules) + + + ras = [] + ras.extend(node.role_types()) + ras.sort(role_type_cmp) + if len(ras): + comment = refpolicy.Comment() + comment.lines.append("============= ROLES ==============") + c.append(comment) + + + c.extend(ras) + + # Everything else + for child in node.children: + if child not in c: + c.append(child) + + node.children = c + + for node in module.nodes(): + sort_node(node) + + diff --git a/lib/python2.7/site-packages/sepolgen/policygen.py b/lib/python2.7/site-packages/sepolgen/policygen.py new file mode 100644 index 0000000..5f38577 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/policygen.py @@ -0,0 +1,402 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +classes and algorithms for the generation of SELinux policy. +""" + +import itertools +import textwrap + +import refpolicy +import objectmodel +import access +import interfaces +import matching +import selinux.audit2why as audit2why +try: + from setools import * +except: + pass + +# Constants for the level of explanation from the generation +# routines +NO_EXPLANATION = 0 +SHORT_EXPLANATION = 1 +LONG_EXPLANATION = 2 + +class PolicyGenerator: + """Generate a reference policy module from access vectors. + + PolicyGenerator generates a new reference policy module + or updates an existing module based on requested access + in the form of access vectors. + + It generates allow rules and optionally module require + statements and reference policy interfaces. By default + only allow rules are generated. The methods .set_gen_refpol + and .set_gen_requires turns on interface generation and + requires generation respectively. + + PolicyGenerator can also optionally add comments explaining + why a particular access was allowed based on the audit + messages that generated the access. The access vectors + passed in must have the .audit_msgs field set correctly + and .explain set to SHORT|LONG_EXPLANATION to enable this + feature. + + The module created by PolicyGenerator can be passed to + output.ModuleWriter to output a text representation. + """ + def __init__(self, module=None): + """Initialize a PolicyGenerator with an optional + existing module. + + If the module paramater is not None then access + will be added to the passed in module. Otherwise + a new reference policy module will be created. + """ + self.ifgen = None + self.explain = NO_EXPLANATION + self.gen_requires = False + if module: + self.moduel = module + else: + self.module = refpolicy.Module() + + self.dontaudit = False + + self.domains = None + def set_gen_refpol(self, if_set=None, perm_maps=None): + """Set whether reference policy interfaces are generated. + + To turn on interface generation pass in an interface set + to use for interface generation. To turn off interface + generation pass in None. + + If interface generation is enabled requires generation + will also be enabled. + """ + if if_set: + self.ifgen = InterfaceGenerator(if_set, perm_maps) + self.gen_requires = True + else: + self.ifgen = None + self.__set_module_style() + + + def set_gen_requires(self, status=True): + """Set whether module requires are generated. + + Passing in true will turn on requires generation and + False will disable generation. If requires generation is + disabled interface generation will also be disabled and + can only be re-enabled via .set_gen_refpol. + """ + self.gen_requires = status + + def set_gen_explain(self, explain=SHORT_EXPLANATION): + """Set whether access is explained. + """ + self.explain = explain + + def set_gen_dontaudit(self, dontaudit): + self.dontaudit = dontaudit + + def __set_module_style(self): + if self.ifgen: + refpolicy = True + else: + refpolicy = False + for mod in self.module.module_declarations(): + mod.refpolicy = refpolicy + + def set_module_name(self, name, version="1.0"): + """Set the name of the module and optionally the version. + """ + # find an existing module declaration + m = None + for mod in self.module.module_declarations(): + m = mod + if not m: + m = refpolicy.ModuleDeclaration() + self.module.children.insert(0, m) + m.name = name + m.version = version + if self.ifgen: + m.refpolicy = True + else: + m.refpolicy = False + + def get_module(self): + # Generate the requires + if self.gen_requires: + gen_requires(self.module) + + """Return the generated module""" + return self.module + + def __add_allow_rules(self, avs): + for av in avs: + rule = refpolicy.AVRule(av) + if self.dontaudit: + rule.rule_type = rule.DONTAUDIT + rule.comment = "" + if self.explain: + rule.comment = str(refpolicy.Comment(explain_access(av, verbosity=self.explain))) + if av.type == audit2why.ALLOW: + rule.comment += "\n#!!!! This avc is allowed in the current policy" + if av.type == audit2why.DONTAUDIT: + rule.comment += "\n#!!!! This avc has a dontaudit rule in the current policy" + + if av.type == audit2why.BOOLEAN: + if len(av.data) > 1: + rule.comment += "\n#!!!! This avc can be allowed using one of the these booleans:\n# %s" % ", ".join(map(lambda x: x[0], av.data)) + else: + rule.comment += "\n#!!!! This avc can be allowed using the boolean '%s'" % av.data[0][0] + + if av.type == audit2why.CONSTRAINT: + rule.comment += "\n#!!!! This avc is a constraint violation. You would need to modify the attributes of either the source or target types to allow this access." + rule.comment += "\n#Constraint rule: " + rule.comment += "\n\t" + av.data[0] + for reason in av.data[1:]: + rule.comment += "\n#\tPossible cause is the source %s and target %s are different." % reason + + try: + if ( av.type == audit2why.TERULE and + "write" in av.perms and + ( "dir" in av.obj_class or "open" in av.perms )): + if not self.domains: + self.domains = seinfo(ATTRIBUTE, name="domain")[0]["types"] + types=[] + + for i in map(lambda x: x[TCONTEXT], sesearch([ALLOW], {SCONTEXT: av.src_type, CLASS: av.obj_class, PERMS: av.perms})): + if i not in self.domains: + types.append(i) + if len(types) == 1: + rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following type:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types)) + elif len(types) >= 1: + rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following types:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types)) + except: + pass + self.module.children.append(rule) + + + def add_access(self, av_set): + """Add the access from the access vector set to this + module. + """ + # Use the interface generator to split the access + # into raw allow rules and interfaces. After this + # a will contain a list of access that should be + # used as raw allow rules and the interfaces will + # be added to the module. + if self.ifgen: + raw_allow, ifcalls = self.ifgen.gen(av_set, self.explain) + self.module.children.extend(ifcalls) + else: + raw_allow = av_set + + # Generate the raw allow rules from the filtered list + self.__add_allow_rules(raw_allow) + + def add_role_types(self, role_type_set): + for role_type in role_type_set: + self.module.children.append(role_type) + +def explain_access(av, ml=None, verbosity=SHORT_EXPLANATION): + """Explain why a policy statement was generated. + + Return a string containing a text explanation of + why a policy statement was generated. The string is + commented and wrapped and can be directly inserted + into a policy. + + Params: + av - access vector representing the access. Should + have .audit_msgs set appropriately. + verbosity - the amount of explanation provided. Should + be set to NO_EXPLANATION, SHORT_EXPLANATION, or + LONG_EXPLANATION. + Returns: + list of strings - strings explaining the access or an empty + string if verbosity=NO_EXPLANATION or there is not sufficient + information to provide an explanation. + """ + s = [] + + def explain_interfaces(): + if not ml: + return + s.append(" Interface options:") + for match in ml.all(): + ifcall = call_interface(match.interface, ml.av) + s.append(' %s # [%d]' % (ifcall.to_string(), match.dist)) + + + # Format the raw audit data to explain why the + # access was requested - either long or short. + if verbosity == LONG_EXPLANATION: + for msg in av.audit_msgs: + s.append(' %s' % msg.header) + s.append(' scontext="%s" tcontext="%s"' % + (str(msg.scontext), str(msg.tcontext))) + s.append(' class="%s" perms="%s"' % + (msg.tclass, refpolicy.list_to_space_str(msg.accesses))) + s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) + s.extend(textwrap.wrap('message="' + msg.message + '"', 80, initial_indent=" ", + subsequent_indent=" ")) + explain_interfaces() + elif verbosity: + s.append(' src="%s" tgt="%s" class="%s", perms="%s"' % + (av.src_type, av.tgt_type, av.obj_class, av.perms.to_space_str())) + # For the short display we are only going to use the additional information + # from the first audit message. For the vast majority of cases this info + # will always be the same anyway. + if len(av.audit_msgs) > 0: + msg = av.audit_msgs[0] + s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) + explain_interfaces() + return s + +def param_comp(a, b): + return cmp(b.num, a.num) + +def call_interface(interface, av): + params = [] + args = [] + + params.extend(interface.params.values()) + params.sort(param_comp) + + ifcall = refpolicy.InterfaceCall() + ifcall.ifname = interface.name + + for i in range(len(params)): + if params[i].type == refpolicy.SRC_TYPE: + ifcall.args.append(av.src_type) + elif params[i].type == refpolicy.TGT_TYPE: + ifcall.args.append(av.tgt_type) + elif params[i].type == refpolicy.OBJ_CLASS: + ifcall.args.append(av.obj_class) + else: + print params[i].type + assert(0) + + assert(len(ifcall.args) > 0) + + return ifcall + +class InterfaceGenerator: + def __init__(self, ifs, perm_maps=None): + self.ifs = ifs + self.hack_check_ifs(ifs) + self.matcher = matching.AccessMatcher(perm_maps) + self.calls = [] + + def hack_check_ifs(self, ifs): + # FIXME: Disable interfaces we can't call - this is a hack. + # Because we don't handle roles, multiple paramaters, etc., + # etc., we must make certain we can actually use a returned + # interface. + for x in ifs.interfaces.values(): + params = [] + params.extend(x.params.values()) + params.sort(param_comp) + for i in range(len(params)): + # Check that the paramater position matches + # the number (e.g., $1 is the first arg). This + # will fail if the parser missed something. + if (i + 1) != params[i].num: + x.enabled = False + break + # Check that we can handle the param type (currently excludes + # roles. + if params[i].type not in [refpolicy.SRC_TYPE, refpolicy.TGT_TYPE, + refpolicy.OBJ_CLASS]: + x.enabled = False + break + + def gen(self, avs, verbosity): + raw_av = self.match(avs) + ifcalls = [] + for ml in self.calls: + ifcall = call_interface(ml.best().interface, ml.av) + if verbosity: + ifcall.comment = refpolicy.Comment(explain_access(ml.av, ml, verbosity)) + ifcalls.append((ifcall, ml)) + + d = [] + for ifcall, ifs in ifcalls: + found = False + for o_ifcall in d: + if o_ifcall.matches(ifcall): + if o_ifcall.comment and ifcall.comment: + o_ifcall.comment.merge(ifcall.comment) + found = True + if not found: + d.append(ifcall) + + return (raw_av, d) + + + def match(self, avs): + raw_av = [] + for av in avs: + ans = matching.MatchList() + self.matcher.search_ifs(self.ifs, av, ans) + if len(ans): + self.calls.append(ans) + else: + raw_av.append(av) + + return raw_av + + +def gen_requires(module): + """Add require statements to the module. + """ + def collect_requires(node): + r = refpolicy.Require() + for avrule in node.avrules(): + r.types.update(avrule.src_types) + r.types.update(avrule.tgt_types) + for obj in avrule.obj_classes: + r.add_obj_class(obj, avrule.perms) + + for ifcall in node.interface_calls(): + for arg in ifcall.args: + # FIXME - handle non-type arguments when we + # can actually figure those out. + r.types.add(arg) + + for role_type in node.role_types(): + r.roles.add(role_type.role) + r.types.update(role_type.types) + + r.types.discard("self") + + node.children.insert(0, r) + + # FUTURE - this is untested on modules with any sort of + # nesting + for node in module.nodes(): + collect_requires(node) + + diff --git a/lib/python2.7/site-packages/sepolgen/refparser.py b/lib/python2.7/site-packages/sepolgen/refparser.py new file mode 100644 index 0000000..83542d3 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/refparser.py @@ -0,0 +1,1128 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006-2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +# OVERVIEW +# +# +# This is a parser for the refpolicy policy "language" - i.e., the +# normal SELinux policy language plus the refpolicy style M4 macro +# constructs on top of that base language. This parser is primarily +# aimed at parsing the policy headers in order to create an abstract +# policy representation suitable for generating policy. +# +# Both the lexer and parser are included in this file. The are implemented +# using the Ply library (included with sepolgen). + +import sys +import os +import re +import traceback + +import refpolicy +import access +import defaults + +import lex +import yacc + +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +# +# lexer +# +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +tokens = ( + # basic tokens, punctuation + 'TICK', + 'SQUOTE', + 'OBRACE', + 'CBRACE', + 'SEMI', + 'COLON', + 'OPAREN', + 'CPAREN', + 'COMMA', + 'MINUS', + 'TILDE', + 'ASTERISK', + 'AMP', + 'BAR', + 'EXPL', + 'EQUAL', + 'FILENAME', + 'IDENTIFIER', + 'NUMBER', + 'PATH', + 'IPV6_ADDR', + # reserved words + # module + 'MODULE', + 'POLICY_MODULE', + 'REQUIRE', + # flask + 'SID', + 'GENFSCON', + 'FS_USE_XATTR', + 'FS_USE_TRANS', + 'FS_USE_TASK', + 'PORTCON', + 'NODECON', + 'NETIFCON', + 'PIRQCON', + 'IOMEMCON', + 'IOPORTCON', + 'PCIDEVICECON', + 'DEVICETREECON', + # object classes + 'CLASS', + # types and attributes + 'TYPEATTRIBUTE', + 'ROLEATTRIBUTE', + 'TYPE', + 'ATTRIBUTE', + 'ATTRIBUTE_ROLE', + 'ALIAS', + 'TYPEALIAS', + # conditional policy + 'BOOL', + 'TRUE', + 'FALSE', + 'IF', + 'ELSE', + # users and roles + 'ROLE', + 'TYPES', + # rules + 'ALLOW', + 'DONTAUDIT', + 'AUDITALLOW', + 'NEVERALLOW', + 'PERMISSIVE', + 'TYPE_TRANSITION', + 'TYPE_CHANGE', + 'TYPE_MEMBER', + 'RANGE_TRANSITION', + 'ROLE_TRANSITION', + # refpolicy keywords + 'OPT_POLICY', + 'INTERFACE', + 'TUNABLE_POLICY', + 'GEN_REQ', + 'TEMPLATE', + 'GEN_CONTEXT', + # m4 + 'IFELSE', + 'IFDEF', + 'IFNDEF', + 'DEFINE' + ) + +# All reserved keywords - see t_IDENTIFIER for how these are matched in +# the lexer. +reserved = { + # module + 'module' : 'MODULE', + 'policy_module' : 'POLICY_MODULE', + 'require' : 'REQUIRE', + # flask + 'sid' : 'SID', + 'genfscon' : 'GENFSCON', + 'fs_use_xattr' : 'FS_USE_XATTR', + 'fs_use_trans' : 'FS_USE_TRANS', + 'fs_use_task' : 'FS_USE_TASK', + 'portcon' : 'PORTCON', + 'nodecon' : 'NODECON', + 'netifcon' : 'NETIFCON', + 'pirqcon' : 'PIRQCON', + 'iomemcon' : 'IOMEMCON', + 'ioportcon' : 'IOPORTCON', + 'pcidevicecon' : 'PCIDEVICECON', + 'devicetreecon' : 'DEVICETREECON', + # object classes + 'class' : 'CLASS', + # types and attributes + 'typeattribute' : 'TYPEATTRIBUTE', + 'roleattribute' : 'ROLEATTRIBUTE', + 'type' : 'TYPE', + 'attribute' : 'ATTRIBUTE', + 'attribute_role' : 'ATTRIBUTE_ROLE', + 'alias' : 'ALIAS', + 'typealias' : 'TYPEALIAS', + # conditional policy + 'bool' : 'BOOL', + 'true' : 'TRUE', + 'false' : 'FALSE', + 'if' : 'IF', + 'else' : 'ELSE', + # users and roles + 'role' : 'ROLE', + 'types' : 'TYPES', + # rules + 'allow' : 'ALLOW', + 'dontaudit' : 'DONTAUDIT', + 'auditallow' : 'AUDITALLOW', + 'neverallow' : 'NEVERALLOW', + 'permissive' : 'PERMISSIVE', + 'type_transition' : 'TYPE_TRANSITION', + 'type_change' : 'TYPE_CHANGE', + 'type_member' : 'TYPE_MEMBER', + 'range_transition' : 'RANGE_TRANSITION', + 'role_transition' : 'ROLE_TRANSITION', + # refpolicy keywords + 'optional_policy' : 'OPT_POLICY', + 'interface' : 'INTERFACE', + 'tunable_policy' : 'TUNABLE_POLICY', + 'gen_require' : 'GEN_REQ', + 'template' : 'TEMPLATE', + 'gen_context' : 'GEN_CONTEXT', + # M4 + 'ifelse' : 'IFELSE', + 'ifndef' : 'IFNDEF', + 'ifdef' : 'IFDEF', + 'define' : 'DEFINE' + } + +# The ply lexer allows definition of tokens in 2 ways: regular expressions +# or functions. + +# Simple regex tokens +t_TICK = r'\`' +t_SQUOTE = r'\'' +t_OBRACE = r'\{' +t_CBRACE = r'\}' +# This will handle spurios extra ';' via the + +t_SEMI = r'\;+' +t_COLON = r'\:' +t_OPAREN = r'\(' +t_CPAREN = r'\)' +t_COMMA = r'\,' +t_MINUS = r'\-' +t_TILDE = r'\~' +t_ASTERISK = r'\*' +t_AMP = r'\&' +t_BAR = r'\|' +t_EXPL = r'\!' +t_EQUAL = r'\=' +t_NUMBER = r'[0-9\.]+' +t_PATH = r'/[a-zA-Z0-9)_\.\*/]*' +#t_IPV6_ADDR = r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]{0,4}:)*' + +# Ignore whitespace - this is a special token for ply that more efficiently +# ignores uninteresting tokens. +t_ignore = " \t" + +# More complex tokens +def t_IPV6_ADDR(t): + r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]|:)*' + # This is a function simply to force it sooner into + # the regex list + return t + +def t_m4comment(t): + r'dnl.*\n' + # Ignore all comments + t.lexer.lineno += 1 + +def t_refpolicywarn1(t): + r'define.*refpolicywarn\(.*\n' + # Ignore refpolicywarn statements - they sometimes + # contain text that we can't parse. + t.skip(1) + +def t_refpolicywarn(t): + r'refpolicywarn\(.*\n' + # Ignore refpolicywarn statements - they sometimes + # contain text that we can't parse. + t.lexer.lineno += 1 + +def t_IDENTIFIER(t): + r'[a-zA-Z_\$][a-zA-Z0-9_\-\+\.\$\*~]*' + # Handle any keywords + t.type = reserved.get(t.value,'IDENTIFIER') + return t + +def t_FILENAME(t): + r'\"[a-zA-Z0-9_\-\+\.\$\*~ :]+\"' + # Handle any keywords + t.type = reserved.get(t.value,'FILENAME') + return t + +def t_comment(t): + r'\#.*\n' + # Ignore all comments + t.lexer.lineno += 1 + +def t_error(t): + print "Illegal character '%s'" % t.value[0] + t.skip(1) + +def t_newline(t): + r'\n+' + t.lexer.lineno += len(t.value) + +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +# +# Parser +# +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +# Global data used during parsing - making it global is easier than +# passing the state through the parsing functions. + +# m is the top-level data structure (stands for modules). +m = None +# error is either None (indicating no error) or a string error message. +error = None +parse_file = "" +# spt is the support macros (e.g., obj/perm sets) - it is an instance of +# refpolicy.SupportMacros and should always be present during parsing +# though it may not contain any macros. +spt = None +success = True + +# utilities +def collect(stmts, parent, val=None): + if stmts is None: + return + for s in stmts: + if s is None: + continue + s.parent = parent + if val is not None: + parent.children.insert(0, (val, s)) + else: + parent.children.insert(0, s) + +def expand(ids, s): + for id in ids: + if spt.has_key(id): + s.update(spt.by_name(id)) + else: + s.add(id) + +# Top-level non-terminal +def p_statements(p): + '''statements : statement + | statements statement + | empty + ''' + if len(p) == 2 and p[1]: + m.children.append(p[1]) + elif len(p) > 2 and p[2]: + m.children.append(p[2]) + +def p_statement(p): + '''statement : interface + | template + | obj_perm_set + | policy + | policy_module_stmt + | module_stmt + ''' + p[0] = p[1] + +def p_empty(p): + 'empty :' + pass + +# +# Reference policy language constructs +# + +# This is for the policy module statement (e.g., policy_module(foo,1.2.0)). +# We have a separate terminal for either the basic language module statement +# and interface calls to make it easier to identifier. +def p_policy_module_stmt(p): + 'policy_module_stmt : POLICY_MODULE OPAREN IDENTIFIER COMMA NUMBER CPAREN' + m = refpolicy.ModuleDeclaration() + m.name = p[3] + m.version = p[5] + m.refpolicy = True + p[0] = m + +def p_interface(p): + '''interface : INTERFACE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + x = refpolicy.Interface(p[4]) + collect(p[8], x) + p[0] = x + +def p_template(p): + '''template : TEMPLATE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + | DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + x = refpolicy.Template(p[4]) + collect(p[8], x) + p[0] = x + +def p_define(p): + '''define : DEFINE OPAREN TICK IDENTIFIER SQUOTE CPAREN''' + # This is for defining single M4 values (to be used later in ifdef statements). + # Example: define(`sulogin_no_pam'). We don't currently do anything with these + # but we should in the future when we correctly resolve ifdef statements. + p[0] = None + +def p_interface_stmts(p): + '''interface_stmts : policy + | interface_stmts policy + | empty + ''' + if len(p) == 2 and p[1]: + p[0] = p[1] + elif len(p) > 2: + if not p[1]: + if p[2]: + p[0] = p[2] + elif not p[2]: + p[0] = p[1] + else: + p[0] = p[1] + p[2] + +def p_optional_policy(p): + '''optional_policy : OPT_POLICY OPAREN TICK interface_stmts SQUOTE CPAREN + | OPT_POLICY OPAREN TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + o = refpolicy.OptionalPolicy() + collect(p[4], o, val=True) + if len(p) > 7: + collect(p[8], o, val=False) + p[0] = [o] + +def p_tunable_policy(p): + '''tunable_policy : TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + | TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + x = refpolicy.TunablePolicy() + x.cond_expr = p[4] + collect(p[8], x, val=True) + if len(p) > 11: + collect(p[12], x, val=False) + p[0] = [x] + +def p_ifelse(p): + '''ifelse : IFELSE OPAREN TICK IDENTIFIER SQUOTE COMMA COMMA TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + | IFELSE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + ''' +# x = refpolicy.IfDef(p[4]) +# v = True +# collect(p[8], x, val=v) +# if len(p) > 12: +# collect(p[12], x, val=False) +# p[0] = [x] + pass + + +def p_ifdef(p): + '''ifdef : IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + | IFNDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + | IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + ''' + x = refpolicy.IfDef(p[4]) + if p[1] == 'ifdef': + v = True + else: + v = False + collect(p[8], x, val=v) + if len(p) > 12: + collect(p[12], x, val=False) + p[0] = [x] + +def p_interface_call(p): + '''interface_call : IDENTIFIER OPAREN interface_call_param_list CPAREN + | IDENTIFIER OPAREN CPAREN + | IDENTIFIER OPAREN interface_call_param_list CPAREN SEMI''' + # Allow spurious semi-colons at the end of interface calls + i = refpolicy.InterfaceCall(ifname=p[1]) + if len(p) > 4: + i.args.extend(p[3]) + p[0] = i + +def p_interface_call_param(p): + '''interface_call_param : IDENTIFIER + | IDENTIFIER MINUS IDENTIFIER + | nested_id_set + | TRUE + | FALSE + | FILENAME + ''' + # Intentionally let single identifiers pass through + # List means set, non-list identifier + if len(p) == 2: + p[0] = p[1] + else: + p[0] = [p[1], "-" + p[3]] + +def p_interface_call_param_list(p): + '''interface_call_param_list : interface_call_param + | interface_call_param_list COMMA interface_call_param + ''' + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = p[1] + [p[3]] + + +def p_obj_perm_set(p): + 'obj_perm_set : DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK names SQUOTE CPAREN' + s = refpolicy.ObjPermSet(p[4]) + s.perms = p[8] + p[0] = s + +# +# Basic SELinux policy language +# + +def p_policy(p): + '''policy : policy_stmt + | optional_policy + | tunable_policy + | ifdef + | ifelse + | conditional + ''' + p[0] = p[1] + +def p_policy_stmt(p): + '''policy_stmt : gen_require + | avrule_def + | typerule_def + | typeattribute_def + | roleattribute_def + | interface_call + | role_def + | role_allow + | permissive + | type_def + | typealias_def + | attribute_def + | attribute_role_def + | range_transition_def + | role_transition_def + | bool + | define + | initial_sid + | genfscon + | fs_use + | portcon + | nodecon + | netifcon + | pirqcon + | iomemcon + | ioportcon + | pcidevicecon + | devicetreecon + ''' + if p[1]: + p[0] = [p[1]] + +def p_module_stmt(p): + 'module_stmt : MODULE IDENTIFIER NUMBER SEMI' + m = refpolicy.ModuleDeclaration() + m.name = p[2] + m.version = p[3] + m.refpolicy = False + p[0] = m + +def p_gen_require(p): + '''gen_require : GEN_REQ OPAREN TICK requires SQUOTE CPAREN + | REQUIRE OBRACE requires CBRACE''' + # We ignore the require statements - they are redundant data from our point-of-view. + # Checkmodule will verify them later anyway so we just assume that they match what + # is in the rest of the interface. + pass + +def p_requires(p): + '''requires : require + | requires require + | ifdef + | requires ifdef + ''' + pass + +def p_require(p): + '''require : TYPE comma_list SEMI + | ROLE comma_list SEMI + | ATTRIBUTE comma_list SEMI + | ATTRIBUTE_ROLE comma_list SEMI + | CLASS comma_list SEMI + | BOOL comma_list SEMI + ''' + pass + +def p_security_context(p): + '''security_context : IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER + | IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER COLON mls_range_def''' + # This will likely need some updates to handle complex levels + s = refpolicy.SecurityContext() + s.user = p[1] + s.role = p[3] + s.type = p[5] + if len(p) > 6: + s.level = p[7] + + p[0] = s + +def p_gen_context(p): + '''gen_context : GEN_CONTEXT OPAREN security_context COMMA mls_range_def CPAREN + ''' + # We actually store gen_context statements in a SecurityContext + # object - it knows how to output either a bare context or a + # gen_context statement. + s = p[3] + s.level = p[5] + + p[0] = s + +def p_context(p): + '''context : security_context + | gen_context + ''' + p[0] = p[1] + +def p_initial_sid(p): + '''initial_sid : SID IDENTIFIER context''' + s = refpolicy.InitialSid() + s.name = p[2] + s.context = p[3] + p[0] = s + +def p_genfscon(p): + '''genfscon : GENFSCON IDENTIFIER PATH context''' + + g = refpolicy.GenfsCon() + g.filesystem = p[2] + g.path = p[3] + g.context = p[4] + + p[0] = g + +def p_fs_use(p): + '''fs_use : FS_USE_XATTR IDENTIFIER context SEMI + | FS_USE_TASK IDENTIFIER context SEMI + | FS_USE_TRANS IDENTIFIER context SEMI + ''' + f = refpolicy.FilesystemUse() + if p[1] == "fs_use_xattr": + f.type = refpolicy.FilesystemUse.XATTR + elif p[1] == "fs_use_task": + f.type = refpolicy.FilesystemUse.TASK + elif p[1] == "fs_use_trans": + f.type = refpolicy.FilesystemUse.TRANS + + f.filesystem = p[2] + f.context = p[3] + + p[0] = f + +def p_portcon(p): + '''portcon : PORTCON IDENTIFIER NUMBER context + | PORTCON IDENTIFIER NUMBER MINUS NUMBER context''' + c = refpolicy.PortCon() + c.port_type = p[2] + if len(p) == 5: + c.port_number = p[3] + c.context = p[4] + else: + c.port_number = p[3] + "-" + p[4] + c.context = p[5] + + p[0] = c + +def p_nodecon(p): + '''nodecon : NODECON NUMBER NUMBER context + | NODECON IPV6_ADDR IPV6_ADDR context + ''' + n = refpolicy.NodeCon() + n.start = p[2] + n.end = p[3] + n.context = p[4] + + p[0] = n + +def p_netifcon(p): + 'netifcon : NETIFCON IDENTIFIER context context' + n = refpolicy.NetifCon() + n.interface = p[2] + n.interface_context = p[3] + n.packet_context = p[4] + + p[0] = n + +def p_pirqcon(p): + 'pirqcon : PIRQCON NUMBER context' + c = refpolicy.PirqCon() + c.pirq_number = p[2] + c.context = p[3] + + p[0] = c + +def p_iomemcon(p): + '''iomemcon : IOMEMCON NUMBER context + | IOMEMCON NUMBER MINUS NUMBER context''' + c = refpolicy.IomemCon() + if len(p) == 4: + c.device_mem = p[2] + c.context = p[3] + else: + c.device_mem = p[2] + "-" + p[3] + c.context = p[4] + + p[0] = c + +def p_ioportcon(p): + '''ioportcon : IOPORTCON NUMBER context + | IOPORTCON NUMBER MINUS NUMBER context''' + c = refpolicy.IoportCon() + if len(p) == 4: + c.ioport = p[2] + c.context = p[3] + else: + c.ioport = p[2] + "-" + p[3] + c.context = p[4] + + p[0] = c + +def p_pcidevicecon(p): + 'pcidevicecon : PCIDEVICECON NUMBER context' + c = refpolicy.PciDeviceCon() + c.device = p[2] + c.context = p[3] + + p[0] = c + +def p_devicetreecon(p): + 'devicetreecon : DEVICETREECON NUMBER context' + c = refpolicy.DevicetTeeCon() + c.path = p[2] + c.context = p[3] + + p[0] = c + +def p_mls_range_def(p): + '''mls_range_def : mls_level_def MINUS mls_level_def + | mls_level_def + ''' + p[0] = p[1] + if len(p) > 2: + p[0] = p[0] + "-" + p[3] + +def p_mls_level_def(p): + '''mls_level_def : IDENTIFIER COLON comma_list + | IDENTIFIER + ''' + p[0] = p[1] + if len(p) > 2: + p[0] = p[0] + ":" + ",".join(p[3]) + +def p_type_def(p): + '''type_def : TYPE IDENTIFIER COMMA comma_list SEMI + | TYPE IDENTIFIER SEMI + | TYPE IDENTIFIER ALIAS names SEMI + | TYPE IDENTIFIER ALIAS names COMMA comma_list SEMI + ''' + t = refpolicy.Type(p[2]) + if len(p) == 6: + if p[3] == ',': + t.attributes.update(p[4]) + else: + t.aliases = p[4] + elif len(p) > 4: + t.aliases = p[4] + if len(p) == 8: + t.attributes.update(p[6]) + p[0] = t + +def p_attribute_def(p): + 'attribute_def : ATTRIBUTE IDENTIFIER SEMI' + a = refpolicy.Attribute(p[2]) + p[0] = a + +def p_attribute_role_def(p): + 'attribute_role_def : ATTRIBUTE_ROLE IDENTIFIER SEMI' + a = refpolicy.Attribute_Role(p[2]) + p[0] = a + +def p_typealias_def(p): + 'typealias_def : TYPEALIAS IDENTIFIER ALIAS names SEMI' + t = refpolicy.TypeAlias() + t.type = p[2] + t.aliases = p[4] + p[0] = t + +def p_role_def(p): + '''role_def : ROLE IDENTIFIER TYPES comma_list SEMI + | ROLE IDENTIFIER SEMI''' + r = refpolicy.Role() + r.role = p[2] + if len(p) > 4: + r.types.update(p[4]) + p[0] = r + +def p_role_allow(p): + 'role_allow : ALLOW names names SEMI' + r = refpolicy.RoleAllow() + r.src_roles = p[2] + r.tgt_roles = p[3] + p[0] = r + +def p_permissive(p): + 'permissive : PERMISSIVE names SEMI' + t.skip(1) + +def p_avrule_def(p): + '''avrule_def : ALLOW names names COLON names names SEMI + | DONTAUDIT names names COLON names names SEMI + | AUDITALLOW names names COLON names names SEMI + | NEVERALLOW names names COLON names names SEMI + ''' + a = refpolicy.AVRule() + if p[1] == 'dontaudit': + a.rule_type = refpolicy.AVRule.DONTAUDIT + elif p[1] == 'auditallow': + a.rule_type = refpolicy.AVRule.AUDITALLOW + elif p[1] == 'neverallow': + a.rule_type = refpolicy.AVRule.NEVERALLOW + a.src_types = p[2] + a.tgt_types = p[3] + a.obj_classes = p[5] + a.perms = p[6] + p[0] = a + +def p_typerule_def(p): + '''typerule_def : TYPE_TRANSITION names names COLON names IDENTIFIER SEMI + | TYPE_TRANSITION names names COLON names IDENTIFIER FILENAME SEMI + | TYPE_TRANSITION names names COLON names IDENTIFIER IDENTIFIER SEMI + | TYPE_CHANGE names names COLON names IDENTIFIER SEMI + | TYPE_MEMBER names names COLON names IDENTIFIER SEMI + ''' + t = refpolicy.TypeRule() + if p[1] == 'type_change': + t.rule_type = refpolicy.TypeRule.TYPE_CHANGE + elif p[1] == 'type_member': + t.rule_type = refpolicy.TypeRule.TYPE_MEMBER + t.src_types = p[2] + t.tgt_types = p[3] + t.obj_classes = p[5] + t.dest_type = p[6] + t.file_name = p[7] + p[0] = t + +def p_bool(p): + '''bool : BOOL IDENTIFIER TRUE SEMI + | BOOL IDENTIFIER FALSE SEMI''' + b = refpolicy.Bool() + b.name = p[2] + if p[3] == "true": + b.state = True + else: + b.state = False + p[0] = b + +def p_conditional(p): + ''' conditional : IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE + | IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE ELSE OBRACE interface_stmts CBRACE + ''' + c = refpolicy.Conditional() + c.cond_expr = p[3] + collect(p[6], c, val=True) + if len(p) > 8: + collect(p[10], c, val=False) + p[0] = [c] + +def p_typeattribute_def(p): + '''typeattribute_def : TYPEATTRIBUTE IDENTIFIER comma_list SEMI''' + t = refpolicy.TypeAttribute() + t.type = p[2] + t.attributes.update(p[3]) + p[0] = t + +def p_roleattribute_def(p): + '''roleattribute_def : ROLEATTRIBUTE IDENTIFIER comma_list SEMI''' + t = refpolicy.RoleAttribute() + t.role = p[2] + t.roleattributes.update(p[3]) + p[0] = t + +def p_range_transition_def(p): + '''range_transition_def : RANGE_TRANSITION names names COLON names mls_range_def SEMI + | RANGE_TRANSITION names names names SEMI''' + pass + +def p_role_transition_def(p): + '''role_transition_def : ROLE_TRANSITION names names names SEMI''' + pass + +def p_cond_expr(p): + '''cond_expr : IDENTIFIER + | EXPL cond_expr + | cond_expr AMP AMP cond_expr + | cond_expr BAR BAR cond_expr + | cond_expr EQUAL EQUAL cond_expr + | cond_expr EXPL EQUAL cond_expr + ''' + l = len(p) + if l == 2: + p[0] = [p[1]] + elif l == 3: + p[0] = [p[1]] + p[2] + else: + p[0] = p[1] + [p[2] + p[3]] + p[4] + + +# +# Basic terminals +# + +# Identifiers and lists of identifiers. These must +# be handled somewhat gracefully. Names returns an IdSet and care must +# be taken that this is _assigned_ to an object to correctly update +# all of the flags (as opposed to using update). The other terminals +# return list - this is to preserve ordering if it is important for +# parsing (for example, interface_call must retain the ordering). Other +# times the list should be used to update an IdSet. + +def p_names(p): + '''names : identifier + | nested_id_set + | asterisk + | TILDE identifier + | TILDE nested_id_set + | IDENTIFIER MINUS IDENTIFIER + ''' + s = refpolicy.IdSet() + if len(p) < 3: + expand(p[1], s) + elif len(p) == 3: + expand(p[2], s) + s.compliment = True + else: + expand([p[1]]) + s.add("-" + p[3]) + p[0] = s + +def p_identifier(p): + 'identifier : IDENTIFIER' + p[0] = [p[1]] + +def p_asterisk(p): + 'asterisk : ASTERISK' + p[0] = [p[1]] + +def p_nested_id_set(p): + '''nested_id_set : OBRACE nested_id_list CBRACE + ''' + p[0] = p[2] + +def p_nested_id_list(p): + '''nested_id_list : nested_id_element + | nested_id_list nested_id_element + ''' + if len(p) == 2: + p[0] = p[1] + else: + p[0] = p[1] + p[2] + +def p_nested_id_element(p): + '''nested_id_element : identifier + | MINUS IDENTIFIER + | nested_id_set + ''' + if len(p) == 2: + p[0] = p[1] + else: + # For now just leave the '-' + str = "-" + p[2] + p[0] = [str] + +def p_comma_list(p): + '''comma_list : nested_id_list + | comma_list COMMA nested_id_list + ''' + if len(p) > 2: + p[1] = p[1] + p[3] + p[0] = p[1] + +def p_optional_semi(p): + '''optional_semi : SEMI + | empty''' + pass + + +# +# Interface to the parser +# + +def p_error(tok): + global error, parse_file, success, parser + error = "%s: Syntax error on line %d %s [type=%s]" % (parse_file, tok.lineno, tok.value, tok.type) + print error + success = False + +def prep_spt(spt): + if not spt: + return { } + map = {} + for x in spt: + map[x.name] = x + +parser = None +lexer = None +def create_globals(module, support, debug): + global parser, lexer, m, spt + + if not parser: + lexer = lex.lex() + parser = yacc.yacc(method="LALR", debug=debug, write_tables=0) + + if module is not None: + m = module + else: + m = refpolicy.Module() + + if not support: + spt = refpolicy.SupportMacros() + else: + spt = support + +def parse(text, module=None, support=None, debug=False): + create_globals(module, support, debug) + global error, parser, lexer, success + + success = True + + try: + parser.parse(text, debug=debug, lexer=lexer) + except Exception, e: + parser = None + lexer = None + error = "internal parser error: %s" % str(e) + "\n" + traceback.format_exc() + + if not success: + # force the parser and lexer to be rebuilt - we have some problems otherwise + parser = None + msg = 'could not parse text: "%s"' % error + raise ValueError(msg) + return m + +def list_headers(root): + modules = [] + support_macros = None + + for dirpath, dirnames, filenames in os.walk(root): + for name in filenames: + modname = os.path.splitext(name) + filename = os.path.join(dirpath, name) + + if modname[1] == '.spt': + if name == "obj_perm_sets.spt": + support_macros = filename + elif len(re.findall("patterns", modname[0])): + modules.append((modname[0], filename)) + elif modname[1] == '.if': + modules.append((modname[0], filename)) + + return (modules, support_macros) + + +def parse_headers(root, output=None, expand=True, debug=False): + import util + + headers = refpolicy.Headers() + + modules = [] + support_macros = None + + if os.path.isfile(root): + name = os.path.split(root)[1] + if name == '': + raise ValueError("Invalid file name %s" % root) + modname = os.path.splitext(name) + modules.append((modname[0], root)) + all_modules, support_macros = list_headers(defaults.headers()) + else: + modules, support_macros = list_headers(root) + + if expand and not support_macros: + raise ValueError("could not find support macros (obj_perm_sets.spt)") + + def o(msg): + if output: + output.write(msg) + + def parse_file(f, module, spt=None): + global parse_file + if debug: + o("parsing file %s\n" % f) + try: + fd = open(f) + txt = fd.read() + fd.close() + parse_file = f + parse(txt, module, spt, debug) + except IOError, e: + return + except ValueError, e: + raise ValueError("error parsing file %s: %s" % (f, str(e))) + + spt = None + if support_macros: + o("Parsing support macros (%s): " % support_macros) + spt = refpolicy.SupportMacros() + parse_file(support_macros, spt) + + headers.children.append(spt) + + # FIXME: Total hack - add in can_exec rather than parse the insanity + # of misc_macros. We are just going to pretend that this is an interface + # to make the expansion work correctly. + can_exec = refpolicy.Interface("can_exec") + av = access.AccessVector(["$1","$2","file","execute_no_trans","open", "read", + "getattr","lock","execute","ioctl"]) + + can_exec.children.append(refpolicy.AVRule(av)) + headers.children.append(can_exec) + + o("done.\n") + + if output and not debug: + status = util.ConsoleProgressBar(sys.stdout, steps=len(modules)) + status.start("Parsing interface files") + + failures = [] + for x in modules: + m = refpolicy.Module() + m.name = x[0] + try: + if expand: + parse_file(x[1], m, spt) + else: + parse_file(x[1], m) + except ValueError, e: + o(str(e) + "\n") + failures.append(x[1]) + continue + + headers.children.append(m) + if output and not debug: + status.step() + + if len(failures): + o("failed to parse some headers: %s" % ", ".join(failures)) + + return headers diff --git a/lib/python2.7/site-packages/sepolgen/refpolicy.py b/lib/python2.7/site-packages/sepolgen/refpolicy.py new file mode 100644 index 0000000..b8ed5c1 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/refpolicy.py @@ -0,0 +1,917 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +import string +import itertools +import selinux + +# OVERVIEW +# +# This file contains objects and functions used to represent the reference +# policy (including the headers, M4 macros, and policy language statements). +# +# This representation is very different from the semantic representation +# used in libsepol. Instead, it is a more typical abstract representation +# used by the first stage of compilers. It is basically a parse tree. +# +# This choice is intentional as it allows us to handle the unprocessed +# M4 statements - including the $1 style arguments - and to more easily generate +# the data structures that we need for policy generation. +# + +# Constans for referring to fields +SRC_TYPE = 0 +TGT_TYPE = 1 +OBJ_CLASS = 2 +PERMS = 3 +ROLE = 4 +DEST_TYPE = 5 + +# String represenations of the above constants +field_to_str = ["source", "target", "object", "permission", "role", "destination" ] +str_to_field = { "source" : SRC_TYPE, "target" : TGT_TYPE, "object" : OBJ_CLASS, + "permission" : PERMS, "role" : ROLE, "destination" : DEST_TYPE } + +# Base Classes + +class PolicyBase: + def __init__(self, parent=None): + self.parent = None + self.comment = None + +class Node(PolicyBase): + """Base class objects produced from parsing the reference policy. + + The Node class is used as the base class for any non-leaf + object produced by parsing the reference policy. This object + should contain a reference to its parent (or None for a top-level + object) and 0 or more children. + + The general idea here is to have a very simple tree structure. Children + are not separated out by type. Instead the tree structure represents + fairly closely the real structure of the policy statements. + + The object should be iterable - by default over all children but + subclasses are free to provide additional iterators over a subset + of their childre (see Interface for example). + """ + + def __init__(self, parent=None): + PolicyBase.__init__(self, parent) + self.children = [] + + def __iter__(self): + return iter(self.children) + + # Not all of the iterators will return something on all Nodes, but + # they won't explode either. Putting them here is just easier. + + # Top level nodes + + def nodes(self): + return itertools.ifilter(lambda x: isinstance(x, Node), walktree(self)) + + def modules(self): + return itertools.ifilter(lambda x: isinstance(x, Module), walktree(self)) + + def interfaces(self): + return itertools.ifilter(lambda x: isinstance(x, Interface), walktree(self)) + + def templates(self): + return itertools.ifilter(lambda x: isinstance(x, Template), walktree(self)) + + def support_macros(self): + return itertools.ifilter(lambda x: isinstance(x, SupportMacros), walktree(self)) + + # Common policy statements + + def module_declarations(self): + return itertools.ifilter(lambda x: isinstance(x, ModuleDeclaration), walktree(self)) + + def interface_calls(self): + return itertools.ifilter(lambda x: isinstance(x, InterfaceCall), walktree(self)) + + def avrules(self): + return itertools.ifilter(lambda x: isinstance(x, AVRule), walktree(self)) + + def typerules(self): + return itertools.ifilter(lambda x: isinstance(x, TypeRule), walktree(self)) + + def typeattributes(self): + """Iterate over all of the TypeAttribute children of this Interface.""" + return itertools.ifilter(lambda x: isinstance(x, TypeAttribute), walktree(self)) + + def roleattributes(self): + """Iterate over all of the RoleAttribute children of this Interface.""" + return itertools.ifilter(lambda x: isinstance(x, RoleAttribute), walktree(self)) + + def requires(self): + return itertools.ifilter(lambda x: isinstance(x, Require), walktree(self)) + + def roles(self): + return itertools.ifilter(lambda x: isinstance(x, Role), walktree(self)) + + def role_allows(self): + return itertools.ifilter(lambda x: isinstance(x, RoleAllow), walktree(self)) + + def role_types(self): + return itertools.ifilter(lambda x: isinstance(x, RoleType), walktree(self)) + + def __str__(self): + if self.comment: + return str(self.comment) + "\n" + self.to_string() + else: + return self.to_string() + + def __repr__(self): + return "<%s(%s)>" % (self.__class__.__name__, self.to_string()) + + def to_string(self): + return "" + + +class Leaf(PolicyBase): + def __init__(self, parent=None): + PolicyBase.__init__(self, parent) + + def __str__(self): + if self.comment: + return str(self.comment) + "\n" + self.to_string() + else: + return self.to_string() + + def __repr__(self): + return "<%s(%s)>" % (self.__class__.__name__, self.to_string()) + + def to_string(self): + return "" + + + +# Utility functions + +def walktree(node, depthfirst=True, showdepth=False, type=None): + """Iterate over a Node and its Children. + + The walktree function iterates over a tree containing Nodes and + leaf objects. The iteration can perform a depth first or a breadth + first traversal of the tree (controlled by the depthfirst + paramater. The passed in node will be returned. + + This function will only work correctly for trees - arbitrary graphs + will likely cause infinite looping. + """ + # We control depth first / versus breadth first by + # how we pop items off of the node stack. + if depthfirst: + index = -1 + else: + index = 0 + + stack = [(node, 0)] + while len(stack) > 0: + cur, depth = stack.pop(index) + if showdepth: + yield cur, depth + else: + yield cur + + # If the node is not a Node instance it must + # be a leaf - so no need to add it to the stack + if isinstance(cur, Node): + items = [] + i = len(cur.children) - 1 + while i >= 0: + if type is None or isinstance(cur.children[i], type): + items.append((cur.children[i], depth + 1)) + i -= 1 + + stack.extend(items) + +def walknode(node, type=None): + """Iterate over the direct children of a Node. + + The walktree function iterates over the children of a Node. + Unlike walktree it does note return the passed in node or + the children of any Node objects (that is, it does not go + beyond the current level in the tree). + """ + for x in node: + if type is None or isinstance(x, type): + yield x + + +def list_to_space_str(s, cont=('{', '}')): + """Convert a set (or any sequence type) into a string representation + formatted to match SELinux space separated list conventions. + + For example the list ['read', 'write'] would be converted into: + '{ read write }' + """ + l = len(s) + str = "" + if l < 1: + raise ValueError("cannot convert 0 len set to string") + str = " ".join(s) + if l == 1: + return str + else: + return cont[0] + " " + str + " " + cont[1] + +def list_to_comma_str(s): + l = len(s) + if l < 1: + raise ValueError("cannot conver 0 len set to comma string") + + return ", ".join(s) + +# Basic SELinux types + +class IdSet(set): + def __init__(self, list=None): + if list: + set.__init__(self, list) + else: + set.__init__(self) + self.compliment = False + + def to_space_str(self): + return list_to_space_str(self) + + def to_comma_str(self): + return list_to_comma_str(self) + +class SecurityContext(Leaf): + """An SELinux security context with optional MCS / MLS fields.""" + def __init__(self, context=None, parent=None): + """Create a SecurityContext object, optionally from a string. + + Parameters: + [context] - string representing a security context. Same format + as a string passed to the from_string method. + """ + Leaf.__init__(self, parent) + self.user = "" + self.role = "" + self.type = "" + self.level = None + if context is not None: + self.from_string(context) + + def from_string(self, context): + """Parse a string representing a context into a SecurityContext. + + The string should be in the standard format - e.g., + 'user:role:type:level'. + + Raises ValueError if the string is not parsable as a security context. + """ + fields = context.split(":") + if len(fields) < 3: + raise ValueError("context string [%s] not in a valid format" % context) + + self.user = fields[0] + self.role = fields[1] + self.type = fields[2] + if len(fields) > 3: + # FUTURE - normalize level fields to allow more comparisons to succeed. + self.level = string.join(fields[3:], ':') + else: + self.level = None + + def __eq__(self, other): + """Compare two SecurityContext objects - all fields must be exactly the + the same for the comparison to work. It is possible for the level fields + to be semantically the same yet syntactically different - in this case + this function will return false. + """ + return self.user == other.user and \ + self.role == other.role and \ + self.type == other.type and \ + self.level == other.level + + def to_string(self, default_level=None): + """Return a string representing this security context. + + By default, the string will contiain a MCS / MLS level + potentially from the default which is passed in if none was + set. + + Arguments: + default_level - the default level to use if self.level is an + empty string. + + Returns: + A string represening the security context in the form + 'user:role:type:level'. + """ + fields = [self.user, self.role, self.type] + if self.level is None: + if default_level is None: + if selinux.is_selinux_mls_enabled() == 1: + fields.append("s0") + else: + fields.append(default_level) + else: + fields.append(self.level) + return ":".join(fields) + +class ObjectClass(Leaf): + """SELinux object class and permissions. + + This class is a basic representation of an SELinux object + class - it does not represent separate common permissions - + just the union of the common and class specific permissions. + It is meant to be convenient for policy generation. + """ + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + self.perms = IdSet() + +# Basic statements + +class TypeAttribute(Leaf): + """SElinux typeattribute statement. + + This class represents a typeattribute statement. + """ + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = "" + self.attributes = IdSet() + + def to_string(self): + return "typeattribute %s %s;" % (self.type, self.attributes.to_comma_str()) + +class RoleAttribute(Leaf): + """SElinux roleattribute statement. + + This class represents a roleattribute statement. + """ + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.role = "" + self.roleattributes = IdSet() + + def to_string(self): + return "roleattribute %s %s;" % (self.role, self.roleattributes.to_comma_str()) + + +class Role(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.role = "" + self.types = IdSet() + + def to_string(self): + s = "" + for t in self.types: + s += "role %s types %s;\n" % (self.role, t) + return s + +class Type(Leaf): + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + self.attributes = IdSet() + self.aliases = IdSet() + + def to_string(self): + s = "type %s" % self.name + if len(self.aliases) > 0: + s = s + "alias %s" % self.aliases.to_space_str() + if len(self.attributes) > 0: + s = s + ", %s" % self.attributes.to_comma_str() + return s + ";" + +class TypeAlias(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = "" + self.aliases = IdSet() + + def to_string(self): + return "typealias %s alias %s;" % (self.type, self.aliases.to_space_str()) + +class Attribute(Leaf): + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + + def to_string(self): + return "attribute %s;" % self.name + +class Attribute_Role(Leaf): + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + + def to_string(self): + return "attribute_role %s;" % self.name + + +# Classes representing rules + +class AVRule(Leaf): + """SELinux access vector (AV) rule. + + The AVRule class represents all varieties of AV rules including + allow, dontaudit, and auditallow (indicated by the flags self.ALLOW, + self.DONTAUDIT, and self.AUDITALLOW respectively). + + The source and target types, object classes, and perms are all represented + by sets containing strings. Sets are used to make it simple to add + strings repeatedly while avoiding duplicates. + + No checking is done to make certain that the symbols are valid or + consistent (e.g., perms that don't match the object classes). It is + even possible to put invalid types like '$1' into the rules to allow + storage of the reference policy interfaces. + """ + ALLOW = 0 + DONTAUDIT = 1 + AUDITALLOW = 2 + NEVERALLOW = 3 + + def __init__(self, av=None, parent=None): + Leaf.__init__(self, parent) + self.src_types = IdSet() + self.tgt_types = IdSet() + self.obj_classes = IdSet() + self.perms = IdSet() + self.rule_type = self.ALLOW + if av: + self.from_av(av) + + def __rule_type_str(self): + if self.rule_type == self.ALLOW: + return "allow" + elif self.rule_type == self.DONTAUDIT: + return "dontaudit" + else: + return "auditallow" + + def from_av(self, av): + """Add the access from an access vector to this allow + rule. + """ + self.src_types.add(av.src_type) + if av.src_type == av.tgt_type: + self.tgt_types.add("self") + else: + self.tgt_types.add(av.tgt_type) + self.obj_classes.add(av.obj_class) + self.perms.update(av.perms) + + def to_string(self): + """Return a string representation of the rule + that is a valid policy language representation (assuming + that the types, object class, etc. are valie). + """ + return "%s %s %s:%s %s;" % (self.__rule_type_str(), + self.src_types.to_space_str(), + self.tgt_types.to_space_str(), + self.obj_classes.to_space_str(), + self.perms.to_space_str()) +class TypeRule(Leaf): + """SELinux type rules. + + This class is very similar to the AVRule class, but is for representing + the type rules (type_trans, type_change, and type_member). The major + difference is the lack of perms and only and sing destination type. + """ + TYPE_TRANSITION = 0 + TYPE_CHANGE = 1 + TYPE_MEMBER = 2 + + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.src_types = IdSet() + self.tgt_types = IdSet() + self.obj_classes = IdSet() + self.dest_type = "" + self.rule_type = self.TYPE_TRANSITION + + def __rule_type_str(self): + if self.rule_type == self.TYPE_TRANSITION: + return "type_transition" + elif self.rule_type == self.TYPE_CHANGE: + return "type_change" + else: + return "type_member" + + def to_string(self): + return "%s %s %s:%s %s;" % (self.__rule_type_str(), + self.src_types.to_space_str(), + self.tgt_types.to_space_str(), + self.obj_classes.to_space_str(), + self.dest_type) + +class RoleAllow(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.src_roles = IdSet() + self.tgt_roles = IdSet() + + def to_string(self): + return "allow %s %s;" % (self.src_roles.to_comma_str(), + self.tgt_roles.to_comma_str()) + +class RoleType(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.role = "" + self.types = IdSet() + + def to_string(self): + s = "" + for t in self.types: + s += "role %s types %s;\n" % (self.role, t) + return s + +class ModuleDeclaration(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.name = "" + self.version = "" + self.refpolicy = False + + def to_string(self): + if self.refpolicy: + return "policy_module(%s, %s)" % (self.name, self.version) + else: + return "module %s %s;" % (self.name, self.version) + +class Conditional(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + self.cond_expr = [] + + def to_string(self): + return "[If %s]" % list_to_space_str(self.cond_expr, cont=("", "")) + +class Bool(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.name = "" + self.state = False + + def to_string(self): + s = "bool %s " % self.name + if s.state: + return s + "true" + else: + return s + "false" + +class InitialSid(Leaf): + def __init(self, parent=None): + Leaf.__init__(self, parent) + self.name = "" + self.context = None + + def to_string(self): + return "sid %s %s" % (self.name, str(self.context)) + +class GenfsCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.filesystem = "" + self.path = "" + self.context = None + + def to_string(self): + return "genfscon %s %s %s" % (self.filesystem, self.path, str(self.context)) + +class FilesystemUse(Leaf): + XATTR = 1 + TRANS = 2 + TASK = 3 + + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = self.XATTR + self.filesystem = "" + self.context = None + + def to_string(self): + s = "" + if self.type == XATTR: + s = "fs_use_xattr " + elif self.type == TRANS: + s = "fs_use_trans " + elif self.type == TASK: + s = "fs_use_task " + + return "%s %s %s;" % (s, self.filesystem, str(self.context)) + +class PortCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.port_type = "" + self.port_number = "" + self.context = None + + def to_string(self): + return "portcon %s %s %s" % (self.port_type, self.port_number, str(self.context)) + +class NodeCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.start = "" + self.end = "" + self.context = None + + def to_string(self): + return "nodecon %s %s %s" % (self.start, self.end, str(self.context)) + +class NetifCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.interface = "" + self.interface_context = None + self.packet_context = None + + def to_string(self): + return "netifcon %s %s %s" % (self.interface, str(self.interface_context), + str(self.packet_context)) +class PirqCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.pirq_number = "" + self.context = None + + def to_string(self): + return "pirqcon %s %s" % (self.pirq_number, str(self.context)) + +class IomemCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.device_mem = "" + self.context = None + + def to_string(self): + return "iomemcon %s %s" % (self.device_mem, str(self.context)) + +class IoportCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.ioport = "" + self.context = None + + def to_string(self): + return "ioportcon %s %s" % (self.ioport, str(self.context)) + +class PciDeviceCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.device = "" + self.context = None + + def to_string(self): + return "pcidevicecon %s %s" % (self.device, str(self.context)) + +class DeviceTreeCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.path = "" + self.context = None + + def to_string(self): + return "devicetreecon %s %s" % (self.path, str(self.context)) + +# Reference policy specific types + +def print_tree(head): + for node, depth in walktree(head, showdepth=True): + s = "" + for i in range(depth): + s = s + "\t" + print s + str(node) + + +class Headers(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + + def to_string(self): + return "[Headers]" + + +class Module(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + + def to_string(self): + return "" + +class Interface(Node): + """A reference policy interface definition. + + This class represents a reference policy interface definition. + """ + def __init__(self, name="", parent=None): + Node.__init__(self, parent) + self.name = name + + def to_string(self): + return "[Interface name: %s]" % self.name + +class TunablePolicy(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + self.cond_expr = [] + + def to_string(self): + return "[Tunable Policy %s]" % list_to_space_str(self.cond_expr, cont=("", "")) + +class Template(Node): + def __init__(self, name="", parent=None): + Node.__init__(self, parent) + self.name = name + + def to_string(self): + return "[Template name: %s]" % self.name + +class IfDef(Node): + def __init__(self, name="", parent=None): + Node.__init__(self, parent) + self.name = name + + def to_string(self): + return "[Ifdef name: %s]" % self.name + +class InterfaceCall(Leaf): + def __init__(self, ifname="", parent=None): + Leaf.__init__(self, parent) + self.ifname = ifname + self.args = [] + self.comments = [] + + def matches(self, other): + if self.ifname != other.ifname: + return False + if len(self.args) != len(other.args): + return False + for a,b in zip(self.args, other.args): + if a != b: + return False + return True + + def to_string(self): + s = "%s(" % self.ifname + i = 0 + for a in self.args: + if isinstance(a, list): + str = list_to_space_str(a) + else: + str = a + + if i != 0: + s = s + ", %s" % str + else: + s = s + str + i += 1 + return s + ")" + +class OptionalPolicy(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + + def to_string(self): + return "[Optional Policy]" + +class SupportMacros(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + self.map = None + + def to_string(self): + return "[Support Macros]" + + def __expand_perm(self, perm): + # Recursive expansion - the assumption is that these + # are ordered correctly so that no macro is used before + # it is defined + s = set() + if self.map.has_key(perm): + for p in self.by_name(perm): + s.update(self.__expand_perm(p)) + else: + s.add(perm) + return s + + def __gen_map(self): + self.map = {} + for x in self: + exp_perms = set() + for perm in x.perms: + exp_perms.update(self.__expand_perm(perm)) + self.map[x.name] = exp_perms + + def by_name(self, name): + if not self.map: + self.__gen_map() + return self.map[name] + + def has_key(self, name): + if not self.map: + self.__gen_map() + return self.map.has_key(name) + +class Require(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.types = IdSet() + self.obj_classes = { } + self.roles = IdSet() + self.data = IdSet() + self.users = IdSet() + + def add_obj_class(self, obj_class, perms): + p = self.obj_classes.setdefault(obj_class, IdSet()) + p.update(perms) + + + def to_string(self): + s = [] + s.append("require {") + for type in self.types: + s.append("\ttype %s;" % type) + for obj_class, perms in self.obj_classes.items(): + s.append("\tclass %s %s;" % (obj_class, perms.to_space_str())) + for role in self.roles: + s.append("\trole %s;" % role) + for bool in self.data: + s.append("\tbool %s;" % bool) + for user in self.users: + s.append("\tuser %s;" % user) + s.append("}") + + # Handle empty requires + if len(s) == 2: + return "" + + return "\n".join(s) + + +class ObjPermSet: + def __init__(self, name): + self.name = name + self.perms = set() + + def to_string(self): + return "define(`%s', `%s')" % (self.name, self.perms.to_space_str()) + +class ClassMap: + def __init__(self, obj_class, perms): + self.obj_class = obj_class + self.perms = perms + + def to_string(self): + return self.obj_class + ": " + self.perms + +class Comment: + def __init__(self, l=None): + if l: + self.lines = l + else: + self.lines = [] + + def to_string(self): + # If there are no lines, treat this as a spacer between + # policy statements and return a new line. + if len(self.lines) == 0: + return "" + else: + out = [] + for line in self.lines: + out.append("#" + line) + return "\n".join(out) + + def merge(self, other): + if len(other.lines): + for line in other.lines: + if line != "": + self.lines.append(line) + + def __str__(self): + return self.to_string() + + diff --git a/lib/python2.7/site-packages/sepolgen/sepolgeni18n.py b/lib/python2.7/site-packages/sepolgen/sepolgeni18n.py new file mode 100644 index 0000000..998c435 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/sepolgeni18n.py @@ -0,0 +1,26 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +try: + import gettext + t = gettext.translation( 'yumex' ) + _ = t.gettext +except: + def _(str): + return str diff --git a/lib/python2.7/site-packages/sepolgen/util.py b/lib/python2.7/site-packages/sepolgen/util.py new file mode 100644 index 0000000..74a11f5 --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/util.py @@ -0,0 +1,87 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +class ConsoleProgressBar: + def __init__(self, out, steps=100, indicator='#'): + self.blocks = 0 + self.current = 0 + self.steps = steps + self.indicator = indicator + self.out = out + self.done = False + + def start(self, message=None): + self.done = False + if message: + self.out.write('\n%s:\n' % message) + self.out.write('%--10---20---30---40---50---60---70---80---90--100\n') + + def step(self, n=1): + self.current += n + + old = self.blocks + self.blocks = int(round(self.current / float(self.steps) * 100) / 2) + + if self.blocks > 50: + self.blocks = 50 + + new = self.blocks - old + + self.out.write(self.indicator * new) + self.out.flush() + + if self.blocks == 50 and not self.done: + self.done = True + self.out.write("\n") + +def set_to_list(s): + l = [] + l.extend(s) + return l + +def first(s, sorted=False): + """ + Return the first element of a set. + + It sometimes useful to return the first element from a set but, + because sets are not indexable, this is rather hard. This function + will return the first element from a set. If sorted is True, then + the set will first be sorted (making this an expensive operation). + Otherwise a random element will be returned (as sets are not ordered). + """ + if not len(s): + raise IndexError("empty containter") + + if sorted: + l = set_to_list(s) + l.sort() + return l[0] + else: + for x in s: + return x + +if __name__ == "__main__": + import sys + import time + p = ConsoleProgressBar(sys.stdout, steps=999) + p.start("computing pi") + for i in range(999): + p.step() + time.sleep(0.001) + diff --git a/lib/python2.7/site-packages/sepolgen/yacc.py b/lib/python2.7/site-packages/sepolgen/yacc.py new file mode 100644 index 0000000..bc4536d --- /dev/null +++ b/lib/python2.7/site-packages/sepolgen/yacc.py @@ -0,0 +1,2209 @@ +#----------------------------------------------------------------------------- +# ply: yacc.py +# +# Author(s): David M. Beazley (dave@dabeaz.com) +# +# Copyright (C) 2001-2006, David M. Beazley +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See the file COPYING for a complete copy of the LGPL. +# +# +# This implements an LR parser that is constructed from grammar rules defined +# as Python functions. The grammer is specified by supplying the BNF inside +# Python documentation strings. The inspiration for this technique was borrowed +# from John Aycock's Spark parsing system. PLY might be viewed as cross between +# Spark and the GNU bison utility. +# +# The current implementation is only somewhat object-oriented. The +# LR parser itself is defined in terms of an object (which allows multiple +# parsers to co-exist). However, most of the variables used during table +# construction are defined in terms of global variables. Users shouldn't +# notice unless they are trying to define multiple parsers at the same +# time using threads (in which case they should have their head examined). +# +# This implementation supports both SLR and LALR(1) parsing. LALR(1) +# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), +# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, +# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced +# by the more efficient DeRemer and Pennello algorithm. +# +# :::::::: WARNING ::::::: +# +# Construction of LR parsing tables is fairly complicated and expensive. +# To make this module run fast, a *LOT* of work has been put into +# optimization---often at the expensive of readability and what might +# consider to be good Python "coding style." Modify the code at your +# own risk! +# ---------------------------------------------------------------------------- + +__version__ = "2.2" + +#----------------------------------------------------------------------------- +# === User configurable parameters === +# +# Change these to modify the default behavior of yacc (if you wish) +#----------------------------------------------------------------------------- + +yaccdebug = 1 # Debugging mode. If set, yacc generates a + # a 'parser.out' file in the current directory + +debug_file = 'parser.out' # Default name of the debugging file +tab_module = 'parsetab' # Default name of the table module +default_lr = 'LALR' # Default LR table generation method + +error_count = 3 # Number of symbols that must be shifted to leave recovery mode + +import re, types, sys, cStringIO, hashlib, os.path + +# Exception raised for yacc-related errors +class YaccError(Exception): pass + +#----------------------------------------------------------------------------- +# === LR Parsing Engine === +# +# The following classes are used for the LR parser itself. These are not +# used during table construction and are independent of the actual LR +# table generation algorithm +#----------------------------------------------------------------------------- + +# This class is used to hold non-terminal grammar symbols during parsing. +# It normally has the following attributes set: +# .type = Grammar symbol type +# .value = Symbol value +# .lineno = Starting line number +# .endlineno = Ending line number (optional, set automatically) +# .lexpos = Starting lex position +# .endlexpos = Ending lex position (optional, set automatically) + +class YaccSymbol: + def __str__(self): return self.type + def __repr__(self): return str(self) + +# This class is a wrapper around the objects actually passed to each +# grammar rule. Index lookup and assignment actually assign the +# .value attribute of the underlying YaccSymbol object. +# The lineno() method returns the line number of a given +# item (or 0 if not defined). The linespan() method returns +# a tuple of (startline,endline) representing the range of lines +# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) +# representing the range of positional information for a symbol. + +class YaccProduction: + def __init__(self,s,stack=None): + self.slice = s + self.pbstack = [] + self.stack = stack + + def __getitem__(self,n): + if type(n) == types.IntType: + if n >= 0: return self.slice[n].value + else: return self.stack[n].value + else: + return [s.value for s in self.slice[n.start:n.stop:n.step]] + + def __setitem__(self,n,v): + self.slice[n].value = v + + def __len__(self): + return len(self.slice) + + def lineno(self,n): + return getattr(self.slice[n],"lineno",0) + + def linespan(self,n): + startline = getattr(self.slice[n],"lineno",0) + endline = getattr(self.slice[n],"endlineno",startline) + return startline,endline + + def lexpos(self,n): + return getattr(self.slice[n],"lexpos",0) + + def lexspan(self,n): + startpos = getattr(self.slice[n],"lexpos",0) + endpos = getattr(self.slice[n],"endlexpos",startpos) + return startpos,endpos + + def pushback(self,n): + if n <= 0: + raise ValueError, "Expected a positive value" + if n > (len(self.slice)-1): + raise ValueError, "Can't push %d tokens. Only %d are available." % (n,len(self.slice)-1) + for i in range(0,n): + self.pbstack.append(self.slice[-i-1]) + +# The LR Parsing engine. This is defined as a class so that multiple parsers +# can exist in the same process. A user never instantiates this directly. +# Instead, the global yacc() function should be used to create a suitable Parser +# object. + +class Parser: + def __init__(self,magic=None): + + # This is a hack to keep users from trying to instantiate a Parser + # object directly. + + if magic != "xyzzy": + raise YaccError, "Can't instantiate Parser. Use yacc() instead." + + # Reset internal state + self.productions = None # List of productions + self.errorfunc = None # Error handling function + self.action = { } # LR Action table + self.goto = { } # LR goto table + self.require = { } # Attribute require table + self.method = "Unknown LR" # Table construction method used + + def errok(self): + self.errorcount = 0 + + def restart(self): + del self.statestack[:] + del self.symstack[:] + sym = YaccSymbol() + sym.type = '$end' + self.symstack.append(sym) + self.statestack.append(0) + + def parse(self,input=None,lexer=None,debug=0): + lookahead = None # Current lookahead symbol + lookaheadstack = [ ] # Stack of lookahead symbols + actions = self.action # Local reference to action table + goto = self.goto # Local reference to goto table + prod = self.productions # Local reference to production list + pslice = YaccProduction(None) # Production object passed to grammar rules + pslice.parser = self # Parser object + self.errorcount = 0 # Used during error recovery + + # If no lexer was given, we will try to use the lex module + if not lexer: + import lex + lexer = lex.lexer + + pslice.lexer = lexer + + # If input was supplied, pass to lexer + if input: + lexer.input(input) + + # Tokenize function + get_token = lexer.token + + statestack = [ ] # Stack of parsing states + self.statestack = statestack + symstack = [ ] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + + while 1: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + if debug > 1: + print 'state', statestack[-1] + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + if debug: + errorlead = ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip() + + # Check the action table + s = statestack[-1] + ltype = lookahead.type + t = actions.get((s,ltype),None) + + if debug > 1: + print 'action', t + if t is not None: + if t > 0: + # shift a symbol on the stack + if ltype == '$end': + # Error, end of input + sys.stderr.write("yacc: Parse error. EOF\n") + return + statestack.append(t) + if debug > 1: + sys.stderr.write("%-60s shift state %s\n" % (errorlead, t)) + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if self.errorcount > 0: + self.errorcount -= 1 + + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + if debug > 1: + sys.stderr.write("%-60s reduce %d\n" % (errorlead, -t)) + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + try: + sym.lineno = targ[1].lineno + sym.endlineno = getattr(targ[-1],"endlineno",targ[-1].lineno) + sym.lexpos = targ[1].lexpos + sym.endlexpos = getattr(targ[-1],"endlexpos",targ[-1].lexpos) + except AttributeError: + sym.lineno = 0 + del symstack[-plen:] + del statestack[-plen:] + else: + sym.lineno = 0 + targ = [ sym ] + pslice.slice = targ + pslice.pbstack = [] + # Call the grammar rule with our special slice object + p.func(pslice) + + # If there was a pushback, put that on the stack + if pslice.pbstack: + lookaheadstack.append(lookahead) + for _t in pslice.pbstack: + lookaheadstack.append(_t) + lookahead = None + + symstack.append(sym) + statestack.append(goto[statestack[-1],pname]) + continue + + if t == 0: + n = symstack[-1] + return getattr(n,"value",None) + sys.stderr.write(errorlead, "\n") + + if t == None: + if debug: + sys.stderr.write(errorlead + "\n") + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if not self.errorcount: + self.errorcount = error_count + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + global errok,token,restart + errok = self.errok # Set some special functions available in error recovery + token = get_token + restart = self.restart + tok = self.errorfunc(errtoken) + del errok, token, restart # Delete special functions + + if not self.errorcount: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken,"lineno"): lineno = lookahead.lineno + else: lineno = 0 + if lineno: + sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) + else: + sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) + else: + sys.stderr.write("yacc: Parse error in input. EOF\n") + return + + else: + self.errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + t = YaccSymbol() + t.type = 'error' + if hasattr(lookahead,"lineno"): + t.lineno = lookahead.lineno + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + symstack.pop() + statestack.pop() + + continue + + # Call an error function here + raise RuntimeError, "yacc: internal parser error!!!\n" + +# ----------------------------------------------------------------------------- +# === Parser Construction === +# +# The following functions and variables are used to implement the yacc() function +# itself. This is pretty hairy stuff involving lots of error checking, +# construction of LR items, kernels, and so forth. Although a lot of +# this work is done using global variables, the resulting Parser object +# is completely self contained--meaning that it is safe to repeatedly +# call yacc() with different grammars in the same application. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# validate_file() +# +# This function checks to see if there are duplicated p_rulename() functions +# in the parser module file. Without this function, it is really easy for +# users to make mistakes by cutting and pasting code fragments (and it's a real +# bugger to try and figure out why the resulting parser doesn't work). Therefore, +# we just do a little regular expression pattern matching of def statements +# to try and detect duplicates. +# ----------------------------------------------------------------------------- + +def validate_file(filename): + base,ext = os.path.splitext(filename) + if ext != '.py': return 1 # No idea. Assume it's okay. + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + return 1 # Oh well + + # Match def p_funcname( + fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') + counthash = { } + linen = 1 + noerror = 1 + for l in lines: + m = fre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + sys.stderr.write("%s:%d: Function %s redefined. Previously defined on line %d\n" % (filename,linen,name,prev)) + noerror = 0 + linen += 1 + return noerror + +# This function looks for functions that might be grammar rules, but which don't have the proper p_suffix. +def validate_dict(d): + for n,v in d.items(): + if n[0:2] == 'p_' and type(v) in (types.FunctionType, types.MethodType): continue + if n[0:2] == 't_': continue + + if n[0:2] == 'p_': + sys.stderr.write("yacc: Warning. '%s' not defined as a function\n" % n) + if 1 and isinstance(v,types.FunctionType) and v.func_code.co_argcount == 1: + try: + doc = v.__doc__.split(" ") + if doc[1] == ':': + sys.stderr.write("%s:%d: Warning. Possible grammar rule '%s' defined without p_ prefix.\n" % (v.func_code.co_filename, v.func_code.co_firstlineno,n)) + except StandardError: + pass + +# ----------------------------------------------------------------------------- +# === GRAMMAR FUNCTIONS === +# +# The following global variables and functions are used to store, manipulate, +# and verify the grammar rules specified by the user. +# ----------------------------------------------------------------------------- + +# Initialize all of the global variables used during grammar construction +def initialize_vars(): + global Productions, Prodnames, Prodmap, Terminals + global Nonterminals, First, Follow, Precedence, LRitems + global Errorfunc, Signature, Requires + + Productions = [None] # A list of all of the productions. The first + # entry is always reserved for the purpose of + # building an augmented grammar + + Prodnames = { } # A dictionary mapping the names of nonterminals to a list of all + # productions of that nonterminal. + + Prodmap = { } # A dictionary that is only used to detect duplicate + # productions. + + Terminals = { } # A dictionary mapping the names of terminal symbols to a + # list of the rules where they are used. + + Nonterminals = { } # A dictionary mapping names of nonterminals to a list + # of rule numbers where they are used. + + First = { } # A dictionary of precomputed FIRST(x) symbols + + Follow = { } # A dictionary of precomputed FOLLOW(x) symbols + + Precedence = { } # Precedence rules for each terminal. Contains tuples of the + # form ('right',level) or ('nonassoc', level) or ('left',level) + + LRitems = [ ] # A list of all LR items for the grammar. These are the + # productions with the "dot" like E -> E . PLUS E + + Errorfunc = None # User defined error handler + + Signature = hashlib.sha256() # Digital signature of the grammar rules, precedence + # and other information. Used to determined when a + # parsing table needs to be regenerated. + + Requires = { } # Requires list + + # File objects used when creating the parser.out debugging file + global _vf, _vfc + _vf = cStringIO.StringIO() + _vfc = cStringIO.StringIO() + +# ----------------------------------------------------------------------------- +# class Production: +# +# This class stores the raw information about a single production or grammar rule. +# It has a few required attributes: +# +# name - Name of the production (nonterminal) +# prod - A list of symbols making up its production +# number - Production number. +# +# In addition, a few additional attributes are used to help with debugging or +# optimization of table generation. +# +# file - File where production action is defined. +# lineno - Line number where action is defined +# func - Action function +# prec - Precedence level +# lr_next - Next LR item. Example, if we are ' E -> E . PLUS E' +# then lr_next refers to 'E -> E PLUS . E' +# lr_index - LR item index (location of the ".") in the prod list. +# lookaheads - LALR lookahead symbols for this item +# len - Length of the production (number of symbols on right hand side) +# ----------------------------------------------------------------------------- + +class Production: + def __init__(self,**kw): + for k,v in kw.items(): + setattr(self,k,v) + self.lr_index = -1 + self.lr0_added = 0 # Flag indicating whether or not added to LR0 closure + self.lr1_added = 0 # Flag indicating whether or not added to LR1 + self.usyms = [ ] + self.lookaheads = { } + self.lk_added = { } + self.setnumbers = [ ] + + def __str__(self): + if self.prod: + s = "%s -> %s" % (self.name," ".join(self.prod)) + else: + s = "%s -> " % self.name + return s + + def __repr__(self): + return str(self) + + # Compute lr_items from the production + def lr_item(self,n): + if n > len(self.prod): return None + p = Production() + p.name = self.name + p.prod = list(self.prod) + p.number = self.number + p.lr_index = n + p.lookaheads = { } + p.setnumbers = self.setnumbers + p.prod.insert(n,".") + p.prod = tuple(p.prod) + p.len = len(p.prod) + p.usyms = self.usyms + + # Precompute list of productions immediately following + try: + p.lrafter = Prodnames[p.prod[n+1]] + except (IndexError,KeyError),e: + p.lrafter = [] + try: + p.lrbefore = p.prod[n-1] + except IndexError: + p.lrbefore = None + + return p + +class MiniProduction: + pass + +# regex matching identifiers +_is_identifier = re.compile(r'^[a-zA-Z0-9_-~]+$') + +# ----------------------------------------------------------------------------- +# add_production() +# +# Given an action function, this function assembles a production rule. +# The production rule is assumed to be found in the function's docstring. +# This rule has the general syntax: +# +# name1 ::= production1 +# | production2 +# | production3 +# ... +# | productionn +# name2 ::= production1 +# | production2 +# ... +# ----------------------------------------------------------------------------- + +def add_production(f,file,line,prodname,syms): + + if Terminals.has_key(prodname): + sys.stderr.write("%s:%d: Illegal rule name '%s'. Already defined as a token.\n" % (file,line,prodname)) + return -1 + if prodname == 'error': + sys.stderr.write("%s:%d: Illegal rule name '%s'. error is a reserved word.\n" % (file,line,prodname)) + return -1 + + if not _is_identifier.match(prodname): + sys.stderr.write("%s:%d: Illegal rule name '%s'\n" % (file,line,prodname)) + return -1 + + for x in range(len(syms)): + s = syms[x] + if s[0] in "'\"": + try: + c = eval(s) + if (len(c) > 1): + sys.stderr.write("%s:%d: Literal token %s in rule '%s' may only be a single character\n" % (file,line,s, prodname)) + return -1 + if not Terminals.has_key(c): + Terminals[c] = [] + syms[x] = c + continue + except SyntaxError: + pass + if not _is_identifier.match(s) and s != '%prec': + sys.stderr.write("%s:%d: Illegal name '%s' in rule '%s'\n" % (file,line,s, prodname)) + return -1 + + # See if the rule is already in the rulemap + map = "%s -> %s" % (prodname,syms) + if Prodmap.has_key(map): + m = Prodmap[map] + sys.stderr.write("%s:%d: Duplicate rule %s.\n" % (file,line, m)) + sys.stderr.write("%s:%d: Previous definition at %s:%d\n" % (file,line, m.file, m.line)) + return -1 + + p = Production() + p.name = prodname + p.prod = syms + p.file = file + p.line = line + p.func = f + p.number = len(Productions) + + + Productions.append(p) + Prodmap[map] = p + if not Nonterminals.has_key(prodname): + Nonterminals[prodname] = [ ] + + # Add all terminals to Terminals + i = 0 + while i < len(p.prod): + t = p.prod[i] + if t == '%prec': + try: + precname = p.prod[i+1] + except IndexError: + sys.stderr.write("%s:%d: Syntax error. Nothing follows %%prec.\n" % (p.file,p.line)) + return -1 + + prec = Precedence.get(precname,None) + if not prec: + sys.stderr.write("%s:%d: Nothing known about the precedence of '%s'\n" % (p.file,p.line,precname)) + return -1 + else: + p.prec = prec + del p.prod[i] + del p.prod[i] + continue + + if Terminals.has_key(t): + Terminals[t].append(p.number) + # Is a terminal. We'll assign a precedence to p based on this + if not hasattr(p,"prec"): + p.prec = Precedence.get(t,('right',0)) + else: + if not Nonterminals.has_key(t): + Nonterminals[t] = [ ] + Nonterminals[t].append(p.number) + i += 1 + + if not hasattr(p,"prec"): + p.prec = ('right',0) + + # Set final length of productions + p.len = len(p.prod) + p.prod = tuple(p.prod) + + # Calculate unique syms in the production + p.usyms = [ ] + for s in p.prod: + if s not in p.usyms: + p.usyms.append(s) + + # Add to the global productions list + try: + Prodnames[p.name].append(p) + except KeyError: + Prodnames[p.name] = [ p ] + return 0 + +# Given a raw rule function, this function rips out its doc string +# and adds rules to the grammar + +def add_function(f): + line = f.func_code.co_firstlineno + file = f.func_code.co_filename + error = 0 + + if isinstance(f,types.MethodType): + reqdargs = 2 + else: + reqdargs = 1 + + if f.func_code.co_argcount > reqdargs: + sys.stderr.write("%s:%d: Rule '%s' has too many arguments.\n" % (file,line,f.__name__)) + return -1 + + if f.func_code.co_argcount < reqdargs: + sys.stderr.write("%s:%d: Rule '%s' requires an argument.\n" % (file,line,f.__name__)) + return -1 + + if f.__doc__: + # Split the doc string into lines + pstrings = f.__doc__.splitlines() + lastp = None + dline = line + for ps in pstrings: + dline += 1 + p = ps.split() + if not p: continue + try: + if p[0] == '|': + # This is a continuation of a previous rule + if not lastp: + sys.stderr.write("%s:%d: Misplaced '|'.\n" % (file,dline)) + return -1 + prodname = lastp + if len(p) > 1: + syms = p[1:] + else: + syms = [ ] + else: + prodname = p[0] + lastp = prodname + assign = p[1] + if len(p) > 2: + syms = p[2:] + else: + syms = [ ] + if assign != ':' and assign != '::=': + sys.stderr.write("%s:%d: Syntax error. Expected ':'\n" % (file,dline)) + return -1 + + + e = add_production(f,file,dline,prodname,syms) + error += e + + + except StandardError: + sys.stderr.write("%s:%d: Syntax error in rule '%s'\n" % (file,dline,ps)) + error -= 1 + else: + sys.stderr.write("%s:%d: No documentation string specified in function '%s'\n" % (file,line,f.__name__)) + return error + + +# Cycle checking code (Michael Dyck) + +def compute_reachable(): + ''' + Find each symbol that can be reached from the start symbol. + Print a warning for any nonterminals that can't be reached. + (Unused terminals have already had their warning.) + ''' + Reachable = { } + for s in Terminals.keys() + Nonterminals.keys(): + Reachable[s] = 0 + + mark_reachable_from( Productions[0].prod[0], Reachable ) + + for s in Nonterminals.keys(): + if not Reachable[s]: + sys.stderr.write("yacc: Symbol '%s' is unreachable.\n" % s) + +def mark_reachable_from(s, Reachable): + ''' + Mark all symbols that are reachable from symbol s. + ''' + if Reachable[s]: + # We've already reached symbol s. + return + Reachable[s] = 1 + for p in Prodnames.get(s,[]): + for r in p.prod: + mark_reachable_from(r, Reachable) + +# ----------------------------------------------------------------------------- +# compute_terminates() +# +# This function looks at the various parsing rules and tries to detect +# infinite recursion cycles (grammar rules where there is no possible way +# to derive a string of only terminals). +# ----------------------------------------------------------------------------- +def compute_terminates(): + ''' + Raise an error for any symbols that don't terminate. + ''' + Terminates = {} + + # Terminals: + for t in Terminals.keys(): + Terminates[t] = 1 + + Terminates['$end'] = 1 + + # Nonterminals: + + # Initialize to false: + for n in Nonterminals.keys(): + Terminates[n] = 0 + + # Then propagate termination until no change: + while 1: + some_change = 0 + for (n,pl) in Prodnames.items(): + # Nonterminal n terminates iff any of its productions terminates. + for p in pl: + # Production p terminates iff all of its rhs symbols terminate. + for s in p.prod: + if not Terminates[s]: + # The symbol s does not terminate, + # so production p does not terminate. + p_terminates = 0 + break + else: + # didn't break from the loop, + # so every symbol s terminates + # so production p terminates. + p_terminates = 1 + + if p_terminates: + # symbol n terminates! + if not Terminates[n]: + Terminates[n] = 1 + some_change = 1 + # Don't need to consider any more productions for this n. + break + + if not some_change: + break + + some_error = 0 + for (s,terminates) in Terminates.items(): + if not terminates: + if not Prodnames.has_key(s) and not Terminals.has_key(s) and s != 'error': + # s is used-but-not-defined, and we've already warned of that, + # so it would be overkill to say that it's also non-terminating. + pass + else: + sys.stderr.write("yacc: Infinite recursion detected for symbol '%s'.\n" % s) + some_error = 1 + + return some_error + +# ----------------------------------------------------------------------------- +# verify_productions() +# +# This function examines all of the supplied rules to see if they seem valid. +# ----------------------------------------------------------------------------- +def verify_productions(cycle_check=1): + error = 0 + for p in Productions: + if not p: continue + + for s in p.prod: + if not Prodnames.has_key(s) and not Terminals.has_key(s) and s != 'error': + sys.stderr.write("%s:%d: Symbol '%s' used, but not defined as a token or a rule.\n" % (p.file,p.line,s)) + error = 1 + continue + + unused_tok = 0 + # Now verify all of the tokens + if yaccdebug: + _vf.write("Unused terminals:\n\n") + for s,v in Terminals.items(): + if s != 'error' and not v: + sys.stderr.write("yacc: Warning. Token '%s' defined, but not used.\n" % s) + if yaccdebug: _vf.write(" %s\n"% s) + unused_tok += 1 + + # Print out all of the productions + if yaccdebug: + _vf.write("\nGrammar\n\n") + for i in range(1,len(Productions)): + _vf.write("Rule %-5d %s\n" % (i, Productions[i])) + + unused_prod = 0 + # Verify the use of all productions + for s,v in Nonterminals.items(): + if not v: + p = Prodnames[s][0] + sys.stderr.write("%s:%d: Warning. Rule '%s' defined, but not used.\n" % (p.file,p.line, s)) + unused_prod += 1 + + + if unused_tok == 1: + sys.stderr.write("yacc: Warning. There is 1 unused token.\n") + if unused_tok > 1: + sys.stderr.write("yacc: Warning. There are %d unused tokens.\n" % unused_tok) + + if unused_prod == 1: + sys.stderr.write("yacc: Warning. There is 1 unused rule.\n") + if unused_prod > 1: + sys.stderr.write("yacc: Warning. There are %d unused rules.\n" % unused_prod) + + if yaccdebug: + _vf.write("\nTerminals, with rules where they appear\n\n") + ks = Terminals.keys() + ks.sort() + for k in ks: + _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Terminals[k]]))) + _vf.write("\nNonterminals, with rules where they appear\n\n") + ks = Nonterminals.keys() + ks.sort() + for k in ks: + _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Nonterminals[k]]))) + + if (cycle_check): + compute_reachable() + error += compute_terminates() +# error += check_cycles() + return error + +# ----------------------------------------------------------------------------- +# build_lritems() +# +# This function walks the list of productions and builds a complete set of the +# LR items. The LR items are stored in two ways: First, they are uniquely +# numbered and placed in the list _lritems. Second, a linked list of LR items +# is built for each production. For example: +# +# E -> E PLUS E +# +# Creates the list +# +# [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] +# ----------------------------------------------------------------------------- + +def build_lritems(): + for p in Productions: + lastlri = p + lri = p.lr_item(0) + i = 0 + while 1: + lri = p.lr_item(i) + lastlri.lr_next = lri + if not lri: break + lri.lr_num = len(LRitems) + LRitems.append(lri) + lastlri = lri + i += 1 + + # In order for the rest of the parser generator to work, we need to + # guarantee that no more lritems are generated. Therefore, we nuke + # the p.lr_item method. (Only used in debugging) + # Production.lr_item = None + +# ----------------------------------------------------------------------------- +# add_precedence() +# +# Given a list of precedence rules, add to the precedence table. +# ----------------------------------------------------------------------------- + +def add_precedence(plist): + plevel = 0 + error = 0 + for p in plist: + plevel += 1 + try: + prec = p[0] + terms = p[1:] + if prec != 'left' and prec != 'right' and prec != 'nonassoc': + sys.stderr.write("yacc: Invalid precedence '%s'\n" % prec) + return -1 + for t in terms: + if Precedence.has_key(t): + sys.stderr.write("yacc: Precedence already specified for terminal '%s'\n" % t) + error += 1 + continue + Precedence[t] = (prec,plevel) + except: + sys.stderr.write("yacc: Invalid precedence table.\n") + error += 1 + + return error + +# ----------------------------------------------------------------------------- +# augment_grammar() +# +# Compute the augmented grammar. This is just a rule S' -> start where start +# is the starting symbol. +# ----------------------------------------------------------------------------- + +def augment_grammar(start=None): + if not start: + start = Productions[1].name + Productions[0] = Production(name="S'",prod=[start],number=0,len=1,prec=('right',0),func=None) + Productions[0].usyms = [ start ] + Nonterminals[start].append(0) + + +# ------------------------------------------------------------------------- +# first() +# +# Compute the value of FIRST1(beta) where beta is a tuple of symbols. +# +# During execution of compute_first1, the result may be incomplete. +# Afterward (e.g., when called from compute_follow()), it will be complete. +# ------------------------------------------------------------------------- +def first(beta): + + # We are computing First(x1,x2,x3,...,xn) + result = [ ] + for x in beta: + x_produces_empty = 0 + + # Add all the non- symbols of First[x] to the result. + for f in First[x]: + if f == '': + x_produces_empty = 1 + else: + if f not in result: result.append(f) + + if x_produces_empty: + # We have to consider the next x in beta, + # i.e. stay in the loop. + pass + else: + # We don't have to consider any further symbols in beta. + break + else: + # There was no 'break' from the loop, + # so x_produces_empty was true for all x in beta, + # so beta produces empty as well. + result.append('') + + return result + + +# FOLLOW(x) +# Given a non-terminal. This function computes the set of all symbols +# that might follow it. Dragon book, p. 189. + +def compute_follow(start=None): + # Add '$end' to the follow list of the start symbol + for k in Nonterminals.keys(): + Follow[k] = [ ] + + if not start: + start = Productions[1].name + + Follow[start] = [ '$end' ] + + while 1: + didadd = 0 + for p in Productions[1:]: + # Here is the production set + for i in range(len(p.prod)): + B = p.prod[i] + if Nonterminals.has_key(B): + # Okay. We got a non-terminal in a production + fst = first(p.prod[i+1:]) + hasempty = 0 + for f in fst: + if f != '' and f not in Follow[B]: + Follow[B].append(f) + didadd = 1 + if f == '': + hasempty = 1 + if hasempty or i == (len(p.prod)-1): + # Add elements of follow(a) to follow(b) + for f in Follow[p.name]: + if f not in Follow[B]: + Follow[B].append(f) + didadd = 1 + if not didadd: break + + if 0 and yaccdebug: + _vf.write('\nFollow:\n') + for k in Nonterminals.keys(): + _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Follow[k]]))) + +# ------------------------------------------------------------------------- +# compute_first1() +# +# Compute the value of FIRST1(X) for all symbols +# ------------------------------------------------------------------------- +def compute_first1(): + + # Terminals: + for t in Terminals.keys(): + First[t] = [t] + + First['$end'] = ['$end'] + First['#'] = ['#'] # what's this for? + + # Nonterminals: + + # Initialize to the empty set: + for n in Nonterminals.keys(): + First[n] = [] + + # Then propagate symbols until no change: + while 1: + some_change = 0 + for n in Nonterminals.keys(): + for p in Prodnames[n]: + for f in first(p.prod): + if f not in First[n]: + First[n].append( f ) + some_change = 1 + if not some_change: + break + + if 0 and yaccdebug: + _vf.write('\nFirst:\n') + for k in Nonterminals.keys(): + _vf.write("%-20s : %s\n" % + (k, " ".join([str(s) for s in First[k]]))) + +# ----------------------------------------------------------------------------- +# === SLR Generation === +# +# The following functions are used to construct SLR (Simple LR) parsing tables +# as described on p.221-229 of the dragon book. +# ----------------------------------------------------------------------------- + +# Global variables for the LR parsing engine +def lr_init_vars(): + global _lr_action, _lr_goto, _lr_method + global _lr_goto_cache, _lr0_cidhash + + _lr_action = { } # Action table + _lr_goto = { } # Goto table + _lr_method = "Unknown" # LR method used + _lr_goto_cache = { } + _lr0_cidhash = { } + + +# Compute the LR(0) closure operation on I, where I is a set of LR(0) items. +# prodlist is a list of productions. + +_add_count = 0 # Counter used to detect cycles + +def lr0_closure(I): + global _add_count + + _add_count += 1 + prodlist = Productions + + # Add everything in I to J + J = I[:] + didadd = 1 + while didadd: + didadd = 0 + for j in J: + for x in j.lrafter: + if x.lr0_added == _add_count: continue + # Add B --> .G to J + J.append(x.lr_next) + x.lr0_added = _add_count + didadd = 1 + + return J + +# Compute the LR(0) goto function goto(I,X) where I is a set +# of LR(0) items and X is a grammar symbol. This function is written +# in a way that guarantees uniqueness of the generated goto sets +# (i.e. the same goto set will never be returned as two different Python +# objects). With uniqueness, we can later do fast set comparisons using +# id(obj) instead of element-wise comparison. + +def lr0_goto(I,x): + # First we look for a previously cached entry + g = _lr_goto_cache.get((id(I),x),None) + if g: return g + + # Now we generate the goto set in a way that guarantees uniqueness + # of the result + + s = _lr_goto_cache.get(x,None) + if not s: + s = { } + _lr_goto_cache[x] = s + + gs = [ ] + for p in I: + n = p.lr_next + if n and n.lrbefore == x: + s1 = s.get(id(n),None) + if not s1: + s1 = { } + s[id(n)] = s1 + gs.append(n) + s = s1 + g = s.get('$end',None) + if not g: + if gs: + g = lr0_closure(gs) + s['$end'] = g + else: + s['$end'] = gs + _lr_goto_cache[(id(I),x)] = g + return g + +_lr0_cidhash = { } + +# Compute the LR(0) sets of item function +def lr0_items(): + + C = [ lr0_closure([Productions[0].lr_next]) ] + i = 0 + for I in C: + _lr0_cidhash[id(I)] = i + i += 1 + + # Loop over the items in C and each grammar symbols + i = 0 + while i < len(C): + I = C[i] + i += 1 + + # Collect all of the symbols that could possibly be in the goto(I,X) sets + asyms = { } + for ii in I: + for s in ii.usyms: + asyms[s] = None + + for x in asyms.keys(): + g = lr0_goto(I,x) + if not g: continue + if _lr0_cidhash.has_key(id(g)): continue + _lr0_cidhash[id(g)] = len(C) + C.append(g) + + return C + +# ----------------------------------------------------------------------------- +# ==== LALR(1) Parsing ==== +# +# LALR(1) parsing is almost exactly the same as SLR except that instead of +# relying upon Follow() sets when performing reductions, a more selective +# lookahead set that incorporates the state of the LR(0) machine is utilized. +# Thus, we mainly just have to focus on calculating the lookahead sets. +# +# The method used here is due to DeRemer and Pennelo (1982). +# +# DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) +# Lookahead Sets", ACM Transactions on Programming Languages and Systems, +# Vol. 4, No. 4, Oct. 1982, pp. 615-649 +# +# Further details can also be found in: +# +# J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", +# McGraw-Hill Book Company, (1985). +# +# Note: This implementation is a complete replacement of the LALR(1) +# implementation in PLY-1.x releases. That version was based on +# a less efficient algorithm and it had bugs in its implementation. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# compute_nullable_nonterminals() +# +# Creates a dictionary containing all of the non-terminals that might produce +# an empty production. +# ----------------------------------------------------------------------------- + +def compute_nullable_nonterminals(): + nullable = {} + num_nullable = 0 + while 1: + for p in Productions[1:]: + if p.len == 0: + nullable[p.name] = 1 + continue + for t in p.prod: + if not nullable.has_key(t): break + else: + nullable[p.name] = 1 + if len(nullable) == num_nullable: break + num_nullable = len(nullable) + return nullable + +# ----------------------------------------------------------------------------- +# find_nonterminal_trans(C) +# +# Given a set of LR(0) items, this functions finds all of the non-terminal +# transitions. These are transitions in which a dot appears immediately before +# a non-terminal. Returns a list of tuples of the form (state,N) where state +# is the state number and N is the nonterminal symbol. +# +# The input C is the set of LR(0) items. +# ----------------------------------------------------------------------------- + +def find_nonterminal_transitions(C): + trans = [] + for state in range(len(C)): + for p in C[state]: + if p.lr_index < p.len - 1: + t = (state,p.prod[p.lr_index+1]) + if Nonterminals.has_key(t[1]): + if t not in trans: trans.append(t) + state = state + 1 + return trans + +# ----------------------------------------------------------------------------- +# dr_relation() +# +# Computes the DR(p,A) relationships for non-terminal transitions. The input +# is a tuple (state,N) where state is a number and N is a nonterminal symbol. +# +# Returns a list of terminals. +# ----------------------------------------------------------------------------- + +def dr_relation(C,trans,nullable): + dr_set = { } + state,N = trans + terms = [] + + g = lr0_goto(C[state],N) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index+1] + if Terminals.has_key(a): + if a not in terms: terms.append(a) + + # This extra bit is to handle the start state + if state == 0 and N == Productions[0].prod[0]: + terms.append('$end') + + return terms + +# ----------------------------------------------------------------------------- +# reads_relation() +# +# Computes the READS() relation (p,A) READS (t,C). +# ----------------------------------------------------------------------------- + +def reads_relation(C, trans, empty): + # Look for empty transitions + rel = [] + state, N = trans + + g = lr0_goto(C[state],N) + j = _lr0_cidhash.get(id(g),-1) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index + 1] + if empty.has_key(a): + rel.append((j,a)) + + return rel + +# ----------------------------------------------------------------------------- +# compute_lookback_includes() +# +# Determines the lookback and includes relations +# +# LOOKBACK: +# +# This relation is determined by running the LR(0) state machine forward. +# For example, starting with a production "N : . A B C", we run it forward +# to obtain "N : A B C ." We then build a relationship between this final +# state and the starting state. These relationships are stored in a dictionary +# lookdict. +# +# INCLUDES: +# +# Computes the INCLUDE() relation (p,A) INCLUDES (p',B). +# +# This relation is used to determine non-terminal transitions that occur +# inside of other non-terminal transition states. (p,A) INCLUDES (p', B) +# if the following holds: +# +# B -> LAT, where T -> epsilon and p' -L-> p +# +# L is essentially a prefix (which may be empty), T is a suffix that must be +# able to derive an empty string. State p' must lead to state p with the string L. +# +# ----------------------------------------------------------------------------- + +def compute_lookback_includes(C,trans,nullable): + + lookdict = {} # Dictionary of lookback relations + includedict = {} # Dictionary of include relations + + # Make a dictionary of non-terminal transitions + dtrans = {} + for t in trans: + dtrans[t] = 1 + + # Loop over all transitions and compute lookbacks and includes + for state,N in trans: + lookb = [] + includes = [] + for p in C[state]: + if p.name != N: continue + + # Okay, we have a name match. We now follow the production all the way + # through the state machine until we get the . on the right hand side + + lr_index = p.lr_index + j = state + while lr_index < p.len - 1: + lr_index = lr_index + 1 + t = p.prod[lr_index] + + # Check to see if this symbol and state are a non-terminal transition + if dtrans.has_key((j,t)): + # Yes. Okay, there is some chance that this is an includes relation + # the only way to know for certain is whether the rest of the + # production derives empty + + li = lr_index + 1 + while li < p.len: + if Terminals.has_key(p.prod[li]): break # No forget it + if not nullable.has_key(p.prod[li]): break + li = li + 1 + else: + # Appears to be a relation between (j,t) and (state,N) + includes.append((j,t)) + + g = lr0_goto(C[j],t) # Go to next set + j = _lr0_cidhash.get(id(g),-1) # Go to next state + + # When we get here, j is the final state, now we have to locate the production + for r in C[j]: + if r.name != p.name: continue + if r.len != p.len: continue + i = 0 + # This look is comparing a production ". A B C" with "A B C ." + while i < r.lr_index: + if r.prod[i] != p.prod[i+1]: break + i = i + 1 + else: + lookb.append((j,r)) + for i in includes: + if not includedict.has_key(i): includedict[i] = [] + includedict[i].append((state,N)) + lookdict[(state,N)] = lookb + + return lookdict,includedict + +# ----------------------------------------------------------------------------- +# digraph() +# traverse() +# +# The following two functions are used to compute set valued functions +# of the form: +# +# F(x) = F'(x) U U{F(y) | x R y} +# +# This is used to compute the values of Read() sets as well as FOLLOW sets +# in LALR(1) generation. +# +# Inputs: X - An input set +# R - A relation +# FP - Set-valued function +# ------------------------------------------------------------------------------ + +def digraph(X,R,FP): + N = { } + for x in X: + N[x] = 0 + stack = [] + F = { } + for x in X: + if N[x] == 0: traverse(x,N,stack,F,X,R,FP) + return F + +def traverse(x,N,stack,F,X,R,FP): + stack.append(x) + d = len(stack) + N[x] = d + F[x] = FP(x) # F(X) <- F'(x) + + rel = R(x) # Get y's related to x + for y in rel: + if N[y] == 0: + traverse(y,N,stack,F,X,R,FP) + N[x] = min(N[x],N[y]) + for a in F.get(y,[]): + if a not in F[x]: F[x].append(a) + if N[x] == d: + N[stack[-1]] = sys.maxint + F[stack[-1]] = F[x] + element = stack.pop() + while element != x: + N[stack[-1]] = sys.maxint + F[stack[-1]] = F[x] + element = stack.pop() + +# ----------------------------------------------------------------------------- +# compute_read_sets() +# +# Given a set of LR(0) items, this function computes the read sets. +# +# Inputs: C = Set of LR(0) items +# ntrans = Set of nonterminal transitions +# nullable = Set of empty transitions +# +# Returns a set containing the read sets +# ----------------------------------------------------------------------------- + +def compute_read_sets(C, ntrans, nullable): + FP = lambda x: dr_relation(C,x,nullable) + R = lambda x: reads_relation(C,x,nullable) + F = digraph(ntrans,R,FP) + return F + +# ----------------------------------------------------------------------------- +# compute_follow_sets() +# +# Given a set of LR(0) items, a set of non-terminal transitions, a readset, +# and an include set, this function computes the follow sets +# +# Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} +# +# Inputs: +# ntrans = Set of nonterminal transitions +# readsets = Readset (previously computed) +# inclsets = Include sets (previously computed) +# +# Returns a set containing the follow sets +# ----------------------------------------------------------------------------- + +def compute_follow_sets(ntrans,readsets,inclsets): + FP = lambda x: readsets[x] + R = lambda x: inclsets.get(x,[]) + F = digraph(ntrans,R,FP) + return F + +# ----------------------------------------------------------------------------- +# add_lookaheads() +# +# Attaches the lookahead symbols to grammar rules. +# +# Inputs: lookbacks - Set of lookback relations +# followset - Computed follow set +# +# This function directly attaches the lookaheads to productions contained +# in the lookbacks set +# ----------------------------------------------------------------------------- + +def add_lookaheads(lookbacks,followset): + for trans,lb in lookbacks.items(): + # Loop over productions in lookback + for state,p in lb: + if not p.lookaheads.has_key(state): + p.lookaheads[state] = [] + f = followset.get(trans,[]) + for a in f: + if a not in p.lookaheads[state]: p.lookaheads[state].append(a) + +# ----------------------------------------------------------------------------- +# add_lalr_lookaheads() +# +# This function does all of the work of adding lookahead information for use +# with LALR parsing +# ----------------------------------------------------------------------------- + +def add_lalr_lookaheads(C): + # Determine all of the nullable nonterminals + nullable = compute_nullable_nonterminals() + + # Find all non-terminal transitions + trans = find_nonterminal_transitions(C) + + # Compute read sets + readsets = compute_read_sets(C,trans,nullable) + + # Compute lookback/includes relations + lookd, included = compute_lookback_includes(C,trans,nullable) + + # Compute LALR FOLLOW sets + followsets = compute_follow_sets(trans,readsets,included) + + # Add all of the lookaheads + add_lookaheads(lookd,followsets) + +# ----------------------------------------------------------------------------- +# lr_parse_table() +# +# This function constructs the parse tables for SLR or LALR +# ----------------------------------------------------------------------------- +def lr_parse_table(method): + global _lr_method + goto = _lr_goto # Goto array + action = _lr_action # Action array + actionp = { } # Action production array (temporary) + + _lr_method = method + + n_srconflict = 0 + n_rrconflict = 0 + + if yaccdebug: + sys.stderr.write("yacc: Generating %s parsing table...\n" % method) + _vf.write("\n\nParsing method: %s\n\n" % method) + + # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items + # This determines the number of states + + C = lr0_items() + + if method == 'LALR': + add_lalr_lookaheads(C) + + # Build the parser table, state by state + st = 0 + for I in C: + # Loop over each production in I + actlist = [ ] # List of actions + + if yaccdebug: + _vf.write("\nstate %d\n\n" % st) + for p in I: + _vf.write(" (%d) %s\n" % (p.number, str(p))) + _vf.write("\n") + + for p in I: + try: + if p.prod[-1] == ".": + if p.name == "S'": + # Start symbol. Accept! + action[st,"$end"] = 0 + actionp[st,"$end"] = p + else: + # We are at the end of a production. Reduce! + if method == 'LALR': + laheads = p.lookaheads[st] + else: + laheads = Follow[p.name] + for a in laheads: + actlist.append((a,p,"reduce using rule %d (%s)" % (p.number,p))) + r = action.get((st,a),None) + if r is not None: + # Whoa. Have a shift/reduce or reduce/reduce conflict + if r > 0: + # Need to decide on shift or reduce here + # By default we favor shifting. Need to add + # some precedence rules here. + sprec,slevel = Productions[actionp[st,a].number].prec + rprec,rlevel = Precedence.get(a,('right',0)) + if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): + # We really need to reduce here. + action[st,a] = -p.number + actionp[st,a] = p + if not slevel and not rlevel: + _vfc.write("shift/reduce conflict in state %d resolved as reduce.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as reduce.\n" % a) + n_srconflict += 1 + elif (slevel == rlevel) and (rprec == 'nonassoc'): + action[st,a] = None + else: + # Hmmm. Guess we'll keep the shift + if not rlevel: + _vfc.write("shift/reduce conflict in state %d resolved as shift.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as shift.\n" % a) + n_srconflict +=1 + elif r < 0: + # Reduce/reduce conflict. In this case, we favor the rule + # that was defined first in the grammar file + oldp = Productions[-r] + pp = Productions[p.number] + if oldp.line > pp.line: + action[st,a] = -p.number + actionp[st,a] = p + # sys.stderr.write("Reduce/reduce conflict in state %d\n" % st) + n_rrconflict += 1 + _vfc.write("reduce/reduce conflict in state %d resolved using rule %d (%s).\n" % (st, actionp[st,a].number, actionp[st,a])) + _vf.write(" ! reduce/reduce conflict for %s resolved using rule %d (%s).\n" % (a,actionp[st,a].number, actionp[st,a])) + else: + sys.stderr.write("Unknown conflict in state %d\n" % st) + else: + action[st,a] = -p.number + actionp[st,a] = p + else: + i = p.lr_index + a = p.prod[i+1] # Get symbol right after the "." + if Terminals.has_key(a): + g = lr0_goto(I,a) + j = _lr0_cidhash.get(id(g),-1) + if j >= 0: + # We are in a shift state + actlist.append((a,p,"shift and go to state %d" % j)) + r = action.get((st,a),None) + if r is not None: + # Whoa have a shift/reduce or shift/shift conflict + if r > 0: + if r != j: + sys.stderr.write("Shift/shift conflict in state %d\n" % st) + elif r < 0: + # Do a precedence check. + # - if precedence of reduce rule is higher, we reduce. + # - if precedence of reduce is same and left assoc, we reduce. + # - otherwise we shift + rprec,rlevel = Productions[actionp[st,a].number].prec + sprec,slevel = Precedence.get(a,('right',0)) + if (slevel > rlevel) or ((slevel == rlevel) and (rprec != 'left')): + # We decide to shift here... highest precedence to shift + action[st,a] = j + actionp[st,a] = p + if not rlevel: + n_srconflict += 1 + _vfc.write("shift/reduce conflict in state %d resolved as shift.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as shift.\n" % a) + elif (slevel == rlevel) and (rprec == 'nonassoc'): + action[st,a] = None + else: + # Hmmm. Guess we'll keep the reduce + if not slevel and not rlevel: + n_srconflict +=1 + _vfc.write("shift/reduce conflict in state %d resolved as reduce.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as reduce.\n" % a) + + else: + sys.stderr.write("Unknown conflict in state %d\n" % st) + else: + action[st,a] = j + actionp[st,a] = p + + except StandardError,e: + raise YaccError, "Hosed in lr_parse_table", e + + # Print the actions associated with each terminal + if yaccdebug: + _actprint = { } + for a,p,m in actlist: + if action.has_key((st,a)): + if p is actionp[st,a]: + _vf.write(" %-15s %s\n" % (a,m)) + _actprint[(a,m)] = 1 + _vf.write("\n") + for a,p,m in actlist: + if action.has_key((st,a)): + if p is not actionp[st,a]: + if not _actprint.has_key((a,m)): + _vf.write(" ! %-15s [ %s ]\n" % (a,m)) + _actprint[(a,m)] = 1 + + # Construct the goto table for this state + if yaccdebug: + _vf.write("\n") + nkeys = { } + for ii in I: + for s in ii.usyms: + if Nonterminals.has_key(s): + nkeys[s] = None + for n in nkeys.keys(): + g = lr0_goto(I,n) + j = _lr0_cidhash.get(id(g),-1) + if j >= 0: + goto[st,n] = j + if yaccdebug: + _vf.write(" %-30s shift and go to state %d\n" % (n,j)) + + st += 1 + + if yaccdebug: + if n_srconflict == 1: + sys.stderr.write("yacc: %d shift/reduce conflict\n" % n_srconflict) + if n_srconflict > 1: + sys.stderr.write("yacc: %d shift/reduce conflicts\n" % n_srconflict) + if n_rrconflict == 1: + sys.stderr.write("yacc: %d reduce/reduce conflict\n" % n_rrconflict) + if n_rrconflict > 1: + sys.stderr.write("yacc: %d reduce/reduce conflicts\n" % n_rrconflict) + +# ----------------------------------------------------------------------------- +# ==== LR Utility functions ==== +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# _lr_write_tables() +# +# This function writes the LR parsing tables to a file +# ----------------------------------------------------------------------------- + +def lr_write_tables(modulename=tab_module,outputdir=''): + filename = os.path.join(outputdir,modulename) + ".py" + try: + f = open(filename,"w") + + f.write(""" +# %s +# This file is automatically generated. Do not edit. + +_lr_method = %s + +_lr_signature = %s +""" % (filename, repr(_lr_method), repr(Signature.digest()))) + + # Change smaller to 0 to go back to original tables + smaller = 1 + + # Factor out names to try and make smaller + if smaller: + items = { } + + for k,v in _lr_action.items(): + i = items.get(k[1]) + if not i: + i = ([],[]) + items[k[1]] = i + i[0].append(k[0]) + i[1].append(v) + + f.write("\n_lr_action_items = {") + for k,v in items.items(): + f.write("%r:([" % k) + for i in v[0]: + f.write("%r," % i) + f.write("],[") + for i in v[1]: + f.write("%r," % i) + + f.write("]),") + f.write("}\n") + + f.write(""" +_lr_action = { } +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + _lr_action[(_x,_k)] = _y +del _lr_action_items +""") + + else: + f.write("\n_lr_action = { "); + for k,v in _lr_action.items(): + f.write("(%r,%r):%r," % (k[0],k[1],v)) + f.write("}\n"); + + if smaller: + # Factor out names to try and make smaller + items = { } + + for k,v in _lr_goto.items(): + i = items.get(k[1]) + if not i: + i = ([],[]) + items[k[1]] = i + i[0].append(k[0]) + i[1].append(v) + + f.write("\n_lr_goto_items = {") + for k,v in items.items(): + f.write("%r:([" % k) + for i in v[0]: + f.write("%r," % i) + f.write("],[") + for i in v[1]: + f.write("%r," % i) + + f.write("]),") + f.write("}\n") + + f.write(""" +_lr_goto = { } +for _k, _v in _lr_goto_items.items(): + for _x,_y in zip(_v[0],_v[1]): + _lr_goto[(_x,_k)] = _y +del _lr_goto_items +""") + else: + f.write("\n_lr_goto = { "); + for k,v in _lr_goto.items(): + f.write("(%r,%r):%r," % (k[0],k[1],v)) + f.write("}\n"); + + # Write production table + f.write("_lr_productions = [\n") + for p in Productions: + if p: + if (p.func): + f.write(" (%r,%d,%r,%r,%d),\n" % (p.name, p.len, p.func.__name__,p.file,p.line)) + else: + f.write(" (%r,%d,None,None,None),\n" % (p.name, p.len)) + else: + f.write(" None,\n") + f.write("]\n") + + f.close() + + except IOError,e: + print "Unable to create '%s'" % filename + print e + return + +def lr_read_tables(module=tab_module,optimize=0): + global _lr_action, _lr_goto, _lr_productions, _lr_method + try: + exec "import %s as parsetab" % module + + if (optimize) or (Signature.digest() == parsetab._lr_signature): + _lr_action = parsetab._lr_action + _lr_goto = parsetab._lr_goto + _lr_productions = parsetab._lr_productions + _lr_method = parsetab._lr_method + return 1 + else: + return 0 + + except (ImportError,AttributeError): + return 0 + + +# Available instance types. This is used when parsers are defined by a class. +# it's a little funky because I want to preserve backwards compatibility +# with Python 2.0 where types.ObjectType is undefined. + +try: + _INSTANCETYPE = (types.InstanceType, types.ObjectType) +except AttributeError: + _INSTANCETYPE = types.InstanceType + +# ----------------------------------------------------------------------------- +# yacc(module) +# +# Build the parser module +# ----------------------------------------------------------------------------- + +def yacc(method=default_lr, debug=yaccdebug, module=None, tabmodule=tab_module, start=None, check_recursion=1, optimize=0,write_tables=1,debugfile=debug_file,outputdir=''): + global yaccdebug + yaccdebug = debug + + initialize_vars() + files = { } + error = 0 + + + # Add parsing method to signature + Signature.update(method) + + # If a "module" parameter was supplied, extract its dictionary. + # Note: a module may in fact be an instance as well. + + if module: + # User supplied a module object. + if isinstance(module, types.ModuleType): + ldict = module.__dict__ + elif isinstance(module, _INSTANCETYPE): + _items = [(k,getattr(module,k)) for k in dir(module)] + ldict = { } + for i in _items: + ldict[i[0]] = i[1] + else: + raise ValueError,"Expected a module" + + else: + # No module given. We might be able to get information from the caller. + # Throw an exception and unwind the traceback to get the globals + + try: + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + f = f.f_back # Walk out to our calling function + ldict = f.f_globals # Grab its globals dictionary + + # Add starting symbol to signature + if not start: + start = ldict.get("start",None) + if start: + Signature.update(start) + + # If running in optimized mode. We're going to + + if (optimize and lr_read_tables(tabmodule,1)): + # Read parse table + del Productions[:] + for p in _lr_productions: + if not p: + Productions.append(None) + else: + m = MiniProduction() + m.name = p[0] + m.len = p[1] + m.file = p[3] + m.line = p[4] + if p[2]: + m.func = ldict[p[2]] + Productions.append(m) + + else: + # Get the tokens map + if (module and isinstance(module,_INSTANCETYPE)): + tokens = getattr(module,"tokens",None) + else: + tokens = ldict.get("tokens",None) + + if not tokens: + raise YaccError,"module does not define a list 'tokens'" + if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)): + raise YaccError,"tokens must be a list or tuple." + + # Check to see if a requires dictionary is defined. + requires = ldict.get("require",None) + if requires: + if not (isinstance(requires,types.DictType)): + raise YaccError,"require must be a dictionary." + + for r,v in requires.items(): + try: + if not (isinstance(v,types.ListType)): + raise TypeError + v1 = [x.split(".") for x in v] + Requires[r] = v1 + except StandardError: + print "Invalid specification for rule '%s' in require. Expected a list of strings" % r + + + # Build the dictionary of terminals. We a record a 0 in the + # dictionary to track whether or not a terminal is actually + # used in the grammar + + if 'error' in tokens: + print "yacc: Illegal token 'error'. Is a reserved word." + raise YaccError,"Illegal token name" + + for n in tokens: + if Terminals.has_key(n): + print "yacc: Warning. Token '%s' multiply defined." % n + Terminals[n] = [ ] + + Terminals['error'] = [ ] + + # Get the precedence map (if any) + prec = ldict.get("precedence",None) + if prec: + if not (isinstance(prec,types.ListType) or isinstance(prec,types.TupleType)): + raise YaccError,"precedence must be a list or tuple." + add_precedence(prec) + Signature.update(repr(prec)) + + for n in tokens: + if not Precedence.has_key(n): + Precedence[n] = ('right',0) # Default, right associative, 0 precedence + + # Look for error handler + ef = ldict.get('p_error',None) + if ef: + if isinstance(ef,types.FunctionType): + ismethod = 0 + elif isinstance(ef, types.MethodType): + ismethod = 1 + else: + raise YaccError,"'p_error' defined, but is not a function or method." + eline = ef.func_code.co_firstlineno + efile = ef.func_code.co_filename + files[efile] = None + + if (ef.func_code.co_argcount != 1+ismethod): + raise YaccError,"%s:%d: p_error() requires 1 argument." % (efile,eline) + global Errorfunc + Errorfunc = ef + else: + print "yacc: Warning. no p_error() function is defined." + + # Get the list of built-in functions with p_ prefix + symbols = [ldict[f] for f in ldict.keys() + if (type(ldict[f]) in (types.FunctionType, types.MethodType) and ldict[f].__name__[:2] == 'p_' + and ldict[f].__name__ != 'p_error')] + + # Check for non-empty symbols + if len(symbols) == 0: + raise YaccError,"no rules of the form p_rulename are defined." + + # Sort the symbols by line number + symbols.sort(lambda x,y: cmp(x.func_code.co_firstlineno,y.func_code.co_firstlineno)) + + # Add all of the symbols to the grammar + for f in symbols: + if (add_function(f)) < 0: + error += 1 + else: + files[f.func_code.co_filename] = None + + # Make a signature of the docstrings + for f in symbols: + if f.__doc__: + Signature.update(f.__doc__) + + lr_init_vars() + + if error: + raise YaccError,"Unable to construct parser." + + if not lr_read_tables(tabmodule): + + # Validate files + for filename in files.keys(): + if not validate_file(filename): + error = 1 + + # Validate dictionary + validate_dict(ldict) + + if start and not Prodnames.has_key(start): + raise YaccError,"Bad starting symbol '%s'" % start + + augment_grammar(start) + error = verify_productions(cycle_check=check_recursion) + otherfunc = [ldict[f] for f in ldict.keys() + if (type(f) in (types.FunctionType,types.MethodType) and ldict[f].__name__[:2] != 'p_')] + + if error: + raise YaccError,"Unable to construct parser." + + build_lritems() + compute_first1() + compute_follow(start) + + if method in ['SLR','LALR']: + lr_parse_table(method) + else: + raise YaccError, "Unknown parsing method '%s'" % method + + if write_tables: + lr_write_tables(tabmodule,outputdir) + + if yaccdebug: + try: + f = open(os.path.join(outputdir,debugfile),"w") + f.write(_vfc.getvalue()) + f.write("\n\n") + f.write(_vf.getvalue()) + f.close() + except IOError,e: + print "yacc: can't create '%s'" % debugfile,e + + # Made it here. Create a parser object and set up its internal state. + # Set global parse() method to bound method of parser object. + + p = Parser("xyzzy") + p.productions = Productions + p.errorfunc = Errorfunc + p.action = _lr_action + p.goto = _lr_goto + p.method = _lr_method + p.require = Requires + + global parse + parse = p.parse + + global parser + parser = p + + # Clean up all of the globals we created + if (not optimize): + yacc_cleanup() + return p + +# yacc_cleanup function. Delete all of the global variables +# used during table construction + +def yacc_cleanup(): + global _lr_action, _lr_goto, _lr_method, _lr_goto_cache + del _lr_action, _lr_goto, _lr_method, _lr_goto_cache + + global Productions, Prodnames, Prodmap, Terminals + global Nonterminals, First, Follow, Precedence, LRitems + global Errorfunc, Signature, Requires + + del Productions, Prodnames, Prodmap, Terminals + del Nonterminals, First, Follow, Precedence, LRitems + del Errorfunc, Signature, Requires + + global _vf, _vfc + del _vf, _vfc + + +# Stub that raises an error if parsing is attempted without first calling yacc() +def parse(*args,**kwargs): + raise YaccError, "yacc: No parser built with yacc()" + diff --git a/lib/python2.7/site-packages/setools/__init__.py b/lib/python2.7/site-packages/setools/__init__.py new file mode 100644 index 0000000..4d03553 --- /dev/null +++ b/lib/python2.7/site-packages/setools/__init__.py @@ -0,0 +1,68 @@ +"""The SETools SELinux policy analysis library.""" +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +#try: +# import pkg_resources +# # pylint: disable=no-member +# __version__ = pkg_resources.get_distribution("setools").version +#except ImportError: # pragma: no cover +# __version__ = "unknown" +__version__ = "3.3.8" + +# Python classes for policy representation +from . import policyrep +from .policyrep import SELinuxPolicy + +# Exceptions +from . import exception + +# Component Queries +from .boolquery import BoolQuery +from .categoryquery import CategoryQuery +from .commonquery import CommonQuery +from .objclassquery import ObjClassQuery +from .polcapquery import PolCapQuery +from .rolequery import RoleQuery +from .sensitivityquery import SensitivityQuery +from .typequery import TypeQuery +from .typeattrquery import TypeAttributeQuery +from .userquery import UserQuery + +# Rule Queries +from .mlsrulequery import MLSRuleQuery +from .rbacrulequery import RBACRuleQuery +from .terulequery import TERuleQuery + +# Constraint queries +from .constraintquery import ConstraintQuery + +# In-policy Context Queries +from .fsusequery import FSUseQuery +from .genfsconquery import GenfsconQuery +from .initsidquery import InitialSIDQuery +from .netifconquery import NetifconQuery +from .nodeconquery import NodeconQuery +from .portconquery import PortconQuery + +# Information Flow Analysis +from .infoflow import InfoFlowAnalysis +from .permmap import PermissionMap + +# Domain Transition Analysis +from .dta import DomainTransitionAnalysis diff --git a/lib/python2.7/site-packages/setools/boolquery.py b/lib/python2.7/site-packages/setools/boolquery.py new file mode 100644 index 0000000..b70b7d5 --- /dev/null +++ b/lib/python2.7/site-packages/setools/boolquery.py @@ -0,0 +1,66 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from .descriptors import CriteriaDescriptor + + +class BoolQuery(compquery.ComponentQuery): + + """Query SELinux policy Booleans. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The Boolean name to match. + name_regex If true, regular expression matching + will be used on the Boolean name. + default The default state to match. If this + is None, the default state not be matched. + """ + + _default = None + + @property + def default(self): + return self._default + + @default.setter + def default(self, value): + if value is None: + self._default = None + else: + self._default = bool(value) + + def results(self): + """Generator which yields all Booleans matching the criteria.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Default: {0.default}".format(self)) + + for boolean in self.policy.bools(): + if not self._match_name(boolean): + continue + + if self.default is not None and boolean.state != self.default: + continue + + yield boolean diff --git a/lib/python2.7/site-packages/setools/categoryquery.py b/lib/python2.7/site-packages/setools/categoryquery.py new file mode 100644 index 0000000..d4d7c4c --- /dev/null +++ b/lib/python2.7/site-packages/setools/categoryquery.py @@ -0,0 +1,55 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import mixins + + +class CategoryQuery(mixins.MatchAlias, compquery.ComponentQuery): + + """ + Query MLS Categories + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the category to match. + name_regex If true, regular expression matching will + be used for matching the name. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + """ + + def results(self): + """Generator which yields all matching categories.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + + for cat in self.policy.categories(): + if not self._match_name(cat): + continue + + if not self._match_alias(cat): + continue + + yield cat diff --git a/lib/python2.7/site-packages/setools/commonquery.py b/lib/python2.7/site-packages/setools/commonquery.py new file mode 100644 index 0000000..e105ccb --- /dev/null +++ b/lib/python2.7/site-packages/setools/commonquery.py @@ -0,0 +1,60 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery, mixins + + +class CommonQuery(mixins.MatchPermission, compquery.ComponentQuery): + + """ + Query common permission sets. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the common to match. + name_regex If true, regular expression matching will + be used for matching the name. + perms The permissions to match. + perms_equal If true, only commons with permission sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + """ + + def results(self): + """Generator which yields all matching commons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + + for com in self.policy.commons(): + if not self._match_name(com): + continue + + if not self._match_perms(com): + continue + + yield com diff --git a/lib/python2.7/site-packages/setools/compquery.py b/lib/python2.7/site-packages/setools/compquery.py new file mode 100644 index 0000000..3d8851a --- /dev/null +++ b/lib/python2.7/site-packages/setools/compquery.py @@ -0,0 +1,39 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=no-member,attribute-defined-outside-init,abstract-method +import re + +from . import query +from .descriptors import CriteriaDescriptor + + +class ComponentQuery(query.PolicyQuery): + + """Base class for SETools component queries.""" + + name = CriteriaDescriptor("name_regex") + name_regex = False + + def _match_name(self, obj): + """Match the object to the name criteria.""" + if not self.name: + # if there is no criteria, everything matches. + return True + + return self._match_regex(obj, self.name, self.name_regex) diff --git a/lib/python2.7/site-packages/setools/constraintquery.py b/lib/python2.7/site-packages/setools/constraintquery.py new file mode 100644 index 0000000..82a6fc2 --- /dev/null +++ b/lib/python2.7/site-packages/setools/constraintquery.py @@ -0,0 +1,142 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor +from .policyrep.exception import ConstraintUseError + + +class ConstraintQuery(mixins.MatchObjClass, mixins.MatchPermission, query.PolicyQuery): + + """ + Query constraint rules, (mls)constrain/(mls)validatetrans. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + perms The permission(s) to match. + perms_equal If true, the permission set of the rule + must exactly match the permissions + criteria. If false, any set intersection + will match. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + role The name of the role to match in the + constraint expression. + role_indirect If true, members of an attribute will be + matched rather than the attribute itself. + role_regex If true, regular expression matching will + be used on the role. + type_ The name of the type/attribute to match in the + constraint expression. + type_indirect If true, members of an attribute will be + matched rather than the attribute itself. + type_regex If true, regular expression matching will + be used on the type/attribute. + user The name of the user to match in the + constraint expression. + user_regex If true, regular expression matching will + be used on the user. + """ + + ruletype = RuletypeDescriptor("validate_constraint_ruletype") + user = CriteriaDescriptor("user_regex", "lookup_user") + user_regex = False + role = CriteriaDescriptor("role_regex", "lookup_role") + role_regex = False + role_indirect = True + type_ = CriteriaDescriptor("type_regex", "lookup_type_or_attr") + type_regex = False + type_indirect = True + + def _match_expr(self, expr, criteria, indirect, regex): + """ + Match roles/types/users in a constraint expression, + optionally by expanding the contents of attributes. + + Parameters: + expr The expression to match. + criteria The criteria to match. + indirect If attributes in the expression should be expanded. + regex If regular expression matching should be used. + """ + + if indirect: + obj = set() + for item in expr: + obj.update(item.expand()) + else: + obj = expr + + return self._match_in_set(obj, criteria, regex) + + def results(self): + """Generator which yields all matching constraints rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + + for c in self.policy.constraints(): + if self.ruletype: + if c.ruletype not in self.ruletype: + continue + + if not self._match_object_class(c): + continue + + try: + if not self._match_perms(c): + continue + except ConstraintUseError: + continue + + if self.role and not self._match_expr( + c.roles, + self.role, + self.role_indirect, + self.role_regex): + continue + + if self.type_ and not self._match_expr( + c.types, + self.type_, + self.type_indirect, + self.type_regex): + continue + + if self.user and not self._match_expr( + c.users, + self.user, + False, + self.user_regex): + continue + + yield c diff --git a/lib/python2.7/site-packages/setools/contextquery.py b/lib/python2.7/site-packages/setools/contextquery.py new file mode 100644 index 0000000..5ce1632 --- /dev/null +++ b/lib/python2.7/site-packages/setools/contextquery.py @@ -0,0 +1,98 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=attribute-defined-outside-init,no-member +import re + +from . import query +from .descriptors import CriteriaDescriptor + + +class ContextQuery(query.PolicyQuery): + + """ + Base class for SETools in-policy labeling/context queries. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + context The object to match. + user The user to match in the context. + user_regex If true, regular expression matching + will be used on the user. + role The role to match in the context. + role_regex If true, regular expression matching + will be used on the role. + type_ The type to match in the context. + type_regex If true, regular expression matching + will be used on the type. + range_ The range to match in the context. + range_subset If true, the criteria will match if it + is a subset of the context's range. + range_overlap If true, the criteria will match if it + overlaps any of the context's range. + range_superset If true, the criteria will match if it + is a superset of the context's range. + range_proper If true, use proper superset/subset + on range matching operations. + No effect if not using set operations. + """ + + user = CriteriaDescriptor("user_regex", "lookup_user") + user_regex = False + role = CriteriaDescriptor("role_regex", "lookup_role") + role_regex = False + type_ = CriteriaDescriptor("type_regex", "lookup_type") + type_regex = False + range_ = CriteriaDescriptor(lookup_function="lookup_range") + range_overlap = False + range_subset = False + range_superset = False + range_proper = False + + def _match_context(self, context): + + if self.user and not query.PolicyQuery._match_regex( + context.user, + self.user, + self.user_regex): + return False + + if self.role and not query.PolicyQuery._match_regex( + context.role, + self.role, + self.role_regex): + return False + + if self.type_ and not query.PolicyQuery._match_regex( + context.type_, + self.type_, + self.type_regex): + return False + + if self.range_ and not query.PolicyQuery._match_range( + context.range_, + self.range_, + self.range_subset, + self.range_overlap, + self.range_superset, + self.range_proper): + return False + + return True diff --git a/lib/python2.7/site-packages/setools/descriptors.py b/lib/python2.7/site-packages/setools/descriptors.py new file mode 100644 index 0000000..eab9210 --- /dev/null +++ b/lib/python2.7/site-packages/setools/descriptors.py @@ -0,0 +1,230 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +""" +SETools descriptors. + +These classes override how a class's attributes are get/set/deleted. +This is how the @property decorator works. + +See https://docs.python.org/3/howto/descriptor.html +for more details. +""" + +import re +from collections import defaultdict +from weakref import WeakKeyDictionary + +# +# Query criteria descriptors +# +# Implementation note: if the name_regex attribute value +# is changed the criteria must be reset. +# + + +class CriteriaDescriptor(object): + + """ + Single item criteria descriptor. + + Parameters: + name_regex The name of instance's regex setting attribute; + used as name_regex below. If unset, + regular expressions will never be used. + lookup_function The name of the SELinuxPolicy lookup function, + e.g. lookup_type or lookup_boolean. + default_value The default value of the criteria. The default + is None. + + Read-only instance attribute use (obj parameter): + policy The instance of SELinuxPolicy + name_regex This attribute is read to determine if + the criteria should be looked up or + compiled into a regex. If the attribute + does not exist, False is assumed. + """ + + def __init__(self, name_regex=None, lookup_function=None, default_value=None): + assert name_regex or lookup_function, "A simple attribute should be used if there is " \ + "no regex nor lookup function." + self.regex = name_regex + self.default_value = default_value + self.lookup_function = lookup_function + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return self.instances.setdefault(obj, self.default_value) + + def __set__(self, obj, value): + if not value: + self.instances[obj] = None + elif self.regex and getattr(obj, self.regex, False): + self.instances[obj] = re.compile(value) + elif self.lookup_function: + lookup = getattr(obj.policy, self.lookup_function) + self.instances[obj] = lookup(value) + else: + self.instances[obj] = value + + +class CriteriaSetDescriptor(CriteriaDescriptor): + + """Descriptor for a set of criteria.""" + + def __set__(self, obj, value): + if not value: + self.instances[obj] = None + elif self.regex and getattr(obj, self.regex, False): + self.instances[obj] = re.compile(value) + elif self.lookup_function: + lookup = getattr(obj.policy, self.lookup_function) + self.instances[obj] = set(lookup(v) for v in value) + else: + self.instances[obj] = set(value) + + +class RuletypeDescriptor(object): + + """ + Descriptor for a list of rule types. + + Parameters: + validator The name of the SELinuxPolicy ruletype + validator function, e.g. validate_te_ruletype + default_value The default value of the criteria. The default + is None. + + Read-only instance attribute use (obj parameter): + policy The instance of SELinuxPolicy + """ + + def __init__(self, validator): + self.validator = validator + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return self.instances.setdefault(obj, None) + + def __set__(self, obj, value): + if value: + validate = getattr(obj.policy, self.validator) + validate(value) + self.instances[obj] = value + else: + self.instances[obj] = None + + +# +# NetworkX Graph Descriptors +# +# These descriptors are used to simplify all +# of the dictionary use in the NetworkX graph. +# + + +class NetworkXGraphEdgeDescriptor(object): + + """ + Descriptor base class for NetworkX graph edge attributes. + + Parameter: + name The edge property name + + Instance class attribute use (obj parameter): + G The NetworkX graph + source The edge's source node + target The edge's target node + """ + + def __init__(self, propname): + self.name = propname + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return obj.G[obj.source][obj.target][self.name] + + def __set__(self, obj, value): + raise NotImplementedError + + def __delete__(self, obj): + raise NotImplementedError + + +class EdgeAttrDict(NetworkXGraphEdgeDescriptor): + + """A descriptor for edge attributes that are dictionaries.""" + + def __set__(self, obj, value): + # None is a special value to initialize the attribute + if value is None: + obj.G[obj.source][obj.target][self.name] = defaultdict(list) + else: + raise ValueError("{0} dictionaries should not be assigned directly".format(self.name)) + + def __delete__(self, obj): + obj.G[obj.source][obj.target][self.name].clear() + + +class EdgeAttrIntMax(NetworkXGraphEdgeDescriptor): + + """ + A descriptor for edge attributes that are non-negative integers that always + keep the max assigned value until re-initialized. + """ + + def __set__(self, obj, value): + # None is a special value to initialize + if value is None: + obj.G[obj.source][obj.target][self.name] = 0 + else: + current_value = obj.G[obj.source][obj.target][self.name] + obj.G[obj.source][obj.target][self.name] = max(current_value, value) + + +class EdgeAttrList(NetworkXGraphEdgeDescriptor): + + """A descriptor for edge attributes that are lists.""" + + def __set__(self, obj, value): + # None is a special value to initialize + if value is None: + obj.G[obj.source][obj.target][self.name] = [] + else: + raise ValueError("{0} lists should not be assigned directly".format(self.name)) + + def __delete__(self, obj): + # in Python3 a .clear() function was added for lists + # keep this implementation for Python 2 compat + del obj.G[obj.source][obj.target][self.name][:] diff --git a/lib/python2.7/site-packages/setools/dta.py b/lib/python2.7/site-packages/setools/dta.py new file mode 100644 index 0000000..271efc4 --- /dev/null +++ b/lib/python2.7/site-packages/setools/dta.py @@ -0,0 +1,603 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import itertools +import logging +from collections import defaultdict, namedtuple + +import networkx as nx +from networkx.exception import NetworkXError, NetworkXNoPath + +from .descriptors import EdgeAttrDict, EdgeAttrList + +__all__ = ['DomainTransitionAnalysis'] + +# Return values for the analysis +# are in the following tuple formats: +step_output = namedtuple("step", ["source", + "target", + "transition", + "entrypoints", + "setexec", + "dyntransition", + "setcurrent"]) + +entrypoint_output = namedtuple("entrypoints", ["name", + "entrypoint", + "execute", + "type_transition"]) + + +class DomainTransitionAnalysis(object): + + """Domain transition analysis.""" + + def __init__(self, policy, reverse=False, exclude=None): + """ + Parameter: + policy The policy to analyze. + """ + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + self.exclude = exclude + self.reverse = reverse + self.rebuildgraph = True + self.rebuildsubgraph = True + self.G = nx.DiGraph() + self.subG = None + + @property + def reverse(self): + return self._reverse + + @reverse.setter + def reverse(self, direction): + self._reverse = bool(direction) + self.rebuildsubgraph = True + + @property + def exclude(self): + return self._exclude + + @exclude.setter + def exclude(self, types): + if types: + self._exclude = [self.policy.lookup_type(t) for t in types] + else: + self._exclude = None + + self.rebuildsubgraph = True + + def shortest_path(self, source, target): + """ + Generator which yields one shortest domain transition path + between the source and target types (there may be more). + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating one shortest path from {0} to {1}...".format(s, t)) + + try: + yield self.__generate_steps(nx.shortest_path(self.subG, s, t)) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. excluded + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_paths(self, source, target, maxlen=2): + """ + Generator which yields all domain transition paths between + the source and target up to the specified maximum path + length. + + Parameters: + source The source type. + target The target type. + maxlen Maximum length of paths. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + if maxlen < 1: + raise ValueError("Maximum path length must be positive.") + + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all paths from {0} to {1}, max len {2}...".format(s, t, maxlen)) + + try: + for path in nx.all_simple_paths(self.subG, s, t, maxlen): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. excluded + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_shortest_paths(self, source, target): + """ + Generator which yields all shortest domain transition paths + between the source and target types. + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all shortest paths from {0} to {1}...".format(s, t)) + + try: + for path in nx.all_shortest_paths(self.subG, s, t): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError, KeyError): + # NetworkXError: the type is valid but not in graph, e.g. excluded + # NetworkXNoPath: no paths or the target type is + # not in the graph + # KeyError: work around NetworkX bug + # when the source node is not in the graph + pass + + def transitions(self, type_): + """ + Generator which yields all domain transitions out of a + specified source type. + + Parameters: + type_ The starting type. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + s = self.policy.lookup_type(type_) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all transitions {1} {0}". + format(s, "in to" if self.reverse else "out from")) + + try: + for source, target in self.subG.out_edges_iter(s): + edge = Edge(self.subG, source, target) + + if self.reverse: + real_source, real_target = target, source + else: + real_source, real_target = source, target + + yield step_output(real_source, real_target, + edge.transition, + self.__generate_entrypoints(edge), + edge.setexec, + edge.dyntransition, + edge.setcurrent) + + except NetworkXError: + # NetworkXError: the type is valid but not in graph, e.g. excluded + pass + + def get_stats(self): # pragma: no cover + """ + Get the domain transition graph statistics. + + Return: tuple(nodes, edges) + + nodes The number of nodes (types) in the graph. + edges The number of edges (domain transitions) in the graph. + """ + return (self.G.number_of_nodes(), self.G.number_of_edges()) + + # + # Internal functions follow + # + @staticmethod + def __generate_entrypoints(edge): + """ + Generator which yields the entrypoint, execute, and + type_transition rules for each entrypoint. + + Parameter: + data The dictionary of entrypoints. + + Yield: tuple(type, entry, exec, trans) + + type The entrypoint type. + entry The list of entrypoint rules. + exec The list of execute rules. + trans The list of type_transition rules. + """ + for e in edge.entrypoint: + yield entrypoint_output(e, edge.entrypoint[e], edge.execute[e], edge.type_transition[e]) + + def __generate_steps(self, path): + """ + Generator which yields the source, target, and associated rules + for each domain transition. + + Parameter: + path A list of graph node names representing an information flow path. + + Yield: tuple(source, target, transition, entrypoints, + setexec, dyntransition, setcurrent) + + source The source type for this step of the domain transition. + target The target type for this step of the domain transition. + transition The list of transition rules. + entrypoints Generator which yields entrypoint-related rules. + setexec The list of setexec rules. + dyntranstion The list of dynamic transition rules. + setcurrent The list of setcurrent rules. + """ + + for s in range(1, len(path)): + source = path[s - 1] + target = path[s] + edge = Edge(self.subG, source, target) + + # Yield the actual source and target. + # The above perspective is reversed + # if the graph has been reversed. + if self.reverse: + real_source, real_target = target, source + else: + real_source, real_target = source, target + + yield step_output(real_source, real_target, + edge.transition, + self.__generate_entrypoints(edge), + edge.setexec, + edge.dyntransition, + edge.setcurrent) + + # + # Graph building functions + # + + # Domain transition requirements: + # + # Standard transitions a->b: + # allow a b:process transition; + # allow a b_exec:file execute; + # allow b b_exec:file entrypoint; + # + # and at least one of: + # allow a self:process setexec; + # type_transition a b_exec:process b; + # + # Dynamic transition x->y: + # allow x y:process dyntransition; + # allow x self:process setcurrent; + # + # Algorithm summary: + # 1. iterate over all rules + # 1. skip non allow/type_transition rules + # 2. if process transition or dyntransition, create edge, + # initialize rule lists, add the (dyn)transition rule + # 3. if process setexec or setcurrent, add to appropriate dict + # keyed on the subject + # 4. if file exec, entrypoint, or type_transition:process, + # add to appropriate dict keyed on subject,object. + # 2. Iterate over all graph edges: + # 1. if there is a transition rule (else add to invalid + # transition list): + # 1. use set intersection to find matching exec + # and entrypoint rules. If none, add to invalid + # transition list. + # 2. for each valid entrypoint, add rules to the + # edge's lists if there is either a + # type_transition for it or the source process + # has setexec permissions. + # 3. If there are neither type_transitions nor + # setexec permissions, add to the invalid + # transition list + # 2. if there is a dyntransition rule (else add to invalid + # dyntrans list): + # 1. If the source has a setcurrent rule, add it + # to the edge's list, else add to invalid + # dyntransition list. + # 3. Iterate over all graph edges: + # 1. if the edge has an invalid trans and dyntrans, delete + # the edge. + # 2. if the edge has an invalid trans, clear the related + # lists on the edge. + # 3. if the edge has an invalid dyntrans, clear the related + # lists on the edge. + # + def _build_graph(self): + self.G.clear() + + self.log.info("Building graph from {0}...".format(self.policy)) + + # hash tables keyed on domain type + setexec = defaultdict(list) + setcurrent = defaultdict(list) + + # hash tables keyed on (domain, entrypoint file type) + # the parameter for defaultdict has to be callable + # hence the lambda for the nested defaultdict + execute = defaultdict(lambda: defaultdict(list)) + entrypoint = defaultdict(lambda: defaultdict(list)) + + # hash table keyed on (domain, entrypoint, target domain) + type_trans = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) + + for rule in self.policy.terules(): + if rule.ruletype == "allow": + if rule.tclass not in ["process", "file"]: + continue + + perms = rule.perms + + if rule.tclass == "process": + if "transition" in perms: + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + # only add edges if they actually + # transition to a new type + if s != t: + edge = Edge(self.G, s, t, create=True) + edge.transition.append(rule) + + if "dyntransition" in perms: + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + # only add edges if they actually + # transition to a new type + if s != t: + e = Edge(self.G, s, t, create=True) + e.dyntransition.append(rule) + + if "setexec" in perms: + for s in rule.source.expand(): + setexec[s].append(rule) + + if "setcurrent" in perms: + for s in rule.source.expand(): + setcurrent[s].append(rule) + + else: + if "execute" in perms: + for s, t in itertools.product( + rule.source.expand(), + rule.target.expand()): + execute[s][t].append(rule) + + if "entrypoint" in perms: + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + entrypoint[s][t].append(rule) + + elif rule.ruletype == "type_transition": + if rule.tclass != "process": + continue + + d = rule.default + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + type_trans[s][t][d].append(rule) + + invalid_edge = [] + clear_transition = [] + clear_dyntransition = [] + + for s, t in self.G.edges_iter(): + edge = Edge(self.G, s, t) + invalid_trans = False + invalid_dyntrans = False + + if edge.transition: + # get matching domain exec w/entrypoint type + entry = set(entrypoint[t].keys()) + exe = set(execute[s].keys()) + match = entry.intersection(exe) + + if not match: + # there are no valid entrypoints + invalid_trans = True + else: + # TODO try to improve the + # efficiency in this loop + for m in match: + if s in setexec or type_trans[s][m]: + # add key for each entrypoint + edge.entrypoint[m] += entrypoint[t][m] + edge.execute[m] += execute[s][m] + + if type_trans[s][m][t]: + edge.type_transition[m] += type_trans[s][m][t] + + if s in setexec: + edge.setexec.extend(setexec[s]) + + if not edge.setexec and not edge.type_transition: + invalid_trans = True + else: + invalid_trans = True + + if edge.dyntransition: + if s in setcurrent: + edge.setcurrent.extend(setcurrent[s]) + else: + invalid_dyntrans = True + else: + invalid_dyntrans = True + + # cannot change the edges while iterating over them, + # so keep appropriate lists + if invalid_trans and invalid_dyntrans: + invalid_edge.append(edge) + elif invalid_trans: + clear_transition.append(edge) + elif invalid_dyntrans: + clear_dyntransition.append(edge) + + # Remove invalid transitions + self.G.remove_edges_from(invalid_edge) + for edge in clear_transition: + # if only the regular transition is invalid, + # clear the relevant lists + del edge.transition + del edge.execute + del edge.entrypoint + del edge.type_transition + del edge.setexec + for edge in clear_dyntransition: + # if only the dynamic transition is invalid, + # clear the relevant lists + del edge.dyntransition + del edge.setcurrent + + self.rebuildgraph = False + self.rebuildsubgraph = True + self.log.info("Completed building graph.") + + def __remove_excluded_entrypoints(self): + invalid_edges = [] + for source, target in self.subG.edges_iter(): + edge = Edge(self.subG, source, target) + entrypoints = set(edge.entrypoint) + entrypoints.intersection_update(self.exclude) + + if not entrypoints: + # short circuit if there are no + # excluded entrypoint types on + # this edge. + continue + + for e in entrypoints: + # clear the entrypoint data + del edge.entrypoint[e] + del edge.execute[e] + + try: + del edge.type_transition[e] + except KeyError: # setexec + pass + + # cannot delete the edges while iterating over them + if not edge.entrypoint and not edge.dyntransition: + invalid_edges.append(edge) + + self.subG.remove_edges_from(invalid_edges) + + def _build_subgraph(self): + if self.rebuildgraph: + self._build_graph() + + self.log.info("Building subgraph.") + self.log.debug("Excluding {0}".format(self.exclude)) + self.log.debug("Reverse {0}".format(self.reverse)) + + # reverse graph for reverse DTA + if self.reverse: + self.subG = self.G.reverse(copy=True) + else: + self.subG = self.G.copy() + + if self.exclude: + # delete excluded domains from subgraph + self.subG.remove_nodes_from(self.exclude) + + # delete excluded entrypoints from subgraph + self.__remove_excluded_entrypoints() + + self.rebuildsubgraph = False + self.log.info("Completed building subgraph.") + + +class Edge(object): + + """ + A graph edge. Also used for returning domain transition steps. + + Parameters: + source The source type of the edge. + target The target tyep of the edge. + + Keyword Parameters: + create (T/F) create the edge if it does not exist. + The default is False. + """ + + transition = EdgeAttrList('transition') + setexec = EdgeAttrList('setexec') + dyntransition = EdgeAttrList('dyntransition') + setcurrent = EdgeAttrList('setcurrent') + entrypoint = EdgeAttrDict('entrypoint') + execute = EdgeAttrDict('execute') + type_transition = EdgeAttrDict('type_transition') + + def __init__(self, graph, source, target, create=False): + self.G = graph + self.source = source + self.target = target + + # a bit of a hack to make Edges work + # in NetworkX functions that work on + # 2-tuples of (source, target) + # (see __getitem__ below) + self.st_tuple = (source, target) + + if not self.G.has_edge(source, target): + if not create: + raise ValueError("Edge does not exist in graph") + else: + self.G.add_edge(source, target) + self.transition = None + self.entrypoint = None + self.execute = None + self.type_transition = None + self.setexec = None + self.dyntransition = None + self.setcurrent = None + + def __getitem__(self, key): + return self.st_tuple[key] diff --git a/lib/python2.7/site-packages/setools/exception.py b/lib/python2.7/site-packages/setools/exception.py new file mode 100644 index 0000000..c3505cd --- /dev/null +++ b/lib/python2.7/site-packages/setools/exception.py @@ -0,0 +1,62 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +# +# Base class for exceptions +# + + +class SEToolsException(Exception): + + """Base class for all SETools exceptions.""" + pass + +# +# Permission map exceptions +# + + +class PermissionMapException(SEToolsException): + + """Base class for all permission map exceptions.""" + pass + + +class PermissionMapParseError(PermissionMapException): + + """Exception for parse errors while reading permission map files.""" + pass + + +class RuleTypeError(PermissionMapException): + + """Exception for using rules with incorrect rule type.""" + pass + + +class UnmappedClass(PermissionMapException): + + """Exception for classes that are unmapped""" + pass + + +class UnmappedPermission(PermissionMapException): + + """Exception for permissions that are unmapped""" + pass diff --git a/lib/python2.7/site-packages/setools/fsusequery.py b/lib/python2.7/site-packages/setools/fsusequery.py new file mode 100644 index 0000000..6825a45 --- /dev/null +++ b/lib/python2.7/site-packages/setools/fsusequery.py @@ -0,0 +1,87 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import contextquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + + +class FSUseQuery(contextquery.ContextQuery): + + """ + Query fs_use_* statements. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The rule type(s) to match. + fs The criteria to match the file system type. + fs_regex If true, regular expression matching + will be used on the file system type. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + ruletype = None + fs = CriteriaDescriptor("fs_regex") + fs_regex = False + + def results(self): + """Generator which yields all matching fs_use_* statements.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("FS: {0.fs!r}, regex: {0.fs_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for fsu in self.policy.fs_uses(): + if self.ruletype and fsu.ruletype not in self.ruletype: + continue + + if self.fs and not self._match_regex( + fsu.fs, + self.fs, + self.fs_regex): + continue + + if not self._match_context(fsu.context): + continue + + yield fsu diff --git a/lib/python2.7/site-packages/setools/genfsconquery.py b/lib/python2.7/site-packages/setools/genfsconquery.py new file mode 100644 index 0000000..c67dfd6 --- /dev/null +++ b/lib/python2.7/site-packages/setools/genfsconquery.py @@ -0,0 +1,98 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import contextquery +from .descriptors import CriteriaDescriptor + + +class GenfsconQuery(contextquery.ContextQuery): + + """ + Query genfscon statements. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + fs The criteria to match the file system type. + fs_regex If true, regular expression matching + will be used on the file system type. + path The criteria to match the path. + path_regex If true, regular expression matching + will be used on the path. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + filetype = None + fs = CriteriaDescriptor("fs_regex") + fs_regex = False + path = CriteriaDescriptor("path_regex") + path_regex = False + + def results(self): + """Generator which yields all matching genfscons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("FS: {0.fs!r}, regex: {0.fs_regex}".format(self)) + self.log.debug("Path: {0.path!r}, regex: {0.path_regex}".format(self)) + self.log.debug("Filetype: {0.filetype!r}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for genfs in self.policy.genfscons(): + if self.fs and not self._match_regex( + genfs.fs, + self.fs, + self.fs_regex): + continue + + if self.path and not self._match_regex( + genfs.path, + self.path, + self.path_regex): + continue + + if self.filetype and not self.filetype == genfs.filetype: + continue + + if not self._match_context(genfs.context): + continue + + yield genfs diff --git a/lib/python2.7/site-packages/setools/infoflow.py b/lib/python2.7/site-packages/setools/infoflow.py new file mode 100644 index 0000000..ea3ec32 --- /dev/null +++ b/lib/python2.7/site-packages/setools/infoflow.py @@ -0,0 +1,403 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import itertools +import logging +from collections import namedtuple + +import networkx as nx +from networkx.exception import NetworkXError, NetworkXNoPath + +from .descriptors import EdgeAttrIntMax, EdgeAttrList + +__all__ = ['InfoFlowAnalysis'] + +# Return values for the analysis +# are in the following tuple format: +step_output = namedtuple("step", ["source", + "target", + "rules"]) + + +class InfoFlowAnalysis(object): + + """Information flow analysis.""" + + def __init__(self, policy, perm_map, min_weight=1, exclude=None): + """ + Parameters: + policy The policy to analyze. + perm_map The permission map or path to the permission map file. + minweight The minimum permission weight to include in the analysis. + (default is 1) + exclude The types excluded from the information flow analysis. + (default is none) + """ + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + + self.min_weight = min_weight + self.perm_map = perm_map + self.exclude = exclude + self.rebuildgraph = True + self.rebuildsubgraph = True + + self.G = nx.DiGraph() + self.subG = None + + @property + def min_weight(self): + return self._min_weight + + @min_weight.setter + def min_weight(self, weight): + if not 1 <= weight <= 10: + raise ValueError( + "Min information flow weight must be an integer 1-10.") + + self._min_weight = weight + self.rebuildsubgraph = True + + @property + def perm_map(self): + return self._perm_map + + @perm_map.setter + def perm_map(self, perm_map): + self._perm_map = perm_map + self.rebuildgraph = True + self.rebuildsubgraph = True + + @property + def exclude(self): + return self._exclude + + @exclude.setter + def exclude(self, types): + if types: + self._exclude = [self.policy.lookup_type(t) for t in types] + else: + self._exclude = [] + + self.rebuildsubgraph = True + + def shortest_path(self, source, target): + """ + Generator which yields one shortest path between the source + and target types (there may be more). + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating one shortest path from {0} to {1}...".format(s, t)) + + try: + yield self.__generate_steps(nx.shortest_path(self.subG, s, t)) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_paths(self, source, target, maxlen=2): + """ + Generator which yields all paths between the source and target + up to the specified maximum path length. This algorithm + tends to get very expensive above 3-5 steps, depending + on the policy complexity. + + Parameters: + source The source type. + target The target type. + maxlen Maximum length of paths. + + Yield: generator(steps) + + steps Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + if maxlen < 1: + raise ValueError("Maximum path length must be positive.") + + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all paths from {0} to {1}, max len {2}...".format(s, t, maxlen)) + + try: + for path in nx.all_simple_paths(self.subG, s, t, maxlen): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_shortest_paths(self, source, target): + """ + Generator which yields all shortest paths between the source + and target types. + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all shortest paths from {0} to {1}...".format(s, t)) + + try: + for path in nx.all_shortest_paths(self.subG, s, t): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError, KeyError): + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + # NetworkXNoPath: no paths or the target type is + # not in the graph + # KeyError: work around NetworkX bug + # when the source node is not in the graph + pass + + def infoflows(self, type_, out=True): + """ + Generator which yields all information flows in/out of a + specified source type. + + Parameters: + source The starting type. + + Keyword Parameters: + out If true, information flows out of the type will + be returned. If false, information flows in to the + type will be returned. Default is true. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + information flow. + """ + s = self.policy.lookup_type(type_) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all infoflows out of {0}...".format(s)) + + if out: + flows = self.subG.out_edges_iter(s) + else: + flows = self.subG.in_edges_iter(s) + + try: + for source, target in flows: + edge = Edge(self.subG, source, target) + yield step_output(source, target, edge.rules) + except NetworkXError: + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + pass + + def get_stats(self): # pragma: no cover + """ + Get the information flow graph statistics. + + Return: tuple(nodes, edges) + + nodes The number of nodes (types) in the graph. + edges The number of edges (information flows between types) + in the graph. + """ + return (self.G.number_of_nodes(), self.G.number_of_edges()) + + # + # Internal functions follow + # + + def __generate_steps(self, path): + """ + Generator which returns the source, target, and associated rules + for each information flow step. + + Parameter: + path A list of graph node names representing an information flow path. + + Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + for s in range(1, len(path)): + edge = Edge(self.subG, path[s - 1], path[s]) + yield step_output(edge.source, edge.target, edge.rules) + + # + # + # Graph building functions + # + # + # 1. _build_graph determines the flow in each direction for each TE + # rule and then expands the rule. All information flows are + # included in this main graph: memory is traded off for efficiency + # as the main graph should only need to be rebuilt if permission + # weights change. + # 2. _build_subgraph derives a subgraph which removes all excluded + # types (nodes) and edges (information flows) which are below the + # minimum weight. This subgraph is rebuilt only if the main graph + # is rebuilt or the minimum weight or excluded types change. + + def _build_graph(self): + self.G.clear() + + self.perm_map.map_policy(self.policy) + + self.log.info("Building graph from {0}...".format(self.policy)) + + for rule in self.policy.terules(): + if rule.ruletype != "allow": + continue + + (rweight, wweight) = self.perm_map.rule_weight(rule) + + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + # only add flows if they actually flow + # in or out of the source type type + if s != t: + if wweight: + edge = Edge(self.G, s, t, create=True) + edge.rules.append(rule) + edge.weight = wweight + + if rweight: + edge = Edge(self.G, t, s, create=True) + edge.rules.append(rule) + edge.weight = rweight + + self.rebuildgraph = False + self.rebuildsubgraph = True + self.log.info("Completed building graph.") + + def _build_subgraph(self): + if self.rebuildgraph: + self._build_graph() + + self.log.info("Building subgraph...") + self.log.debug("Excluding {0!r}".format(self.exclude)) + self.log.debug("Min weight {0}".format(self.min_weight)) + + # delete excluded types from subgraph + nodes = [n for n in self.G.nodes() if n not in self.exclude] + self.subG = self.G.subgraph(nodes) + + # delete edges below minimum weight. + # no need if weight is 1, since that + # does not exclude any edges. + if self.min_weight > 1: + delete_list = [] + for s, t in self.subG.edges_iter(): + edge = Edge(self.subG, s, t) + if edge.weight < self.min_weight: + delete_list.append(edge) + + self.subG.remove_edges_from(delete_list) + + self.rebuildsubgraph = False + self.log.info("Completed building subgraph.") + + +class Edge(object): + + """ + A graph edge. Also used for returning information flow steps. + + Parameters: + source The source type of the edge. + target The target type of the edge. + + Keyword Parameters: + create (T/F) create the edge if it does not exist. + The default is False. + """ + + rules = EdgeAttrList('rules') + + # use capacity to store the info flow weight so + # we can use network flow algorithms naturally. + # The weight for each edge is 1 since each info + # flow step is no more costly than another + # (see below add_edge() call) + weight = EdgeAttrIntMax('capacity') + + def __init__(self, graph, source, target, create=False): + self.G = graph + self.source = source + self.target = target + + # a bit of a hack to make edges work + # in NetworkX functions that work on + # 2-tuples of (source, target) + # (see __getitem__ below) + self.st_tuple = (source, target) + + if not self.G.has_edge(source, target): + if create: + self.G.add_edge(source, target, weight=1) + self.rules = None + self.weight = None + else: + raise ValueError("Edge does not exist in graph") + + def __getitem__(self, key): + return self.st_tuple[key] diff --git a/lib/python2.7/site-packages/setools/initsidquery.py b/lib/python2.7/site-packages/setools/initsidquery.py new file mode 100644 index 0000000..1eb3790 --- /dev/null +++ b/lib/python2.7/site-packages/setools/initsidquery.py @@ -0,0 +1,74 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import contextquery + + +class InitialSIDQuery(compquery.ComponentQuery, contextquery.ContextQuery): + + """ + Initial SID (Initial context) query. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The Initial SID name to match. + name_regex If true, regular expression matching + will be used on the Initial SID name. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + def results(self): + """Generator which yields all matching initial SIDs.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for i in self.policy.initialsids(): + if not self._match_name(i): + continue + + if not self._match_context(i.context): + continue + + yield i diff --git a/lib/python2.7/site-packages/setools/mixins.py b/lib/python2.7/site-packages/setools/mixins.py new file mode 100644 index 0000000..a31d420 --- /dev/null +++ b/lib/python2.7/site-packages/setools/mixins.py @@ -0,0 +1,91 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=attribute-defined-outside-init,no-member +import re + +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + + +class MatchAlias(object): + + """Mixin for matching an object's aliases.""" + + alias = CriteriaDescriptor("alias_regex") + alias_regex = False + + def _match_alias(self, obj): + """ + Match the alias criteria + + Parameter: + obj An object with an alias generator method named "aliases" + """ + + if not self.alias: + # if there is no criteria, everything matches. + return True + + return self._match_in_set(obj.aliases(), self.alias, self.alias_regex) + + +class MatchObjClass(object): + + """Mixin for matching an object's class.""" + + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + + def _match_object_class(self, obj): + """ + Match the object class criteria + + Parameter: + obj An object with an object class attribute named "tclass" + """ + + if not self.tclass: + # if there is no criteria, everything matches. + return True + elif self.tclass_regex: + return bool(self.tclass.search(str(obj.tclass))) + else: + return obj.tclass in self.tclass + + +class MatchPermission(object): + + """Mixin for matching an object's permissions.""" + + perms = CriteriaSetDescriptor("perms_regex") + perms_equal = False + perms_regex = False + + def _match_perms(self, obj): + """ + Match the permission criteria + + Parameter: + obj An object with a permission set class attribute named "perms" + """ + + if not self.perms: + # if there is no criteria, everything matches. + return True + + return self._match_regex_or_set(obj.perms, self.perms, self.perms_equal, self.perms_regex) diff --git a/lib/python2.7/site-packages/setools/mlsrulequery.py b/lib/python2.7/site-packages/setools/mlsrulequery.py new file mode 100644 index 0000000..3a9e1bf --- /dev/null +++ b/lib/python2.7/site-packages/setools/mlsrulequery.py @@ -0,0 +1,115 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor + + +class MLSRuleQuery(mixins.MatchObjClass, query.PolicyQuery): + + """ + Query MLS rules. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source type/attribute to match. + source_regex If true, regular expression matching will + be used on the source type/attribute. + target The name of the target type/attribute to match. + target_regex If true, regular expression matching will + be used on the target type/attribute. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + """ + + ruletype = RuletypeDescriptor("validate_mls_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_type_or_attr") + source_regex = False + target = CriteriaDescriptor("target_regex", "lookup_type_or_attr") + target_regex = False + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + default = CriteriaDescriptor(lookup_function="lookup_range") + default_overlap = False + default_subset = False + default_superset = False + default_proper = False + + def results(self): + """Generator which yields all matching MLS rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Source: {0.source!r}, regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Default: {0.default!r}, overlap: {0.default_overlap}, " + "subset: {0.default_subset}, superset: {0.default_superset}, " + "proper: {0.default_proper}".format(self)) + + for rule in self.policy.mlsrules(): + # + # Matching on rule type + # + if self.ruletype: + if rule.ruletype not in self.ruletype: + continue + + # + # Matching on source type + # + if self.source and not self._match_regex( + rule.source, + self.source, + self.source_regex): + continue + + # + # Matching on target type + # + if self.target and not self._match_regex( + rule.target, + self.target, + self.target_regex): + continue + + # + # Matching on object class + # + if not self._match_object_class(rule): + continue + + # + # Matching on range + # + if self.default and not self._match_range( + rule.default, + self.default, + self.default_subset, + self.default_overlap, + self.default_superset, + self.default_proper): + continue + + # if we get here, we have matched all available criteria + yield rule diff --git a/lib/python2.7/site-packages/setools/netifconquery.py b/lib/python2.7/site-packages/setools/netifconquery.py new file mode 100644 index 0000000..30db977 --- /dev/null +++ b/lib/python2.7/site-packages/setools/netifconquery.py @@ -0,0 +1,77 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import contextquery + + +class NetifconQuery(compquery.ComponentQuery, contextquery.ContextQuery): + + """ + Network interface context query. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the network interface to match. + name_regex If true, regular expression matching will + be used for matching the name. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + def results(self): + """Generator which yields all matching netifcons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for netif in self.policy.netifcons(): + if self.name and not self._match_regex( + netif.netif, + self.name, + self.name_regex): + continue + + if not self._match_context(netif.context): + continue + + yield netif diff --git a/lib/python2.7/site-packages/setools/nodeconquery.py b/lib/python2.7/site-packages/setools/nodeconquery.py new file mode 100644 index 0000000..eb21d81 --- /dev/null +++ b/lib/python2.7/site-packages/setools/nodeconquery.py @@ -0,0 +1,148 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +try: + import ipaddress +except ImportError: # pragma: no cover + pass + +import logging +from socket import AF_INET, AF_INET6 + +from . import contextquery + + +class NodeconQuery(contextquery.ContextQuery): + + """ + Query nodecon statements. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + network The IPv4/IPv6 address or IPv4/IPv6 network address + with netmask, e.g. 192.168.1.0/255.255.255.0 or + "192.168.1.0/24". + network_overlap If true, the net will match if it overlaps with + the nodecon's network instead of equality. + ip_version The IP version of the nodecon to match. (socket.AF_INET + for IPv4 or socket.AF_INET6 for IPv6) + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + _network = None + network_overlap = False + _ip_version = None + + @property + def ip_version(self): + return self._ip_version + + @ip_version.setter + def ip_version(self, value): + if value: + if not (value == AF_INET or value == AF_INET6): + raise ValueError( + "The address family must be {0} for IPv4 or {1} for IPv6.". + format(AF_INET, AF_INET6)) + + self._ip_version = value + else: + self._ip_version = None + + @property + def network(self): + return self._network + + @network.setter + def network(self, value): + if value: + try: + self._network = ipaddress.ip_network(value) + except NameError: # pragma: no cover + raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") + else: + self._network = None + + def results(self): + """Generator which yields all matching nodecons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Network: {0.network!r}, overlap: {0.network_overlap}".format(self)) + self.log.debug("IP Version: {0.ip_version}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for nodecon in self.policy.nodecons(): + + if self.network: + try: + netmask = ipaddress.ip_address(nodecon.netmask) + except NameError: # pragma: no cover + # Should never actually hit this since the self.network + # setter raises the same exception. + raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") + + # Python 3.3's IPv6Network constructor does not support + # expanded netmasks, only CIDR numbers. Convert netmask + # into CIDR. + # This is Brian Kernighan's method for counting set bits. + # If the netmask happens to be invalid, this will + # not detect it. + CIDR = 0 + int_netmask = int(netmask) + while int_netmask: + int_netmask &= int_netmask - 1 + CIDR += 1 + + net = ipaddress.ip_network('{0}/{1}'.format(nodecon.address, CIDR)) + + if self.network_overlap: + if not self.network.overlaps(net): + continue + else: + if not net == self.network: + continue + + if self.ip_version and self.ip_version != nodecon.ip_version: + continue + + if not self._match_context(nodecon.context): + continue + + yield nodecon diff --git a/lib/python2.7/site-packages/setools/objclassquery.py b/lib/python2.7/site-packages/setools/objclassquery.py new file mode 100644 index 0000000..8f40df8 --- /dev/null +++ b/lib/python2.7/site-packages/setools/objclassquery.py @@ -0,0 +1,101 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor +from .policyrep.exception import NoCommon + + +class ObjClassQuery(compquery.ComponentQuery): + + """ + Query object classes. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the object set to match. + name_regex If true, regular expression matching will + be used for matching the name. + common The name of the inherited common to match. + common_regex If true, regular expression matching will + be used for matching the common name. + perms The permissions to match. + perms_equal If true, only commons with permission sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + perms_regex If true, regular expression matching + will be used on the permission names instead + of set logic. + comparison will not be used. + perms_indirect If false, permissions inherited from a common + permission set not will be evaluated. Default + is true. + """ + + common = CriteriaDescriptor("common_regex", "lookup_common") + common_regex = False + perms = CriteriaSetDescriptor("perms_regex") + perms_equal = False + perms_indirect = True + perms_regex = False + + def results(self): + """Generator which yields all matching object classes.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Common: {0.common!r}, regex: {0.common_regex}".format(self)) + self.log.debug("Perms: {0.perms}, regex: {0.perms_regex}, " + "eq: {0.perms_equal}, indirect: {0.perms_indirect}".format(self)) + + for class_ in self.policy.classes(): + if not self._match_name(class_): + continue + + if self.common: + try: + if not self._match_regex( + class_.common, + self.common, + self.common_regex): + continue + except NoCommon: + continue + + if self.perms: + perms = class_.perms + + if self.perms_indirect: + try: + perms |= class_.common.perms + except NoCommon: + pass + + if not self._match_regex_or_set( + perms, + self.perms, + self.perms_equal, + self.perms_regex): + continue + + yield class_ diff --git a/lib/python2.7/site-packages/setools/permmap.py b/lib/python2.7/site-packages/setools/permmap.py new file mode 100644 index 0000000..54cd9f9 --- /dev/null +++ b/lib/python2.7/site-packages/setools/permmap.py @@ -0,0 +1,363 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import sys +import logging +from errno import ENOENT + +from . import exception +from . import policyrep + + +class PermissionMap(object): + + """Permission Map for information flow analysis.""" + + valid_infoflow_directions = ["r", "w", "b", "n", "u"] + min_weight = 1 + max_weight = 10 + + def __init__(self, permmapfile=None): + """ + Parameter: + permmapfile The path to the permission map to load. + """ + self.log = logging.getLogger(self.__class__.__name__) + + if permmapfile: + self.load(permmapfile) + else: + for path in ["data/", sys.prefix + "/share/setools/"]: + try: + self.load(path + "perm_map") + break + except (IOError, OSError) as err: + if err.errno != ENOENT: + raise + else: + raise RuntimeError("Unable to load default permission map.") + + def load(self, permmapfile): + """ + Parameter: + permmapfile The path to the permission map to load. + """ + self.log.info("Opening permission map \"{0}\"".format(permmapfile)) + + # state machine + # 1 = read number of classes + # 2 = read class name and number of perms + # 3 = read perms + with open(permmapfile, "r") as mapfile: + class_count = 0 + num_classes = 0 + state = 1 + + self.permmap = dict() + + for line_num, line in enumerate(mapfile, start=1): + entry = line.split() + + if len(entry) == 0 or entry[0][0] == '#': + continue + + if state == 1: + try: + num_classes = int(entry[0]) + except ValueError: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid number of classes: {2}". + format(permmapfile, line_num, entry[0])) + + if num_classes < 1: + raise exception.PermissionMapParseError( + "{0}:{1}:Number of classes must be positive: {2}". + format(permmapfile, line_num, entry[0])) + + state = 2 + + elif state == 2: + if len(entry) != 3 or entry[0] != "class": + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid class declaration: {2}". + format(permmapfile, line_num, entry)) + + class_name = str(entry[1]) + + try: + num_perms = int(entry[2]) + except ValueError: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid number of permissions: {2}". + format(permmapfile, line_num, entry[2])) + + if num_perms < 1: + raise exception.PermissionMapParseError( + "{0}:{1}:Number of permissions must be positive: {2}". + format(permmapfile, line_num, entry[2])) + + class_count += 1 + if class_count > num_classes: + raise exception.PermissionMapParseError( + "{0}:{1}:Extra class found: {2}". + format(permmapfile, line_num, class_name)) + + self.permmap[class_name] = dict() + perm_count = 0 + state = 3 + + elif state == 3: + perm_name = str(entry[0]) + + flow_direction = str(entry[1]) + if flow_direction not in self.valid_infoflow_directions: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid information flow direction: {2}". + format(permmapfile, line_num, entry[1])) + + try: + weight = int(entry[2]) + except ValueError: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid permission weight: {2}". + format(permmapfile, line_num, entry[2])) + + if not self.min_weight <= weight <= self.max_weight: + raise exception.PermissionMapParseError( + "{0}:{1}:Permission weight must be {3}-{4}: {2}". + format(permmapfile, line_num, entry[2], + self.min_weight, self.max_weight)) + + self.permmap[class_name][perm_name] = {'direction': flow_direction, + 'weight': weight, + 'enabled': True} + + perm_count += 1 + if perm_count >= num_perms: + state = 2 + + def exclude_class(self, class_): + """ + Exclude all permissions in an object class for calculating rule weights. + + Parameter: + class_ The object class to exclude. + + Exceptions: + UnmappedClass The specified object class is not mapped. + """ + + classname = str(class_) + + try: + for perm in self.permmap[classname]: + self.permmap[classname][perm]['enabled'] = False + except KeyError: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + def exclude_permission(self, class_, permission): + """ + Exclude a permission for calculating rule weights. + + Parameter: + class_ The object class of the permission. + permission The permission name to exclude. + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['enabled'] = False + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) + + def include_class(self, class_): + """ + Include all permissions in an object class for calculating rule weights. + + Parameter: + class_ The object class to include. + + Exceptions: + UnmappedClass The specified object class is not mapped. + """ + + classname = str(class_) + + try: + for perm in self.permmap[classname]: + self.permmap[classname][perm]['enabled'] = True + except KeyError: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + def include_permission(self, class_, permission): + """ + Include a permission for calculating rule weights. + + Parameter: + class_ The object class of the permission. + permission The permission name to include. + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['enabled'] = True + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) + + def map_policy(self, policy): + """Create mappings for all classes and permissions in the specified policy.""" + for class_ in policy.classes(): + class_name = str(class_) + + if class_name not in self.permmap: + self.log.info("Adding unmapped class {0} from {1}".format(class_name, policy)) + self.permmap[class_name] = dict() + + perms = class_.perms + + try: + perms |= class_.common.perms + except policyrep.exception.NoCommon: + pass + + for perm_name in perms: + if perm_name not in self.permmap[class_name]: + self.log.info("Adding unmapped permission {0} in {1} from {2}". + format(perm_name, class_name, policy)) + self.permmap[class_name][perm_name] = {'direction': 'u', + 'weight': 1, + 'enabled': True} + + def rule_weight(self, rule): + """ + Get the type enforcement rule's information flow read and write weights. + + Parameter: + rule A type enforcement rule. + + Return: Tuple(read_weight, write_weight) + read_weight The type enforcement rule's read weight. + write_weight The type enforcement rule's write weight. + """ + + write_weight = 0 + read_weight = 0 + class_name = str(rule.tclass) + + if rule.ruletype != 'allow': + raise exception.RuleTypeError("{0} rules cannot be used for calculating a weight". + format(rule.ruletype)) + + if class_name not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(class_name)) + + # iterate over the permissions and determine the + # weight of the rule in each direction. The result + # is the largest-weight permission in each direction + for perm_name in rule.perms: + try: + mapping = self.permmap[class_name][perm_name] + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(class_name, perm_name)) + + if not mapping['enabled']: + continue + + if mapping['direction'] == "r": + read_weight = max(read_weight, mapping['weight']) + elif mapping['direction'] == "w": + write_weight = max(write_weight, mapping['weight']) + elif mapping['direction'] == "b": + read_weight = max(read_weight, mapping['weight']) + write_weight = max(write_weight, mapping['weight']) + + return (read_weight, write_weight) + + def set_direction(self, class_, permission, direction): + """ + Set the information flow direction of a permission. + + Parameter: + class_ The object class of the permission. + permission The permission name. + direction The information flow direction the permission (r/w/b/n). + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + + if direction not in self.valid_infoflow_directions: + raise ValueError("Invalid information flow direction: {0}".format(direction)) + + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['direction'] = direction + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) + + def set_weight(self, class_, permission, weight): + """ + Set the weight of a permission. + + Parameter: + class_ The object class of the permission. + permission The permission name. + weight The weight of the permission (1-10). + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + + if not self.min_weight <= weight <= self.max_weight: + raise ValueError("Permission weights must be 1-10: {0}".format(weight)) + + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['weight'] = weight + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) diff --git a/lib/python2.7/site-packages/setools/polcapquery.py b/lib/python2.7/site-packages/setools/polcapquery.py new file mode 100644 index 0000000..e024b05 --- /dev/null +++ b/lib/python2.7/site-packages/setools/polcapquery.py @@ -0,0 +1,47 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery + + +class PolCapQuery(compquery.ComponentQuery): + + """ + Query SELinux policy capabilities + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the policy capability to match. + name_regex If true, regular expression matching will + be used for matching the name. + """ + + def results(self): + """Generator which yields all matching policy capabilities.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + + for cap in self.policy.polcaps(): + if not self._match_name(cap): + continue + + yield cap diff --git a/lib/python2.7/site-packages/setools/policyrep/__init__.py b/lib/python2.7/site-packages/setools/policyrep/__init__.py new file mode 100644 index 0000000..b03e524 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/__init__.py @@ -0,0 +1,568 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=too-many-public-methods +# +# Create a Python representation of the policy. +# The idea is that this is module provides convenient +# abstractions and methods for accessing the policy +# structures. +import logging +from itertools import chain +from errno import ENOENT + +try: + import selinux +except ImportError: + pass + +from . import qpol + +# The libqpol SWIG class is not quite natural for +# Python the policy is repeatedly referenced in the +# function calls, which makes sense for C code +# but not for python code, so each object keeps +# a reference to the policy for internal use. +# This also makes sense since an object would only +# be valid for the policy it comes from. + +# Exceptions +from . import exception + +# Components +from . import boolcond +from . import default +from . import mls +from . import objclass +from . import polcap +from . import role +from . import typeattr +from . import user + +# Rules +from . import mlsrule +from . import rbacrule +from . import terule + +# Constraints +from . import constraint + +# In-policy Labeling +from . import fscontext +from . import initsid +from . import netcontext + + +class SELinuxPolicy(object): + + """The complete SELinux policy.""" + + def __init__(self, policyfile=None): + """ + Parameter: + policyfile Path to a policy to open. + """ + + self.log = logging.getLogger(self.__class__.__name__) + self.policy = None + self.filename = None + + if policyfile: + self._load_policy(policyfile) + else: + try: + self._load_running_policy() + except NameError: + raise RuntimeError("Loading the running policy requires libselinux Python bindings") + + def __repr__(self): + return "".format(self.filename) + + def __str__(self): + return self.filename + + def __deepcopy__(self, memo): + # shallow copy as all of the members are immutable + newobj = SELinuxPolicy.__new__(SELinuxPolicy) + newobj.policy = self.policy + newobj.filename = self.filename + memo[id(self)] = newobj + return newobj + + # + # Policy loading functions + # + + def _load_policy(self, filename): + """Load the specified policy.""" + self.log.info("Opening SELinux policy \"{0}\"".format(filename)) + + try: + self.policy = qpol.qpol_policy_factory(str(filename)) + except SyntaxError as err: + raise exception.InvalidPolicy("Error opening policy file \"{0}\": {1}". + format(filename, err)) + + self.log.info("Successfully opened SELinux policy \"{0}\"".format(filename)) + self.filename = filename + + @staticmethod + def _potential_policies(): + """Generate a list of potential policies to use.""" + # Start with binary policies in the standard location + base_policy_path = selinux.selinux_binary_policy_path() + for version in range(qpol.QPOL_POLICY_MAX_VERSION, qpol.QPOL_POLICY_MIN_VERSION-1, -1): + yield "{0}.{1}".format(base_policy_path, version) + + # Last chance, try selinuxfs. This is not first, to avoid + # holding kernel memory for a long time + if selinux.selinuxfs_exists(): + yield selinux.selinux_current_policy_path() + + def _load_running_policy(self): + """Try to load the current running policy.""" + self.log.info("Attempting to locate current running policy.") + + for filename in self._potential_policies(): + try: + self._load_policy(filename) + except OSError as err: + if err.errno != ENOENT: + raise + else: + break + else: + raise RuntimeError("Unable to locate an SELinux policy to load.") + + # + # Policy properties + # + @property + def handle_unknown(self): + """The handle unknown permissions setting (allow,deny,reject)""" + return self.policy.handle_unknown() + + @property + def mls(self): + """(T/F) The policy has MLS enabled.""" + return mls.enabled(self.policy) + + @property + def version(self): + """The policy database version (e.g. v29)""" + return self.policy.version() + + # + # Policy statistics + # + + @property + def allow_count(self): + """The number of (type) allow rules.""" + return self.policy.avrule_allow_count() + + @property + def auditallow_count(self): + """The number of auditallow rules.""" + return self.policy.avrule_auditallow_count() + + @property + def boolean_count(self): + """The number of Booleans.""" + return self.policy.bool_count() + + @property + def category_count(self): + """The number of categories.""" + return sum(1 for _ in self.categories()) + + @property + def class_count(self): + """The number of object classes.""" + return self.policy.class_count() + + @property + def common_count(self): + """The number of common permission sets.""" + return self.policy.common_count() + + @property + def conditional_count(self): + """The number of conditionals.""" + return self.policy.cond_count() + + @property + def constraint_count(self): + """The number of standard constraints.""" + return sum(1 for c in self.constraints() if c.ruletype == "constrain") + + @property + def dontaudit_count(self): + """The number of dontaudit rules.""" + return self.policy.avrule_dontaudit_count() + + @property + def fs_use_count(self): + """fs_use_* statements.""" + return self.policy.fs_use_count() + + @property + def genfscon_count(self): + """The number of genfscon statements.""" + return self.policy.genfscon_count() + + @property + def initialsids_count(self): + """The number of initial sid statements.""" + return self.policy.isid_count() + + @property + def level_count(self): + """The number of levels.""" + return sum(1 for _ in self.levels()) + + @property + def mlsconstraint_count(self): + """The number of MLS constraints.""" + return sum(1 for c in self.constraints() if c.ruletype == "mlsconstrain") + + @property + def mlsvalidatetrans_count(self): + """The number of MLS validatetrans.""" + return sum(1 for v in self.constraints() if v.ruletype == "mlsvalidatetrans") + + @property + def netifcon_count(self): + """The number of netifcon statements.""" + return self.policy.netifcon_count() + + @property + def neverallow_count(self): + """The number of neverallow rules.""" + return self.policy.avrule_neverallow_count() + + @property + def nodecon_count(self): + """The number of nodecon statements.""" + return self.policy.nodecon_count() + + @property + def permission_count(self): + """The number of permissions.""" + return sum(len(c.perms) for c in chain(self.commons(), self.classes())) + + @property + def permissives_count(self): + """The number of permissive types.""" + return self.policy.permissive_count() + + @property + def polcap_count(self): + """The number of policy capabilities.""" + return self.policy.polcap_count() + + @property + def portcon_count(self): + """The number of portcon statements.""" + return self.policy.portcon_count() + + @property + def range_transition_count(self): + """The number of range_transition rules.""" + return self.policy.range_trans_count() + + @property + def role_count(self): + """The number of roles.""" + return self.policy.role_count() + + @property + def role_allow_count(self): + """The number of (role) allow rules.""" + return self.policy.role_allow_count() + + @property + def role_transition_count(self): + """The number of role_transition rules.""" + return self.policy.role_trans_count() + + @property + def type_attribute_count(self): + """The number of (type) attributes.""" + return sum(1 for _ in self.typeattributes()) + + @property + def type_count(self): + """The number of types.""" + return sum(1 for _ in self.types()) + + @property + def type_change_count(self): + """The number of type_change rules.""" + return self.policy.terule_change_count() + + @property + def type_member_count(self): + """The number of type_member rules.""" + return self.policy.terule_member_count() + + @property + def type_transition_count(self): + """The number of type_transition rules.""" + return self.policy.terule_trans_count() + self.policy.filename_trans_count() + + @property + def user_count(self): + """The number of users.""" + return self.policy.user_count() + + @property + def validatetrans_count(self): + """The number of validatetrans.""" + return sum(1 for v in self.constraints() if v.ruletype == "validatetrans") + + # + # Policy components lookup functions + # + def lookup_boolean(self, name): + """Look up a Boolean.""" + return boolcond.boolean_factory(self.policy, name) + + def lookup_class(self, name): + """Look up an object class.""" + return objclass.class_factory(self.policy, name) + + def lookup_common(self, name): + """Look up a common permission set.""" + return objclass.common_factory(self.policy, name) + + def lookup_initialsid(self, name): + """Look up an initial sid.""" + return initsid.initialsid_factory(self.policy, name) + + def lookup_level(self, level): + """Look up a MLS level.""" + return mls.level_factory(self.policy, level) + + def lookup_sensitivity(self, name): + """Look up a MLS sensitivity by name.""" + return mls.sensitivity_factory(self.policy, name) + + def lookup_range(self, range_): + """Look up a MLS range.""" + return mls.range_factory(self.policy, range_) + + def lookup_role(self, name): + """Look up a role by name.""" + return role.role_factory(self.policy, name) + + def lookup_type(self, name): + """Look up a type by name.""" + return typeattr.type_factory(self.policy, name, deref=True) + + def lookup_type_or_attr(self, name): + """Look up a type or type attribute by name.""" + return typeattr.type_or_attr_factory(self.policy, name, deref=True) + + def lookup_typeattr(self, name): + """Look up a type attribute by name.""" + return typeattr.attribute_factory(self.policy, name) + + def lookup_user(self, name): + """Look up a user by name.""" + return user.user_factory(self.policy, name) + + # + # Policy components generators + # + + def bools(self): + """Generator which yields all Booleans.""" + for bool_ in self.policy.bool_iter(): + yield boolcond.boolean_factory(self.policy, bool_) + + def categories(self): + """Generator which yields all MLS categories.""" + for cat in self.policy.cat_iter(): + try: + yield mls.category_factory(self.policy, cat) + except TypeError: + # libqpol unfortunately iterates over aliases too + pass + + def classes(self): + """Generator which yields all object classes.""" + for class_ in self.policy.class_iter(): + yield objclass.class_factory(self.policy, class_) + + def commons(self): + """Generator which yields all commons.""" + for common in self.policy.common_iter(): + yield objclass.common_factory(self.policy, common) + + def defaults(self): + """Generator which yields all default_* statements.""" + for default_ in self.policy.default_iter(): + try: + for default_obj in default.default_factory(self.policy, default_): + yield default_obj + except exception.NoDefaults: + # qpol iterates over all classes. Handle case + # where a class has no default_* settings. + pass + + def levels(self): + """Generator which yields all level declarations.""" + for level in self.policy.level_iter(): + + try: + yield mls.level_decl_factory(self.policy, level) + except TypeError: + # libqpol unfortunately iterates over levels and sens aliases + pass + + def polcaps(self): + """Generator which yields all policy capabilities.""" + for cap in self.policy.polcap_iter(): + yield polcap.polcap_factory(self.policy, cap) + + def roles(self): + """Generator which yields all roles.""" + for role_ in self.policy.role_iter(): + yield role.role_factory(self.policy, role_) + + def sensitivities(self): + """Generator which yields all sensitivities.""" + # see mls.py for more info on why level_iter is used here. + for sens in self.policy.level_iter(): + try: + yield mls.sensitivity_factory(self.policy, sens) + except TypeError: + # libqpol unfortunately iterates over sens and aliases + pass + + def types(self): + """Generator which yields all types.""" + for type_ in self.policy.type_iter(): + try: + yield typeattr.type_factory(self.policy, type_) + except TypeError: + # libqpol unfortunately iterates over attributes and aliases + pass + + def typeattributes(self): + """Generator which yields all (type) attributes.""" + for type_ in self.policy.type_iter(): + try: + yield typeattr.attribute_factory(self.policy, type_) + except TypeError: + # libqpol unfortunately iterates over attributes and aliases + pass + + def users(self): + """Generator which yields all users.""" + for user_ in self.policy.user_iter(): + yield user.user_factory(self.policy, user_) + + # + # Policy rules generators + # + def mlsrules(self): + """Generator which yields all MLS rules.""" + for rule in self.policy.range_trans_iter(): + yield mlsrule.mls_rule_factory(self.policy, rule) + + def rbacrules(self): + """Generator which yields all RBAC rules.""" + for rule in chain(self.policy.role_allow_iter(), + self.policy.role_trans_iter()): + yield rbacrule.rbac_rule_factory(self.policy, rule) + + def terules(self): + """Generator which yields all type enforcement rules.""" + for rule in chain(self.policy.avrule_iter(), + self.policy.terule_iter(), + self.policy.filename_trans_iter()): + yield terule.te_rule_factory(self.policy, rule) + + # + # Policy rule type validators + # + @staticmethod + def validate_constraint_ruletype(types): + """Validate constraint types.""" + constraint.validate_ruletype(types) + + @staticmethod + def validate_mls_ruletype(types): + """Validate MLS rule types.""" + mlsrule.validate_ruletype(types) + + @staticmethod + def validate_rbac_ruletype(types): + """Validate RBAC rule types.""" + rbacrule.validate_ruletype(types) + + @staticmethod + def validate_te_ruletype(types): + """Validate type enforcement rule types.""" + terule.validate_ruletype(types) + + # + # Constraints generators + # + + def constraints(self): + """Generator which yields all constraints (regular and MLS).""" + for constraint_ in chain(self.policy.constraint_iter(), + self.policy.validatetrans_iter()): + + yield constraint.constraint_factory(self.policy, constraint_) + + # + # In-policy Labeling statement generators + # + def fs_uses(self): + """Generator which yields all fs_use_* statements.""" + for fs_use in self.policy.fs_use_iter(): + yield fscontext.fs_use_factory(self.policy, fs_use) + + def genfscons(self): + """Generator which yields all genfscon statements.""" + for fscon in self.policy.genfscon_iter(): + yield fscontext.genfscon_factory(self.policy, fscon) + + def initialsids(self): + """Generator which yields all initial SID statements.""" + for sid in self.policy.isid_iter(): + yield initsid.initialsid_factory(self.policy, sid) + + def netifcons(self): + """Generator which yields all netifcon statements.""" + for ifcon in self.policy.netifcon_iter(): + yield netcontext.netifcon_factory(self.policy, ifcon) + + def nodecons(self): + """Generator which yields all nodecon statements.""" + for node in self.policy.nodecon_iter(): + yield netcontext.nodecon_factory(self.policy, node) + + def portcons(self): + """Generator which yields all portcon statements.""" + for port in self.policy.portcon_iter(): + yield netcontext.portcon_factory(self.policy, port) diff --git a/lib/python2.7/site-packages/setools/policyrep/_qpol.so b/lib/python2.7/site-packages/setools/policyrep/_qpol.so new file mode 100755 index 0000000000000000000000000000000000000000..aaccf28502370b3a08b4d557f04c7bd4076735b4 GIT binary patch literal 2151445 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∈-e9vJGtJPG4ME{jvWtOX z#_eDK1wjsjVo|$V8a2vTiMLJIiy0!(pE)6;#yk{2!!-w0y;P*x~Gl8Zqk{}C3 z!KoY4Zh5kcfx+X>um2NJ*U!1~p`-)wJT`cL*<0+*AaIuh)Q>jV&A`y{=-2<#po#z- z9%%FI@NwDwn2V^rK=&k|kIOo7gTkc;bb#tp==do#TtMSPOLj9bSUmmp|0+lfXS)mR ze$f0VXg3m;i61A>PJXP*4?)I)@+;3C28In!fBn}52|)5Ay!-{ZPiGGU!-1#2{(k|< zLorC)We)?xif6z6S0bfDP5OL|1H*^c zzy7ZP#Ur+K1}-nb=?pYTGz05?2q$i)D6AWDz|$JwagCCF3=9@;vAY8l-c$B5FnGL0 zjPo~xrZPDo=kkH-nl1Yn7%JZWg6!`Dm!GiqU^;xx6=cqxeGCi}-a`8a;QA*6C0_sR zV_;bD7P)>ZLy?!+&%kit?Jun7r-8!R1}bj`O5acn3dfNB3=A*cBGy$S_v5@!`&}-4 z3E+Jtn5_-b65g&j3-vF}M3)D}8w{K?gNx)7x07*jXrH}&*3^zVP+i4JCaDAV1 zfPq2a(=Vib`b@Rp7A6zuq-=;nP`ph!z`(HM(=YgWe6aB&&|Er5ehbL_Pro4N>VV@F zzP$r%&lRXV_`F`^^x?p?7nVRk@%QHd14F}S)cP4z--#S#V5s=~>;FS|J^-I9Ii}J71%rvka^&E0-Fb2-;%&A z05T6VG9V3Wko&eAWMHuP_3M8o)P2a~QPA?uf$17rYtMxnv^fT40s@pHLFTa>Vql2) z{R_T738S6l0ZK<;|AN{{`S{vNpmIOt5CcQTpI`rXfT9#p{vz6^pweR@QgQ19Sr5{4 zh=Jk5pI`q$%gsPqK^Ww|C5IRoUi|s>e<4T!VIH_Vhn>%sfaYB%ZqTXmD1&+6mK-SF zzZ_y<@c4@v?>vVY7##lo`o9v7J?O)T9(e4DIn2PY0E;~(hZz`VP|=cpndt%Fy_-hYmSk}PQe)jR4!gQ!oa}6 z@caKfc)92fIv)_*x3|jUjUR|mmFnah+z5sA2dA(N}3=H65j(=4=PqbVjv6> zzjBm;!Gq;D%J>av{m~ncds%)X-D?jHXK=oRmKzOBOs~)a8d8pd%+)!@z@WhT`#&eh zA}9ulyBuR+kYN4&zZoh9qCny?#~2tUu>SrJY9oQfKo}%ma*ToD2J3IgdM%J3beyLL zWIo$($a+Y$b`Er&+!eHw38V#N=AL5=3=V9+|9^y*gV6DB@O&0{zdpF#1uAR;F~`5% zxj_dQV>ZM=hC|ztGRGMh9 zp8+U$f?Nw9-~=t?Kq)3cj)Tn0%bZ|fn85KH%R0AeST)K3%5N?w7#MbN{Qh48jR$0Z zf$KM9e+4ibVz1xaxqYym69;h)Wc=p@1H%PQ4EK2P8Dzk>yhF1mSpA(73=AJQe?#tB z2B#-aP`er2Uk4ox1@1jSy6g;~aY3Gw3=9ffzai@m!Rit1Yo2uLGileBm=_^uHXN6!OI!6 z@(kR50N2mp{$DQE4Q-H+0j0w)Cm9$tcz^$g-2;YE--FGAr$g)q@p^EVVv8AZk@K}_2q9LNSU&{)r$Qw$6S{J;Ot1{HjWb`zo; z2fGvOZx_A-W@+r{%7dF}4c4BDD>vvmSS&>{s4EHDrzLZmfgwN$k*<;3Sze(1QK0z( z7v$m+oDx8ntRk`)Sg0QDDl#{2OcBUn2_#=a3k~Rg_$8+q7(9f3!|#WO#up+Skn@*= zAJ%HiiJR#s)=+>H@8Er;JZBgf3`Bnaj{_w=aQzPMS9$OmfDS-_)bC#0p0Lv;K&rs^ zGPs;!V2BX;{U3CE8fbhD8cyJIPQdjFXk5t+Ih?>#WpMp1XBZe}i2VMa4^7|DavU*_ z2`*>BofJ^GLdKJvKqnkQ=YP*IFq{zk{r?#>y;g)NnEfnIZe||rGLGEfenQV#28ILDzyIHb z+XL<=fa4iF|A!pU3mBPwK?{K}I||?y1*lxUbC!W2K>qjterUKNm&;)D!1)K7&nGZ4 zmw;S{*%Wf)25k_*l21SlYS6fy%Q*&y3dP?@=YWIDO>j9I1j*Fk?P;JO1(jzd=NK3| zlz#vJ0CyL-JagrP)q@Ck1@a|$V=c{~(+1#j{>V871`XBU|5c#zkGzi=bjdTMhzIp{ zAmb@tKdqGc8eB&nZifxvfb^%FXJB}t z@%z6&Tt8a949Xv?&^j=XZAajAvE)1h!w;?B|2d%P0%||FJ^|-1cRmB=IiUSr$n}RC z?D}pw}TQgxP1ja zZ_*WXo|6ZtJ^=Z9Iv=Qvf*S|Qhbb2s7-m@fM&Iw}%7;>ZK z#0~b(n@bD~4lcj{Kf~)EXgm}!9|l>2S-iS(gPJW^7FvQESfF$la+!hQg!}LR{m^s* z^^Yqbtb7BP<1l-043~mUgXEVbml+s1JbuH^twS~s91q~~5^Nqge3(HF$1KM^K|Ke| z_yIYRfdSOs;<*CaFZuhw184w-P!_MF!+mP`LG6VPM$c z^ZUQLIBdQaTrYsr8(Mt@IyZo+8EZ}fXMb?HdFKiP!-VAD|Cd3_UF7tOW*+E1gmmn| z2CC&C?RuT73=AJqePzN^#heB?tB3;kgN$WPh7Z}E@LU4!0G!8$h^$o|D~Y$zaL~CbiEHE zoq+3mcfJXXo>+s=of~v3IhOniZdQQDS52-lFj!>$Mp`%T4H*Y2V1nFY=E%nZ@?Xp~ z28M>L-~TN@2ajV*PvCq2bvt+@tT|Nk4le-ye;5xo8c)D-uH z_sGDNT|A$HE3B^pPTy~?F)&=H`u%@1G`x}112}xZ?g8f`570hC5$x{q;AYx`wJ3H4 zM>Up)6107nbDe=fpzinoy`U@yuJ^#_-g)pT^ss>IJ#agh38gf41s&y64z5$2_#|BU z1fWw>pmCHv*BKZZ>V8A!rCj&~s`)s;^Tk)LGcc^E`wcmdJ`k}k>dkcqh67l{S#B^e z+`u9(bAy550~T?U8w?B_^_b@S++bi(sK*?4O1S}AZ~q&zJ{Ih6@HmPW+~45yE|>}+ zN!^uC0|VtzwZXMB zdYVG4pa-=}j@)Emh-gF}w*b$xLi*c?bx7`@c~;1jE2KR8bCZE#LgR19I$Lmi20R`J zE`Pw~8K^x|h&EB`%IyM9jqnl)6o?S>d~Pu?JZMCWKlL$z;~CtKL!3j#1nn`y+aFlh z34r2#&MgK8fu`U85%Q;Cca(+YAgE z&Az_Le3=Cbr|38MeJF&$xIDLW6 zRbtA-8ZZz8LGJUp%fPUp|M!1(RQI{^p|tbC=7GkmmVwd@W`;+YH|H(`L&C)0|1ZML z1CKjkFZT+Vt3ks7nC5|cFA)E}xy!)tVe;?)VyNlHl@Hb~1&1HleO`PCIGR^T{WzO@ zp!@B9|33yd50Cp2K=Uk^soIm9IRq3AnC((fs}S1$y~n_yG41#NGw^Ug#1lB)k;{Ps zW)_fnnBm~a&CCaq!L$ifl!Mmw@Z4u$m@wlv{JtaP@(Wx)gZrgkd=8*46#C48GpM_b zF6hAx8gEUx&%khE=I{TWAoqLl31HL@VE2R5EhxV_@mV+?<}+|S#;4(QhEKuiET4qa zIX(fW^L!lcd;!huZEQWPy)1pq;9(t5|LMqm28JE8fB(;c#{;(TaRse&1D%tO;aDtx`Wz%E_?>im3NMy#tNu>2OSy)-h2&im!>>m zV33&i`#%e6zrvLdGu`>{B`_}r#S&&paph(PAH0C+LQok2%714-?pyf#zahwd;PM6B zJ_naCo}l(Ls6JbdHqPRReF7KMzLI&!z_4M_@Bg6YE2sdiy$ovXf=(|)o1QqcPWSRUb) z2f1GbD(?o>-v+waqM6y9PXTnKFl0XtY&;GW4XzzjB0dAo6P~dz7 z9Uy`<1KdHQxmc#n!248}JYrz*Sp55cC&+$8d(D*(mVVqp>l2(I>DvKxi4@jOCeC>? zmdBv`>7eZbM7nVV_3Oa(k~^OPXng_dax>6M0AvwJJ^|&&kjD%RD;EF$e-bslgUfBi z`X5++XLbjrcg$4n%?-Nn9!m`kE*L@WnLUpg7z9@Q{$B(N7Z*MO(CHms+-~3{HK3KW zpnL&}zbB6w7&2D;hU{Ah=UebT3l~0xQpo5m()k#m^)(_-7#L=(_zmA@-OL0z2?sP^ z4@&1IPZ$_ZtoV&GzYG%hdBVVO0E>9a69$GISj20dFfeSuB0l8_1H%d|;%lBTFf70# zegx`XP&Ez;3J?al=gt!bh6!luLE-V`2?ImNir?^c=Fs%)&8Gl8ycK-R8^{#Mc(KY; z($t5LsJ?{+^&o$3fvN{3FNiB4_2ikS3=9n`e*b3%6-?lKf~b$d=UyV}86Q3e<^)i> z!z^drA?XfV1qz)PH+jaupt0)re`(M{0I)r1^$oZk0Z!kbcFs#CtPN1ecrvKGXnDrK z@MF{O|H63B6M~wzfsy$;b_2l;WKbD<!-oCP`DsME7`#6r8Ise$NAtlak)iu! zTAnj7oY?;x<(wIi{d1l(FkIOG`@b42lA-FiJZE5dvH$mfkr612Y>(P0ks#v}Q@F{qJ z?FDa60j1B9mkbOUhY;(Npy#w9(kFNv2sD(Dgt=}7T>3-W^Lt)0FdR7Y`#&4V{fK%N zJf4I|=b&YzpeYGRMGGDo1MRRv2!VqXRG;v?Vqj=E3XM;2c!1Y=y7MVyz~_8H_wJg! zVqlnY^!NW)PtX`0xJ*L1N%=1+VW1k5?hW z6%;@DnDfc5phFZf4_ODb3vymFFg!Spn0M@F^2f3+4cz|%t+PCUwb;Zu9t7UL3UV#D zpMB*u14A3=Aty{r(>YEsvq;1?`+{X!+j2$n*}Y$KAPco$LzgF@wsZ zGjA9e7Mw-2JE7}N!1K^d35Yxh3Knqwf5X7=mFN5a4rn@tst4~Y0!^uAF(R)J0_lR} zJCnBz3@gt5Mn8uQJP!z7j|$yifIN=j%FVPFGQQ~u9>xVP3xTH3mbVNH9p`>S_O(H* z0gvO(dCS0X;M{M>IfYPhP&vBgEd#@ibHD$Cw&6nd;Y03=gSF4Dyk%f`aqc%{eKb@b zD1W?p%fRsA9BO?AGLPjQMExU>T5vfJxA*Y2c@SJs64iD8*q66ZZAOhVI?qw?u)>3t|vG-fx~6XI|c@e zOQ_)j^52z#;N}zNG4j-l);3I3G`YzbW17F1(k zDQCeE019W54-5<(H!%CfJ|7qu7;d2Ui$V2D$_EC9AJ>2XPXxsYqFw>re-;d@SU}}& z&j$tuha11)_xU2%i{N+w_Zvaw?hMRm@Z^rfj0G?7zN#l57#LpM`28RGUN(?F{(N9y zc!1_!PB}a#dIh5ejN1r!2|5h zo{tO+2{%#0737{JA3^tjLC5JK=?8RR2DrTol0O1+?@eg=0nVRw@cajofAW!mf#cTi z|Eoamh3L-(cM_rLljRcw!;D+M|7(Hd!TKY(_&9t(+fBjqeJYn=B&nE_k z1GiAz2{H&&JcG^2`NY8BaQippzDRI70Ix4|=ToSJC3A3jGvyO#zae&WK>peCiGkt8 zZS3P2p!9YHDi6N@3&kGP{Xu^|F)%RP`Hi$c6>a~|X9fm^JHP*{g2DwHZt?JN1=(Zs znSsIJ4zyj3Vow&_KOl7}pF#T%@v8&*tLHNVL&BZkko6^C`AD9s1`p1H*^AzyEhY%M0Xn zQ=s~fkqP<62XF3T$bAql;CXFG_YFK=SG`i{vIwh2;9TKgs;BJZ+IMIQ7H<0mwJ6{5)t`|0LwI)t5+0!TSkE^Gh6AsEL++~v#}lGo1zM^L z@;A6E=Lq&G_`(`c{yOrFf#JpL-;i?$!Ra2>Z>fY;FW~a$%{K-Hfj7S)_w0hzc|pdn z14>ZTiF{{Zuz2(Pzda~Lx`Ot#dvk&I9e@fm4^U=ffUG;U`Od%)@CLa(4{j&B@}Z;? zXuCFn=^obUBPZ?)}ang8cI zXn*AI|CZ2nhJC#V-2K?spMmCsu?$jz@=?qW28M!nzX_b@4sG`}Ffz@?>H!aKrZ-qa z4xDg6>E_4}28I{!fBy#^M~c>dYT*KpLpXybCR~s&&H?4WKR*~455?3?DxH{_g>5c!T4^3*LVN=O=JG7F^DG@oiuNH47~;&!-1F z8(h!TfXoy6^B>#z3dkLEeljpH2>*ecj|_DONPNpr1_lY?KN#oEocYPXARzn)a=r+X z`X^BJ=;Nb*pz4vuLHSAK7Xt%_@E`cTk7m&IejKi#2`&yVX!-}$S2n*G7z~8}AoWB1 zxcH#^bV2Tn`NhECApGY)Xxly1IpFqt$u9;53oPP2Q1d`#HnRF9zd-BX|6ug%_xxgD zC=mXGF+O(X7Xw3w@Sp!;FhijItT(?H7$yk+`47651||h^C(Cb$dgO2biOc+EU}zBj zgE7u+@|%I70*koMZ-{%4$7Mn0r2J-J$Uq9GC_WBwxmg1>Ulv+!Li?YP^J5VAcDixH zHp_!Y51B<65yc-kJdgZlV2BX?gK>_?o!<-$0iu5(=VrKr$}!M6JKo$(-h3ROeDmiw z1H%T2$92I2vLN@a`NP04LGsW4{h-JL z*AL+FL2$j81b40*H&YYBy`cK^$sYy=2`Q*Q!R0=9-qMRtfeCb~1Y~1_BdEFt?OSvP z-2wutDIww@yP)IVI)51$7D)a1uLExvfXCNe`C#)<2>*D1)~SM)WMgURfLl(W^0DSG z14D!CpZ_iJ@B!B!;CUjn^4%M>uiy*z4x1Nv#Rld;8o0j(Dz~2eWnfq#|L6ZTP=)}P zTWIzm$}M-k24-ea>iTmVXQk8diV)dxGjMaD0O6d(fB}=!8+oIYchpOl4fK65ktiYZ_#t z5*nW%bN+zLxBl~A2O9p+em|n!0`6~m@;QJm#YG<|gRX-Bg@?<31_lY+KmT)}=0Mw7 zh&bR>~F)`ZuH=0a>iPpLmG~t zc&}k#WO(54=f51tY8O6*Na+1xQy3T-UO4>uzaAtG?qB4@i^C5j3M83g4IM4i<;m0qHl?FflSLaQXAU1e$L!@*lY0 z;KetAo9PzjL3yuxc&Km7vwH*cq7(*famW)>pte8g||C*7`Ancptgqy zGb2NW`=9@yxeic!vY81qROJD=rV@OvI_Q`tNIMRk|Ct#XZn#s={wW~)J^uV>0Hq)B zH~`50P>B7`pa=rl4_dVZ8IL}~%*bHj`RD%`c)f(PeD?v}x95Pp9)XwdA}ovyJAD59 ze+doGJ|94}MgXsXLQE@xdurhMh!hq^h6vw3|Ia|p!@3VA z7(P1g1U3y?9)jALOIR2gUikj`9}3bB4iC`jdK`|RrYNYqI>N%ppyBuD|0+;;VJok| zh zN;#l>UBk-AU=Z-Q&<@pIs*RuuLg;M%Rg-QBvnHK3|`uKa7Tg@G(-S4 z^9nNW2`eMRj(|V^Q{mx*Eq%cHdCZ>p##2D%nXoZ3+z9;he?QzjH&8#wl@FyI1T`-J zG?##-euCDYpm1nmV`T6M{_{Tr|AC)Z0Bt`a>LKKKbzu69 zZCD9p{u4GvhKi6s|3TYIKvsY-Nc;~QBSS+7X8T@*9ipBaq!5Zh<$?}7BSS&RpZ~rf zF>v_@Zbw4Tzd)32AZbv#jbUeGm=N*@K5p8~lm))f2XwL>$o?93MurU`e<1VkAWhJI z$`p1+h8<|`1=+ubosr={$REf#gNXVFJii61yde7=A?=R?cHRKU{Wcto3=Cm^{^vm54x&KfAsmbh5n+G+^MC}P z7$lyH*Atg9~>6R*PJ?L0vp7SsB#P0Hrq{E=C52*gyYkp#Fibp9Qz45%q=(Ujb7w zW_9icUUr1OfEH58gSH_uFfgp)Vq{2&`}2P_G@U^EtKj}0Xh~!<@^G~?XyJu3xMc?F zxPsb&Z@3s4F2w!$Z_B{I0IJv9n4tTX6rj6=!RuGuVe_*fRp9o83O6IejkrJmLDO51 z^_Xboz8BvCM$q;Hv|V2ASYjU3e=gx>WT=S$0~zN8mm`7Dc2qTZv% zeg(K)1L{V8$DFrw=Vk((B7?j}6~3t*IzHgT%gA7m_UHcrX!<78@O8xUiUhO zmyuyX`k()AKrsl8f3$Q5>PJLDngQ^}1$bWvG(aFl6?h^7B}F(w&Kvjxa&N|;|A*o3 z#a2In%T0H_1B}cOpz;E9LIa#fLGk6o$H=fD^AFNFFxbq4w!;#bBM7xK=I}8xJjnX< z|0*>8G3tNh^6Ua5vjb?H0dv3=+>!*j?+qU#LqYBz$i4E2@C4^4Xg|TB7A-hkxtY?j zWMc-Kalf~!R<7}IvYek6-zz~$Eyeny5Jd4K+2l85c9#ThT|d<3=^iHwktvHf)GJQh8a`-!0%y&&fhtL_D4Z$8Yj>h#h?if=$saKj1v@LApKJW z85ugJ{`oHh^*6G9#CAScZl($FK~GQ>4AKtnZ(R{&WXPCCzJ3uQMh1`RfBsj2Oa!Mt zv~ma3&w=jZgO@wd_B?2RT8I!ML&x+#|5ZT);ox=%TD&2rj{wlRD6H)+90y~1Kn}*l zY`1{J0A&9aAw~v^*?;~!z|$wfesKB(?-u}ZA5jHT{x*1?035#H^z6bH5Q#N8xpRX$pIE{d(%b-_qr)S{$S`5u zpZ|-X_CVX$;Q6QmNKxYo8WjW;3!wg}jTj??zy`#)3$l9X&JtJ9yg5icC|~4&%-{Iu ze+{m5f?6(sW_>YBb7(pSh07i>Mur1h{`}tobuV)J0G!Xk=E1^+={MHw?8IGzH9bO# zd2qW~MVygAV%wkpFF+mvk3U87DFncV<3NIt^%g$jj0_L9{rO*vE8ipBZ;^E+<_1VD=!=9q1g~$)IKfWw1a&twpMd*^ zi1rd_Z6q`kKpOO(+^+C^0uh0>U-y9Ax9<<+zHPL63fliR2!LpXS5vOs)!efOOwbwZEJ)!19y(?6!|Wt`aD&qQ7D+}1ivxfDXTakH9RJ|)4n%nXYkx6kV^2@+ z+)SXW!7&R$Z~_3umy8r6gTvuJ{}ti(fa4#{9%%k5V4ez!f6R0XHVz!VIZ})aybr(q zSBIMi9ybJ=2i~6q4qvdl!RM&`W8$;GEC3)826EpXDMp41NB{iim4&ryeL&?R*gSAJ zAo771-vcHleC;{Vcq5NABg2G8f8ggf;*2lQ@C8~&5LzsO{TU+7$iVUV&wmZLKf&SV z$_L9g2zR;jePCrejJ05bBqY$d*%E0+h8It<%>PI;GCX*SI`0n&olz1!1TNfyh)jUe z5FRFZ^*v;DNcIN^eILqy0i_e6;-8@W?@*ce5dPG&5dOafkns2k<(o4?#BHGA z!n+V652_l_-2&n-xeqbV0K`GU1^1CSSePGfLj)b}L1`B7Dr^Xc0`fH@s1d@zz<|TN z3!t^cc+79`qK-T8SV0r11AU-Q00TqABnZ7>CWO8)2SO_>gwPv0K%G+t28SgO{)D9v zI-vzZFM!e$p!zpJ^)o>A2SD{-SPW600M-AX3Bng>hR^{}+MpN0p8(|-K=l_u#Wz4{ zSo(?vFA8J;PXU7hDie)giN^1Q@?mo>FHVDWF))Cxz5=ln?n2V1B#6Vnz!3366U>qZ zlc4j`Aq*)9#gG7%2hC%FgdnM(f#K`F|M?*P2dG0KzG7fl0p8TfzyPs@fguBWsTF8T zFGxKHXyBWHfdMqO1L8}72nGfQP!$PbLed*F)iTV0Y6R7PAbCh`WMF{J;RS#<=P^KY zGXnz$_|RH}KRlq5s-P{(ApJ1={``ly{{g7u#=yYv7s`kE;~$jY5ew4A!0;c+hq>zm zl)oVoBL5T0XM-dsh~OwmKKKhsm*@tlffo-jFqlDUHz*wjrPH8v8I*2=($k>yGAO+b zN*{yL*P!$>DE$pevw;r>V_*=2(rQrJ3`)B}=`bjr2BpiObQ_eO2Bnul>1|N@7?i#S zrJq6RZ%~@;93%w9ptKs4HiOb`P&y1sr$Om5DBT97r$OmuP1j}U8I;}zrH?`BYf$P>#*)Bu<52e+h^a5r`ISrA;B!kN{(-rcQvQm>v z6p|B*iZWA+6%rMk^_{h~6*BXROA_;vQxyvGGxJJP!N-=Mn?R|4bn^z9ZvbuA!DzU9 zzCu_ZkS|k%2n{INAOwkD2Pn+|r4I-~#2cWr1C(Zf(gy^f@=)3VN;5#|1N=~VDD42H z8KCq5KBzpDc7W0hQ2GEbR31t@KxqajeSill52YQTGy{}Azzvm$(hg9X0ZJd>g33c_ z2Pn+|r4MjI<)O3#lxBd^2RNYeP}%`XGeGGB>`-|q?Es}2U^Fz=9Yi4Npj?uv1`72b zpin=9C}r+SvIk(Ek+b zZ=g{B0SfgqNK)v33iUTosQ&OVlCeg+u| z{ZFC(1`72bpin=9EQS83P=5o3`VUa3pFxg7|5K>HfkOQUDAdm&Poe)Q)Zaj%{sUCf z4|9hD%seO!3tw0~Il#gjO2g70EFC+*(j%0HfeF6eSP#V_$ zhPC72{Q+pZ9M(U9^>fhsOELc-BhV>On$!+dHdJ53CfJBGX(rS{^=}|k|756shRx*J zzZj}NflU2dp!ye(ssAuk{|z$rpMmOU*g~HFA3^ndkg5MGRDTDV`kA1o9qb@ezbI7y z4>I*DLG^2FB`^F8q52cZ)b9b+zky8s(NO&#$kd+))o-wkJpVUB^%sz-zYnT^0h#(2 zLG`~NQ~xHYeueGi`TrnPe*~HOFF^HAAXEPXsQwdV>i-DU&#{9%|1(2p6g|k)FACM) zL8g9XsQw*f>NkPv|3RjHN2q>{o#cgI5LAB#nfeo<`WKL?zXGcN1DX2!q52(mk>~&U zQ2iBT>fZy^e}hc@=b`!~c9ZA+XHfkaWa|G6)jxww{Y;DycV8e=zW`J}!ybq^q_y4{ z7-XRO4an5557nPQrhXf!{t0C2_lD{}L8kr)sD6&UWQN~8Nc%e;Y7VL4p9a526!7!ekj3ZbJ26 z;DX3NNQ8U?)O`Y9ATkgVA^!=gzW}t?5{vz8k0JJ7z+wOXj}Z9{_aG)iNQC)%Q2h(^ zAuVI$(A_E~2^2t#B4&X~xFvB+os{aEH z`)i>31&kmjLr8@AW>EbC*C8?x5+Oegs{a8F{nAkV3fCYyAtXZoCaC@m#t<0@iID&Q z0TTWO;LFP~{r3i{zrhru6G9^NFM`^C;TA*&LL%hVoh{se1?41`3;--3pJ!$XJ+gha?+ zhq}+e1|kC?5%Lkxa!=tkLmdtWFRC$-U2Eg0KRM<(|oR{ko+y+1knj0 z5&C~X(`UjHhzx{8$R|L}e}F@O2UNb`5kx11MCdn!rjHHI5E%%Gklz5+FYpE;10fOe z{!sl5E)W?AiIBel)o%d4`~lPdDp3CixI%P7NQ8bvX#W0yBY!(U^((kRbV5jk{#>a0 z7JP(=q09e(wm)DlLzj<*n*RZZ`DIY~1?~`?5E5b+h+F`bF908o2D1eu1HrrxAn~67 zzElKL{tUE!akvaI8A3wzg2)c2{~U1m?-JC01MuN@nC4f%gV?Wd5MnZf1epxM-=N{Y z;TuE-LPF#~q#D%y47?B-2nmvb;1H<10v|*MLPF#~WHwaZfgd6RAwe<_+z6FV5P--) zNQfMmdQSx`7!#KcW7AAOg_|A;DU~1V41&3Iq5OPfY#h(D3>26QUDBg0+H)7f|;pL_lO9 zBv=Yee1!V{z1JR!5B2{79RBBIhSa|X z(GZ;w5~1H6YJNftL-9Dw+b0f+l`Lhavx!~B=f@N)oPPK+7Gx2GHCcEXks`29n^dQ@FrhO^O@g6(#HcF?stHu?*)<&n;|5`Eg+Kr zB}D&(WQYue1j#_K22}n)3Pc7%LgYZ?RA~4Z{DsIsNRSK!he7p!03Viz>Ha!s_&DGQ zpNY`$5zxXOKKW4d71AL#Lr93-AW{MvJ`G19G7u6Z1HpOF@Hrp_k%5p9IS@G?D*r$l zA_E~oG7y{w_5Xz55E%#wkpq#_q5gaD7R1B!|8l7R4&dy8S)`A z5E3Kak}6hmx=kRY2OI2sy$3E;zZG4->+#`nO7>S4+kK+T^} z3NaZ%Ld*t{2ch}n!as-%gapYzFf$9J{5}9alnc}Rw^09Gz~R4csQm}ZASOddh}j^r z2V3tmI`=jQ2$NP#~yxlQ1cIf4;907p9M61Doldd3?U(QgUBgR{S0jo83+lIf#4_5`H2bO zLkls@H-+ZU1O(B6mW~4**|Eh^c=i)c+4&fmCD4r$hbkV1V8KSE1%NbV6)~ zkPx##X|rj~{v=E`yLDmqGAD zsQVQ9AuLgfP{Kx7~!NCtu{pyn&!@ZU_R{}vcxkKfHu|2_BzF%RARY^eVd zCPQQ(Bt$KUoB_3;VG2YBLV{!<_yAPiU@AlgLPF#~A2njM7f;FJ>4W}S75E3E>A_JiE6BHpb5E3K)PKg<{oewY=+?T(EO=z8X^NB zA#xzn3>v-@rbA>PBuEB=L!j~pWhBt#BGI>6kA!~I*K>2CuL_m@N6?|{Soub}Qf zfW!SoQ1^Y932_;O1i1`?CqU&DWL@-QTQ2`q%jKuE9*nCOD$e+Td(Zb10f-DAo4jZ{5L>k zAS6fzf?3%h`L6*-{3t==rvQA}FsA!7pz(768nfu;`$OZ$;T%K;LPG2YkyoJp7uW=m zfsh~>2>uL}ci0S(fshb65Gep1Uwp6yA_E~oG7zi>m2cPvk%5p9IS}a$l~({C%82Q| z8_@6-(1PfMkRY89tN;x^1svgL1r0w1bL`~_oS}8tT3SINWy%YX1cZ?B>6K${zq<8i*OcGoazS;3;U`aQ`%@`x7ogdvw>< zp8<#aH$vUda0%ja2nljI1Uo?8H$fD;`EgKr199x~H=yo+@C0HWy7>#B?su@pu3r`E z{sn)q>#v5|zW|5(e?aa30KS|DGkqu=g~ZPU4(#@OK;=IOW0x<0$_EHxm!AQZ-yncp z{s2_I0Y~`EfcozPj_`qm3U0C?7_$FRAGZ>`GTWf`3*3PygB|Pj0_yI)PKZ_&$g;7!siR-$UhL=LN#t z_X8>pRgRN_rAJ&eE{zM8!fe8&0wE4MYYpTt(3xs5dq8*mfy6+qQdi{|#9k==?a4y~tvsSk!^8#za>KI%^JH4CXgv z^FU|np{oO(qlYdAI%^MI40OI8x)|s2Arewv7DPj6cV}lS1&z?8(!7#V1rt3BJtJL1%Th4cP!l2m zJ}Vw%2PC&KFbF``>YszoNg~?^VuQ|#XJYU>#>@b*`+*lkH!COtKnl<>M7`DvnEDR^ z5cQ0ph(%Ki;zHEBaj-zmX9$I;H-cCPQi6gZ>P56+>K$Ss>K}p%6%>^a4n)0vJWRbo z0Yv>#Py|7gppX#t$0owmGt@)W-vSi?C@LWwi28F^Vd@{uf~bcsp@%3!A|dLvKf=@> zm;+I71WM#c>R?QW`V;)Du<%&~QNI#zcsA<6)Nfb@QSXX4tP^H5n1+~t(;KFqVHHIE zTfFAW=fc!~SO-xbi#I(;Pk^Zp*a}g96heXBfJ{K#Q@jSIUSS7B;UZAxK~@N5L)6c| z2~&UJI7Gb?r~-hhL8c(;r%JHF!t*{vy(nJ)ZnuD`S9lCjzZ0+es7RRl2hSnu!S~K1 zI|RyxxJP3-OuYdYB%(qg6jT+0f~c=L0#iSM7ot!UZ+;Pc2~)oTe9;F319S-h!eA&1 zV*VFqc4+t<5Qmr#U2+3eg`gnnPs+j7OG4BuK+m*AsD!W}>O+fR>L1uZ)TeeqRTZch7O4N76)PK9bQ5dzJ^2_4)r}p zVd@RuK-Amf4gXhfVCoCLK-9yQilAEnanBKcPN;tqenZqdff_^TisAm<=K)i{fgh4E zRq>W54>DouABaHI&jwX?IL)6i1*X110iymQUh}VPgQ-tYgQ%Ylp)hQK_&4GhOg)1( zMBy_f45eTJ&>c%m42!GeC53Fjk2$Ffh*I zU|?uS#$FLJt>9r`V7X!eI_sE&eNGw!12ZGX4SNO#W+wJ;77Pr`EbI%xf^4_If~<8x z3=AwRteJTw49py?pdeyOc41&(ZO&w1U|>>DWME)z0Wq05*zF=27+5)&CWJCDu&x1# zv2iQ_*~7^8(4K*TNiu|ifpstFwo<0EyBQeR4E8cGFtB%)GBDg=|7y#`z`&8an}LD- zTN22N9P4!$7}$T$0=30B5|tSk*#Cgo0vvT#3=HglL2L<*hzJG-_J1I@0!NcR0|Wbi z5L<)uhAQYJW=0UlfKytXfq|V7%(39SXu-h1&IIN-a0aO`FtD?LIUbx#6&M)US;3qD zPAOjo26i?uCxUZRFarZS2jeCt28IOA>;eV`b}mLx5uU-h8zjTS2y$=%=VV<526h23 zr-akPn}LB{h_QfyfuVvkGM0gXU5wF}<4AOlnxL3i#>-~>(nvgh@9MB( zVB-Ke1uXi6iGcwuD$Kyf@rsFoffuBfX;LTy16x`@0|Nsu$T&6*5XsB|N}3GJ9PHE6 zKt@Y4GcfS+ONTHpa0)Rl2PM^`HVh102KFGPa2f*xmmw%Vn5?)M7`Ti>iE#0Cl+0S+Dp zh7Lv^7e)}D6RgaY5v-0Atj-lI!NGYUoq>S|A|t@LJB)#W$Bhx>Q3=lHLJSN%?qEF% zoI8Bs91YIXoD2*+-i+S73=9UGA(jjbJU)yWybKH$oVp4O3_QM!GkF;p95^#&85nr{ z7*F#uFnDlotYBc^@n>Y>V_*p2oRGx8z!Sj8$H%}B!C7mluT|We;2{JJ7)Ptjhfl+WK$fXU8!JwFA6nv1$z`)bUSjWe}AiyYCYtF#H)5O@t z$G{-LDCk(oz`)ZCc7+0?;AANV2A&qiX?zR}8jONRY#A7MS{cvsF)$b~3YPMN5-;N- zJ_ZI0M!~HhmF@0HYum$eCSWUIe4ywj2fq zo^CKNfl)A{gn@yl2h7W06x_|nz`)ZB<`pms%K0-e@brPxNCl(dc3lPro{5a0blt$n zz{UYeyG(~chUFwMGcYiPfugY0jt!JmT-X>Ggg|}=VPO!#z{a7$#=yYzG7jX26tJo+ zm?}^j0ILEM3`}-13=BN&p!+S@IQn6VKoJO*M-U85s-VQ$u^DXKPLN7a#y!EtzyMan zz~luEh|6I4n;>~m_5m&6hRTO1F);9SfmTGaar^|ygY@&VGcX9l2?jO}kO!DTK=ITo z#}4wJ3Om$)79gb{XH4Nhpe-Dy8#5!I=wISqxGMvbhIUCCFd~ zCNYr8Nz=h9=fYHi4i5nP0c;$EfGF<>XJFu&3>r*h<2VCT{uFL7ND){g0~-gZ_+{e& z)oDzfpqQWg2W$xw2Lpo;$Uo8?(13)P4>E*-i3?PW%mb-pT5in1z%$>F0~93YFiqZY zO&}Hn8wbcFrt2W{7sP;-Cc%_e! zk^H1A2A*Xg=W~eUq~4;Mfgv&k7J*g5!=j0|U=0P)I6pga$D%@T>;0RXAo> zF);9~0eMt|V+scY1J7EJ+YC7LK&gKnh;6|!6I3;<2eBPEZi8~#29OgxIKp`u7{Df6;CTsRYjAXd%DPve3~s=2 zJA;9N=e02#1A_&}OJ@cKo;M)21IIOx`ENmN4-RP&1_qvYpu8TyQLD(n!1Ep?9>D=R zw4dh#$jk%|-fRX2o{u1Q21f-80|U<|5W9fm3n&IYgV+@uf}n!w3y9so@lb<-f#)kI z*LQH#nldo(d;^&|f#Wte0|U=@5PJqkHb~765PJc~EKtDz1hH3ed7#Mi|g4hQ*9;7ia@caX@PjD20s*(R7_61I1X$A%!21XF)24@baiev;= zcMmvwK@~0&Smp(13CL<@Fy{m3Ly+HCz?>g~&p^qL70h8^6#SmTz`(->=5a6zo(07z z7nmo&D3}1M*SNtv2}VI1P^rcP<|!}=E-+(Y;Nb=HG#CXBfXY?@Mv#vT7#a9$0vQ;1 z1Q|hzK|Fv*n6Z(Efq^qplYxOpgb`GZGH|j)Ffj0lg0*vSzBgoG;1OdKW@lg!;M@(W z)x^OZ2~HDG#VElDDi9SokAdn6Nk&k?r@_hL$iToO#Rw|f3^-#!4GL*SP+GU(Tmmvp z7HqTwXDBFJ$$?GtVBmKHr89ZPuiOj_;u1WHjQc?DN&rQS64YIw>uPwE!J0WZUq>=9 z@Tf3?N<{$%{(N-?1|C($ULFPpaRVN8MkQVb2F@^0s?=co2r6Va|A5qMGJ*;?4$eAI zY-lm6aezFfQOUr-qsq|- zj~?SG9tH;fIu8a09z#YeUQph5;ALPC1+}XnnU6mj)J!pA1QjD}9I+rtP&*5pJsJ2x zX9DsVGnRmLR`Ehhjmf;QQUlCN0<~h6z9Oc_DdEdO*+DmP=i4%TrOqyuCgs6BC z_~DNRmGhR2pt_xZy%7Tgj}@aDAIPsdd<+aipavMIK?OC$AEeQm(H*S77o-5>LQuO3 ztN=oQoWQ^@3~E`~K+KilVqoC0g|L69F);AhF@hQZ{N13|q&>t)tr<9;dNMHZs_*7!VBp}W1+g_io)X}2 z$YWsO)dH!JU|{3m=4W7#*j5G#EI)n*2KHsa3=A54+@K~S2Pkm(c)&3xlAc<^z{hJO zz`(#EQVe4Ag$pn+2#BQQCo}NzgVacH94cdA;1d89)Cvr29FhDC4E(b}#qMlIP+N>y zfMq{70|V2;0tNp&+&b1JiL+1_s`+>--E1OkcGa82H6O zNt@|a6axbvhphm}r_KV<<`KvbkeC3unt_Smj)8%XGYPCH1EdHPWQ_u_)DE%$l55yF zR)EY`FlAs6;F=`Bz`!nV%D`YD!2cQMd;tN3^92MT&QAvQs>DF12yobFGB5~8g4hxq zt1K881mr;JRDokj90P-ZD##cOjw~Gp1_3n?+kkK}yg7)KIr$1aUY)(aUHL?vgM_ z7nc+2&ZfX0|TQM<9|?iaoSWf zFfe+9RS9q|k7Hn9^Z|1~DRhM(0|P(E;f$_~AT!xGK7d3(CNugnf@G!E^MJe+#F!<- zz`%KHBLf3tFqqB2DY$@vfiVO;B)|c&C6uv12vm+91lb=3Rwlt2CB(qM7|vKG1S&_t zevAP7QGI@8wU0{w1=V40*2F7kyP&(IO;6DZm{vOtE5DV|H z?gLo}%8-nAS;0vf+_`7G%L-1@(gmP|bB`6|bOGs-N(RRJte_B+kj^j9V_?tWn1MlDgK+`7hzJ7%c>I8IAv=;43)#U|a2^8rX%RcZipA_; zDo~9RTvn=G%kYpb8;9Mm_R#9#Z@1I_?HVA7?}JN85qRXK7#l$Ap4}<7#PIW z8ATZwBvP{(7?`w*7#PGg1VQ{VNd^WcTX6;kaZN=Kf2lJA15->11A~ORA&9SG%)r1j zSB-%|Qr!{6k1+t*ugSn5r5*_4`)e{VFkQD~V35{I1o7tuFfcGx^D!{UXcU6@PDKn1 zObI3o46^EtAbtWU{JzOCFvzJ-1o5{BFfcHcs4_6ft1blby+FgC>%AEm6x26@_!g`T z3{1hS3=E1|2SNN-QVa}CWoZlyO6nIu{JUWc3{2bf7#NiN9)kGG$`}}!_C_!;D3yK$ z@r%qD7?`rO7#Nh*7{wSEBvxrNFfeI)GBBv93WE5{;u#p2&S^0)sA?#J`03@KE=&Xi zgPN)#h@TDee}WSOgPNKnh~MGOz`(Rqg@Hj`H4wxvash=G8v}!eY9fe#FouDFX@xxl zgP?Pv7%1zPi!m^WP7{NbuwWJ_FA0NcQsIT5)eCGKbHo@JctPoljRQ2gE3++*fq_LZ zO+=i5K{g6BNi4e^l(}UW1u`%&$zCmCU|^O#ufo8tA~P1Z1tfq`B2 zgen6AhpcWP0|Te*eK7_GE?EOH1_o}~22kTywnKn{fmc?NiGhJnw!)TyfuDitTm=Jz zVEb}0P`#(iz@VwFCCO1A}}?CWsBnK?>la?mM`sQv{du zKNvwpoRV@$G6Um3NJ-70(g!LktHGIxfcfsF%{ZspDtf=WsOMqvqDtirn-71_s7xLB`8)`Dj598ze8v zz#zv7s%<+389&42I|V^(1~v{*IV6_~^51bmMqx>q|BnlT*q{Ms5e5c1>mpE03o;tR z<-ZGp*dYBN^CP)I_6sov!{wueKx~kHkh~!)0|R4?5Mwc1zD5YdW?@q+@XvN(U|^gk%D5S>f0`(W4NBi2^XJEdlDsJ6Ww`t) zQ4kxHzCiNV!WbABABZx3hRZ(?1+hWl2a2EhWeg0A|3n!uzKd(AlHC=dY2s(fKZ>_2RBe4K83KMKE2P5@acW92@DJjDn~gO7#Ov|LB}X6 z%fKLC2I7LsQ2BBgOF5H)QI`$WAX3g^VAKP%7*tl64^LRlc^ znSf1KE=nzCU^D}(XHa?1$-uz49n4{@f@+3vA;tI}ut5wetxOCIjH@B)7D3fPxDa(4 zpz8i4GB7Z{0M`+W`=IJxu!6k7pz^nrfr0TIM2<&}fk8ekw*({rN@gJMz5}a+xbFj4 znc`MZzWE4dD=D4;wVl6!ISdRc43!KFjE}%w7e*&JPzxT^&v*o8D=FssFfcH_1j{NH zGcdjavp^NPha3X~FQ_Y_c6cEJ15+9(#+kF^7#P%3B4#rp7WbFs^4|oB%TU z2s4-iid^~J;&hM%NEw5Q52!(&&k2%XWRPcIP`KR4z`&T#31+i_@+3r3(b}JZfw2gz zP!Zg4F9x$UKguyMDEfoaWCbT^XkSTj4ycb;1(s!CfO7kwTs96+H$d&KHUk5*0Vo+T zTgWposGkUf@zq0(K}=z01_t#NRv;!Qw=u{mJ25aYvNJKp%7gMvCWwt_2(UAOjABr^ zYXPc5!TmbMR*=HHBnHM-aKOozz?lpx=0Xe%jPXpM&JE)$kh)|T7m^R-!RE;qSAm2< z_G@LNRxmIoF#VEeU^u|Tz@S{pz?jGc$^^=(r3{QoV5X9CX(w5u z!5j-xqY7#kR0xB^T;-Gt0|R3sM7A0(n3 z5I>ke`G`Sf0jRDG12<0?#g!NsvMwEDRTt1+6q;<1iBenXRnE zz`$Du^5}F>b$STgLSl@98gWPvVZx2h9{$-GQ{UM!J6fB7#ME~fW#CxgPeCuKpeD2i;Y7}6y$R@Q0&hG zNn8?OG*^bDj!Oa{HmHpTGJTpB0|Vn(un=QD)bz7p(;?w{7UHxV2F7y&AbAEhj#6a? z240XADn6jXY(2Qm%(xAzc|BM&$Z6}r&XLbxV1%#~b!!r

HavMQisAc?&FF-QjPSG-c5F6BJ1Z8(OQ0|oCW#mwWMW_@nhz)8Ki!d;#B?U7u zFr5ciU=FGb4C=c;o#FGK=}Jglb{^z1^&l>gs)eAQs>-x*1_nkYt}IY_1tK6lK_xCw z1)z8w)Gbp6vl-YpnuHh_VJkrzjM|o<`bW7awS<9D2h4_aM0G7e?KveyPf&j% z7R&)Pj_!d%N|Xy6j=E}~=m!lyiGtkD?yMV=4v$t1_f|ry%Gd1EnrY_2l-Qr2P``i zE~~`@@*soCY$XN;#%lr~4&zObEI2x^fioMZ{JI8CZt^+#S9G{W~GaLB#>ode8;00b*2Chb0DGaAJ^8PAp(x)a3;YHZZ7c1@&WPAu2uL zDrLbcK?SiKFT^)9KyfX~1PUm|hah#@>lhdqMVUa+D4&{_!oVm7WQVnJ^aDyfj z^g+{>po*D+LFE}A0|R3+q(zgf0cvm>g7_fM$mbxKX+{i;DUfzf4xGuLQUhwP7D04x z2k8d2hl(KTKfq}6YtW3Eim4UGYtU@_2m4UGo%w$lx23`K3s&`? z2logeR-ESmwYlU$=&W1doqF-SPXsE`J z2c$+}u_yxrqahC{L7{31p4?LgtyW>Q=VPIgKV#T-}WP1vT2`UT~d_eY1u>y%IgWH5ttw7?SvS6AONF!)TBd9V7 z26f-oTQTLv%AR&UvcCz@R%1w1%32fkAJT4g-TFNKRicmw|yX z7F=1uF5>_#!UV}N&QZ~2U@!nzE)W%rp$5nH!qqc|FzA3rBR)Vk27xMlh`u;o1_r}7 zpc*dJ@EeE`z2^gA4KO0kj`#d1_s7Zqp4uZ7&M*? zQwUbHiOmE=o7O=#Zs>r9Az&Ld5hi^CnUoAR!YCI^flY!bGyuiUJ~k5&4Kqn!0W^Kp z3m%>hfbRSREjR$##yF= zvB8hV3@T>yDClfy7ent_3fdEs5qdL{;j`IZJy z`4+H+p`36jkj479L3JHFcreQWsxS~F0WxgPR(%Er>xFg<42&G$(JfA91{TQ9GHZTN zw~vb*w2*+4*#Rc{Hkg5d(E>dB#mT%N5ULhD4rU1+mEvU9fbQwF2!~n+iW-oOHsBF5 zYw!peC-Z_|njk?2%SNcOnGokQ6@!HJjX<^VA@I-0>$IQTB1MXZN0qbX)7-0uGUxLe!fx#R+&2SMs?(~b9fx#YJ_*??3b7Ns( zZ~!I3FHjf3HkdJ9W9u_uV9*7n!bL0$3>N4L^gsy{Bm+*GFj>%@EsUU~$;iL}OPUN0 z;QVqA;uW|BH`v~TEC6MgqaX_e&=lx_@(@S{YynIbbSN<6CN>ig4Y$DY9@O>gS1>a; zet>e0tYBtfu)GN6-GT%)^Jm5oE5;B`X4p1pE5<;kVg@V5AVxzg#t5c~5ey7Epm93` zsB=KuC_ye~oKs-Pz@WQXmVx0P3j>3bB~<=D$QJ$IpoW_uI7ngpo~MFzfukQyGz@RHD$G|X+m4U$` z5h|$ywLnixj)8%Lje$YWOpbv;8=OU4HbE6Z${7X*45RX+ioktVUjQtKpkP?Lj&gMq=h zNt%J-Gt}e*j0_Bnli1me85m4JY@^B23=Evi2GH#1=z1SyF9QRpY=LTp34uY1D!e6{7)zn4E|6B(EU;jj16o~rVI?av7n)AZUzR+ zB&dQmaFm9EGJgmsvja4BGcXu~vSB(@5$L2w#(FkGP!?niF-c%zU;t-2J#J{GOM&hy z^Oy?_svc92heJUXq33$2*g}X^%%2%U85s1{K@+|6SV7Ub0cs8OKrRNxIlIk386zo+ zfpH#q6#qIm1B1x~CI$v`a4J{~)>g#Bz~D0TAtVSuJN!W`elrFJb8w&H5O`_HZqQb4 z_cKr%Hmzi4P&b1(jl+P!{X10r#7brc(7+Wa#LGaD!O6S~TGukT3qh@TypowA8KRld zkilIUD*R(5GeZMdIE2vvR4kM+Ft}SoB|!$yhe&cVuY(!s=mM1%U&YMe=m+H}tpc^) zRP;cNu3M1y`$sca`~4QU{SFyFy~P6(R|0j@AMk)y3xP)VK(s!)2Ll5mBe+-p0BR~| zQxGU%80XwHhXpDlc%bPi4=A6424op|K@ER*UIqsHG6qIwUXWRjco`TVoiG-#dB&hP zc?mW20BDkjh1c+g83O~`Crfh%1_s?TW(*7ofEB%m%o!N;o|-W*X!9{J zINCsEH9=l?fpQ?>0Of$g!IA;Wvs(o&jv=Xr!Rsz)o`;{O04fX04ItYX=dfEcFxaM~ zF)%RlgS#V%e4wJ2fl+`5By)g|fx#9$ASwtBSg&7!(LH>?~x?%xnd?brYccLW&!)!?X;BEnx zJF<$IA=e5V@SubUZa{Uw%1XxrP*tE_hT|1v&O;=Q_cJI5RDJ5`i!(4NKu_3-2YDXc zQWCRfV9<3JXJEJ}%)nr1BhJ9U_6tPLM`r5Xf0}4=R(qLfFp8*=IX)puzx*MPhK>Z$&R>nCjHVh25;0_~1MVSb!i4Ebe7hzzq z0XLl+!6ty(YM@!gO;D3AfWoiYY#GQ40rCtCzeN}rbY0~c801757%UG#RY4pS!c?rl zz~Bt(9>OFTKx6MMW~Zzf7)(HHXyev>HdN!iRm=?EtYJ|AX~P~tO8gTu7#vqa)q+|e z4Epmy?aCFVAm75ydP4a26Wq5D6`;jKkPZceUnL6j?Mko-Fy9J6PrWh#Id`?`6dMKx zU65~&i9&q~+V5wn1XYFMTbM)wNc$So9X1RMCLlJ$R>OsX zfl(LiR@lj02;V-ig~csI1&@lz$UR`gV&L z1A}D~R27DAVG;*G+6_&Y*@Bv_EbyL@do)xd$hVhl@%uI#suto~Gte@yO(vkYg`J~@ z@a;J}m~SB}uETu`;R}eve7hNJ0?fCUpjLbUId`i`fgRMhG2&3)){8SRSU!TP!tgCj zLIE^pxXomW9Rq_2hz*O|-B67n-yX2T@7vQ*wGiKSg2pz?!ES|}dWZ1s9($N?Au1M$ z!{QdgzYX`T1=s|bZ;hbm=LLYAYh@B=5B06O1k|@)5)2HMPEb`CzJ*CNfV5kiG}$vS zn1I+Y-%3L@f_%Hq9=~t3p=u$%T?`t9xNQuITi6+d2;Z)9fcX}pB3A+asxW*DlXw8qe&4vjfq}sU#D@8{8mbZG z+c^&SecKIH3-K*zH7O%A*sTYk5(wYUaD@35qQXKF=35BAP!i@_7O)90-+qFcB*4bN zz{qB7R4_}yd<)^*NWpyD2{r-d+aRbv3P8^7Hqvl{`nE#~ z>f6;)pdJrY6^3t*=qD-C*l?VAY%^AOMS3}i8e5(P9TVVr`Z((O2BYd0W0`o0I z#Y|~f+(P)5;Jy_Bn*j5z5cHI519k=mMlk~x7pQOLWT0{DD8s;DsRUJp;aiwQ0!X{K z0jQER0kL6m`y1*5kZ(g=@cWhWR66>RY6^jfTpD zeCyhTpfRp=u$% z-3M9(>#hrmTL$PU`v~9axWjx4QSl4zTL|A+4(3}Aun92V8bM7806Eu7_naHlxAk&R z-!75^)wfVp7`}CaN;H79d+UAz`4+^6rDSQSJjl0l?)ZJH4OI*Atq!QUb4CZ`+XSct z!naZ$FyBH{9F>E`ErkCM?%T6q6JWlb1T|>`$hqfr_P9fR>m?8MZLU11zJ;p7@a-a~ z!~>A_3px+nLG>+C+*U*7LB8ej!0+2`s9K0`SwX!5VX#|aH((%q%i#(0Ekwlxd6;h@ z{FCyqxD^4L0Q2o9s1*X7pi)X_l?T+fq6$#oS|~6uSTaFx48ia%Ou_-AU0mm!2dKW~ zM5N@~P~$o{{Ra0fL`Ad$%(oE!6a|=XXM#l5ee{@*v-WhGwl8 zL*cU)=siAfs9K0`K{rk^{?h{a7Iym#!naSnU~vmkp{5A)ErcJh2=ncKun92VZh~5I z0pwgp?KUr{Z&xcqVBlh4U}Vx>$iH+iPCS6~S(W-8F>p?ImxRZy_o^!hH+jYbn8es{}Ry=361?txX0X=c;Ixctd?# zrUdovOeIi#3sr^TTP3JO0!X{6)(me@{}#!&zo9+=`Syr6e&2FK&4Tzg88lw(q6vyy z2dD(Xw+DP+zJ;jRqXdgv2>&bGx2|9lV7@Janlu6ATzAbRAE<8~m7%^(Q3lnwP*oVd zZGuW10BQHoZ1Vxtw@AK?hRTC{yU7Q?Z?mClA->H44Ioa{0QnYnPZ+|t>wICpg{Wv( zhWQr4KcEbY+i74EV7|Qswc-QFxid5Ze4)POQGxnaUj@{^g{s2v?IWm!0%*12OpOv> zP<_jdNXfgQ@*v+X@Wt=j(@?b#->xZQU|{^E4)QJRRyTxi=lH>V3sDiI0`o0|->m}k z?QgINFy9(M?}rNjIrp!+ogdV<*I~Z>1@f&ER27DAVG<1>?f=x1{6PI%B;QIyHG+KG z=ZD|7+EBF+-+JmZFfb~B-3q(I58>M`f0%C}Dr8k*zJ>5Xr$9hP;S|9pz4?x;g)C2rM^(~TbtD*8B-`4o!_iZ;+EyTAL zpi-((4HUPq8yOM4tq6em7NX(>+_wApw0Mxg+YEa)! zPy^Ms&>Jo>e9HutZ~$p9RkI5K)weu|lzba%Jjl0M0r-9U8mboJTXE2G)a|Mu-@@+K zMEEu>5awHmip^@UxP|cFzeOMr zh48nl!{T-}*aVnwH$knq0CMhJm2WWLGHXD6tEK_!-$GSk`1TM~f&sLQZ=Q-&5U9T8 zMa1oFs65EGZbA5cyBew%;@i)l(esDOAm2WKN+5je6b$n%M1`jY%(oDJlLpMUkH99t zd@BUKrPu)E+$YLUf}nAG4(8jBAm1uMRbluRCXoQr{#2PG7*yXP`Sv%|p&;Ly1mpKD zH`Ee{Z^c3VTO+VrVK*rwd}|N_^DRV$h$hUp5WcS_%(up16JWkAf?6>FT!nbmvFyBH{+=u%X!k5s3`PKn!0?fCUpjLbUIoC<)KnT>gDOyn9wrhdvTc|1w z-#&s$D1eqlIV(L00oAvBh?Kk=Di89lKq!9So`$N0_?8Q_N&0{y$hWXt&Jn)l34{3- zqGFX6EN&tE$8g^s1e*Z!tr7IT^Z<}^4=b(e^tp!tR_$`1Tjvw-6Pj+A!Zj`0KP`ajOG1 z0p{CDP%AcooU5leCk*P_UohXwf|hA6f~vysEllD8NV~q`fiO^gi{#sCs78=)--O|h z+is{@h;LOvTPwO0KyiBjDuM9ri*Q)nLR2{Dz37qZ#RKWfcZ8EYEl8nxm)BbBA~vV zpbJgOn{`3;EmReTZt4^{oIRZf8T~LB8DRV9j+)@du3d6TBi3E`Lm$DU6 zp!yccx4)qd1^ISK6n@`wLoI>$)(o_^eWeV@w+>JVgl`u_!+Z--k)sduErh>R9~QT( zz$U!tgCjLO~ETlP(hy1FCNY5h-~$R3pf@4KetAdm5@1>{}hs zp(wEHf?%sw8RtmEGB6m<0X4Hijh2Ba3km3*LeS-OFlh{NQ;>Qe(DDg%9gIuaFjYfW z+cPlefX+iOfLemsU@$qBfx*a02DFs)05fELw~-%&>ks8dfw>;g)!!C|P%}3uj$#}GgEe?l-VLw{PUZlp zRmLDE3qUmlfTHFGy9tOU$-$16P!m9b&cMLn`X02w@iBVkH>-XiXe&M>Ub1t4aAui z#ZWHnS^(4KG7JoQn`IanHW-3j?6Lr=0Jch7Iv%##_8E97*A+ts2FI09Mc_4IzB8db z&~i+ZcvvzHW$@gNBmo(74P{`^0Ua@M0V-n+vJ<>eNh^VYL03(dfk6SZG}}Oyfguva zu##n9m}JDjVCpK%z+mwLstvRO8KhGeBz+pB4y1v_7;1#AF&4F86C6NWRA8n+mSllW ztYeb`*$&#A0yYD*Sp~%mm`Xj+#+5Q-1_nLQ&XtYEAU8O1e*sYp44|d*pqmvyXHN(~ z#h^>&85rk8Co(YTo|k1{xN6M6V0c>=v=9qK&ewY?%fQfP!oZ;SQI>(h$drM>0km4) z1Zn~-9vDBf&IdUXv|N6nDFcI}EmQ$`xxAf#5Cel&A_IfzKUoF_2hfHOKd3w`=QDm` zeGW1JwDIGiDFcIL7*qjt#|IN=<)eExRPg62W(L+oa9lEh*RO)sn0YLR%1T0IiWwN( zu0zEjD_t2F+%=#Apw+F85QU6}43=I{VbJPUkXrC^Lq-Dz27OUb>B`a8@9xm`&?=Uk422X~sASnh0P?E7?4CQ2=Rsgw0!-_G4^)q9rvVb2bWI^ld z7#Q>uK|_HoVAEk55vGeLK}=@>o4y2Ox&XTA58!LUFiqD1omK$T(FbuB1LGXferDYs zMFxgXW(*9vGZYyZLd+Q$>_CeRyOJ0f^tLK8FtA!MFu2=5EfZeN%#fc14sNis9bKRj z;B~B1p&al!R+9xnP&55Ni$OuQ%(4JA2!$9J^j0b|Fub&2V6aSuYC&D>%Af<rgRYAj14Ax|5unDvu*{Ny!R~?(1A}QY1A`$*4a;Z7(D{ZS zMJ%5gL+0y(ROwkUF!&#XnhVM5feZ|mE1@D9tKmy-tr$Zfb1$GxA`A>Vpk198pvr!N zZUFEdNL2ru|1H&s2 zH`Zus|1w4iGr5fU0+2&CI}H*C5QmFd>zJ!St;b z1A{Z@0GD4-`3<1yZf>r6kiQnnF)$Q^7DvGuYvmXiR)W?$ft2a(lw)9Ebzor7J1WP( z(CNUyVD~_nf#EgCH`nAC81{oi1Vk7Zu7N}zgSM(TGB7wU{tod?%xY!^$MsMSXaT9^ zUMMFW5(`Y=Z2_SC%wQ=6m1=`Zfwq~sL_-CgAqFyJr7|$+eUf8fNCVlE0F?#XlMCg5 z>~XJ#azOStrh*eQcq0Nhrrq12vU655Gem-ALqJ82+Z?Dks7wGAL6%%lfo)(n1%iqQ z1~*@*AV?Kx$As@iXuyItLu>#W2b$9hg(fcs%LhpEp!KSCprRSH1Ns8gB#HaH z9SUfu0-6m9H8cQGA-bTmQs+1_FzCf=GBCVwWMBx)1Z^}63I>rZ0q!8m7<5w2U8rde zpgUn!Fa@sx%|u^dgIY)v zpaqOcfd~VG-d%YH1`lY^>wgboU|?JUUJD)Y1LRr;h8+-!fpN~-bV%W~0$edqa%NyK zODg~=2c41p5wsWxylnxZ9dw_JHTXoCRp29Gm?vH+ckhLod2KZ_14}wM#yFWDL!AT4 z7KT=gfsD3Rj6tB};yxFu9F#j{Aj+A_85rDmL&ZVHctgZFnLnZ%V+=Fq7*zSc)yxc; z5amqepgj^`ORgfxHiBir+k@O6LB%E4Ff+^ti-$6C*g<#DICA`i1dI6^W(Ecw&=ENT zP>~LZ1Owxoq6`KG-4zN940Bu<7>qV4Fff1u!KDGJ5LU^nXD~1XgOpjI%b0@nSWbkh zhwtFxhTh-Gz`)>c50wN3k$na@M8QE60~H5pN`Z)TGJiz`5lE5a45%WACqM@XEr5#D z!#&}i$-tnSsldSS8{`SlqGzxt1b#tOfbJs%vHpNO0a9jxE@KMP;{$3Tpvy2GXAcIo z4jd(*hJjiJAWxl#x&q`WkxYcA-ay4cp0b39b21lBhF8X5Psu|KgLn#jG6Ym)J;mgWgni28LwNN|p_vytF`_fuRi~yh@#cVGl_70Z14$QE=alfx)5&EW`lOCKjjAPBD(-pa~xi8DQ7bm zjLpeB0ov=ZmYNJYo(!yyleq&bY79Ev;R)0j0np^fD{jLRpkow*gRf{XFa$r)U|=u- zNf^BWi3XSih%zvk{sf6Oh%zu3v4P5~Fk{f^TAsflF5CbzCV(lp611-^Po9Cn7<2#y zifkKWa3{$0I*>Ua^+r=brU#lX&|qLNU9G{uU<^9Uq8e)A1CWVTjD}%gMZt+6t==HQ zK?7nXm{jo;1D!+(KHQJdHk*MVbROs!3v)3BePz&=>%-uM;IK=M!@+tO7#Qbd=D<4V z5EY`JRTaO85sJZiov0Xu7dHGC^#)H@q~pUOgTIh6*wXLi9z|xy%cH~C=~DH zp@w1@JQSBfRbhnUBT!h~1|J8?$e#}j#k-;m`kO-;7#M@VVF0`K9ubPx1+Y+rsJH-H z76uMQ2%pmn7K*`O6QH3OV)9vvfg#ij7K%|O0{=k1U|?_n#mh~oE8(FSZt^Fefx!Y@ zJc2Esf#E$=F*p>_RWROxgkrB3EEHkN;h`9T9Ew+=hJiv+sQ@(;bK#-L26Ze(C?o^KNbd@o*02YeJMH%!fK-PfJAmTF63T&x;%{T8LIws4bnytE0tNS4xo6M z3FW{;QQ5eoh=IWZT|8n^Ap^sDsA6y^qN`w>1PR4>A6O{Dl*2>u0CFf!g&GD5#dSrf zp*S5LipQX;FhVg6RF$s>AN|Pqum~248-*G4bM+V)7&n8LWj=uFMuZ}72`m&LD!M_- zl3D8j7str10Qot7$io3N9{zG##C|-U-?SY5l4()Tr3=9_N;t@BB z7#R4WI>Dicu7YucFt}Xw@P&mUOgTIhe;|kASE$XPQ2bJi8j73Xp{NG65+fAngZB4# zfe*-JR4;*rVvjI`erp5+1EVB33>=`k5uq4d3JXPu3ebW#TW}~s_-lP(p(q750UC@=Ko^gYD`8;Bhbjh#BDxC3Zb&E^`oTgG zrW_uM23$zBODNPZP$*iJqK4ubcqq0(Rbhl;AZP<|82B)9#@td^C`Je}=-)GDU|{?M zUg)s^sv8lCGs|G12vHFWTFwU!MF@YIA1oCAf=z&i;y-23sppUUU}=#> zjDbNPDhLim44sSk!J)_;01HK!a(F1tKn_Jws9~T`JXVexir?X(=mu4V5sKeHn_ct4 z2dgvwEQf_+p(ulXH)uh?V(|8`1gLIAD5_S%LJ^`uAGG`s9EuQrMgS}nmw-)xrp2X3 zlcg9KwgkXJakbF~sL>9fc&UVP;Gwv}=vf7*y@)0r@u8f7p&u#;4n+)|1(4bWbPOV- z?*dZ}55)t>p;!tv3>1nym8hY}b02&V4g-VdGN>wyP}Bq+XJ!aKzMj#(5*CWaq73@a zLFW&5fVZ6;fa*qsVs;fQ6d@`kK+77zp$Opz2EszI6Kn!B6uS&T=j6`~goWY+Ljgue zPuu|%FE^p~z(cXuP^SviUPKd*Fso!>cn=i>ha!efBSRp=f4M05#eH6fc%g4m=c%4T`Ek?L{>4 zh?*(}27jm^I218-UKRo8=zT%3P=qOmhoS~IQte_2H4GGrGpbQT(E=WdX;4)dp?Dm$ z5UL1#6$9hxYFH?ih%o5eg4U&60dE{@fa*qs;`bU@C_+@cf`=l6uN({u#j9WwprLq8 z4|L~4elRQ)@915C8tnjzmzhuwJQQ#0?Wh5@7tzEc4plQStcMDMLlHw~F(ee12g5=U zrW_uM5y+u96>1nL6z|obhN3q-6pukwVT7U`XzR2a_`(ZD!CF`-dWbOSFSKT0V3Y=j z!2_snL@3(U!9o$D;xcG4D>yAe_&g!7P?Q0i01ZW1J5h1WpRMsk|gl9J_o37L?|}b!$J|FVh?DMEI1S) z{IBp(Oa_|(4aF2)(ETNjp|DWQ(wzV`+5r?Vkx&ji6w`HG>Ot*AH1P<(ItGS(s315L zF?32oLa{Ow7K$+C@K8K}9Ezb(!$6^!RgW5qW$;jJgQ~&^#TlSQroX_Kw=mAFhlS!F zVFvx{p!+?ZgE!DEfa*qs;>iYBC_+>$1ueb>ha!Z3FBFy*Uw}=3hT=;d&@C}qVX#nq zuk!(Fv;!z!c0xJuP<*4))c|TQqKQXLsb^p~4;2K5B8JZ2kXCn67%UWF%Hg5-13478 zLJb3j;)Vv)Q0#?=;xni!j8HrYxld zg(5^nA845?I20lL<6*E+)Q6Y=X)hY+fbQZE4u^%JnN9%IXa`WdNJ2UAP&C%L&>RDAX`eD1K{14MhW(LQglSD)dm) zZvkyW=>)GTNPw!z12wS0(;0u8AQN*C`Et+_S@8LE5WY|ZY(-5sc-6tI2nGgc&_y&! zP=f?O;~5j#?ldtl=x&r|U@(n@p1cQBkN{E;%eK1-v{q1zfdT#Gy`xZrAcyu@)I+)O zH9vc$85s0VN;5F*jRd(Fd>rHgsGH-r9uQ2wd|AD4Uqs%|GFfiN)6 zv2A5wux$qQHR`|zlRk`uoru{0=1WCE{dWnf^#RC=CXNlwQ2)J&VqmaS5NBYhXl7tA zg*z@Knt_4AxJjCU;R94-0BB8HESm|4M)$KXE5tz%KQq`hh%+$EYhhq82VZeAoda~m z$%beK1{X^fh(c+Q*BKZX1VDM1B0U*R1s)i+cFT!2?7uIhk|A)8C(*e z_JL-CK_;1jjClka&zlD}hBF2<7hnL@3YuT{YXPS+kaow}P;v0`j-^lzXnBX@dngBF zv->Y7Cua>aLoY(Rn;6t`*lajB`9wk$KLCZ&77poFXz+s0j0KuR)A+nhXpZ z5*QftZfPX@1A|W+1B2dcO$G+@BnAeP4H66tdOtK77?y!}A3!|N z>9co|7#LhQ*&!(aw*JMmje)^PKnpY-yrbR+bRNDSR0X_P`oN*9BE!I-3R)*4A;Z8> z0%GvWFfi;*W?*nrfhq?@2Y9g*^TZvX8C%QGP(Oo?ABN6UGl0^%Bm;vBQoJ3c5VUXuv@pPc!ErTI5}c5CLOGy>3`+fHpxy^r;RDtQo=0_j z3RMTz^cl(lX>v4yS`N~|paVJp{Q*>j6XFO4#yOul7#MVUv>6ycH)!fgXfrU>fL7HQ zXfrT~r!g=DsAz-2I6#Vlp{Ij^!N?JGZ{tJ;lL)(z_649Vj3(-Ge+`AgG|+t zXJFWr#=xMfD9^xf6~qveXJGh|#=u~?8LDL=I9-Q87tw&$%QzfYWMH@fm4?kEGp6ta zcQ7y*-T+BfRSY!IJ_W>&2g$dA)t>_KyFiPs+QITR znG6ip)}Z<84zPYuez5)u>S%X@<(VgLsJ8}fE@$k5^0(BRi-WHA>jv|SK>DwO_UrY4 z&1ahEV0{b3?S*nZtnGXm7#RDY+yF@Pr(X;dd`~iA&7aj`9?*pB3`)kUpqvR(3=E9( z#Qt_MFz8BmGB6l`)t;&IE`f z!BP){`xU4-$698F2T*ZPt`A`X9R?Z5$qYN`60{r#lmsF`%djlFxFB%@Z!JlHYD`E1 za{x_?TR;`S4}hH_=HAV~0PfPy0l616HGU$Cfx*%psuFJ03|M=L0c=tNR36qIVVo&8 z5oXeBm`R{}u`KhUD&ZzQfScs*0`(`T0e7++?0@()`$lLp7*yg-bO3o2l*%KRCI&FL z*Fg1wYM+;2eIcO4V8s{+N&^h;GobR{LHED*fXoeHp17r+!TmE-0@N0i1WN=m8Z$VG zL){2k3+)ICB+#+@jxc|MIsH&&=4+W5+?PQ)PHUMNQo%-n*Em@ohKj@edmQQ!1_u4$ zWCjMtW8ma<0jg{!BzhPa=Y;mbvJ*swRW>X;LHNblu|fK%vq(7KiD zpnI54ih&NBW}28_4$eKNz^UIkhk?Nwocd3LwKGl3fZR@Y1}qI(=y6@l0dzx*JU1jE z@xvTOpnJtOYNtV44VOUO0g6!-2{0!pyuX zwgzNoj~WBR#vBF)M+>OdnzhUfkVF;%6#-rS4bsJUQS5Io1A`%GB?lL?12l2Z*9E28 zt2qn|mg!Ip@UVxeVPJ6o4-IfoTGQ+Ur3uhtL&%Yl*P&4aPHPzq?$S_mK&7lZSZxT? z!~{?pEM{PEggFb8CK&V=rZO-vegX$+0aP3GuoVWzIj1MU;uNCd2WXuPxW@(I8|K2| z^fTCmS`Z%`r(eKvx)sC+7m;7ZM8p{wg!32}tif^o4I0NDkU0Jhjzn{iG`Ix(0p>GJ zbb%CrKf&BakTf_6`~us?G%)~D0RDz@BOoc_4_GxMMev9_fNm6e2K5@~;AC)AaEe#; zGcf3is6!m3%c0J|a6gZM!SWMSzZl zN*mOZaDN1q1r=8tV6rX@jxg_o5}G5-2OtiEeg`O@*Mett5}+=f2#ObQqr7esEY(3& zOwWgvBoO|Ed|0Zh1DhaIz`$S*PIdL*sUgq>dyrJtAk)hL%Gl77q)`S`PcJBdl_X7I z?eLPM87vJ+H+?b=pqs4LKph9)0?{qAXd(lH?khD0hF1j)431l&3f6#gJ*c37iNMoI zkIYq&7En6jD`a4B+zZu$NGA`VBJkMlkWrfik6i;k$d~~nb`1*|7%bmIHNayRriOvR zy$>n}irt_|sIkla05X39D=1b#)q>)+0IW6yybKn+*47c`EO3;mq=O2I>yS&jc1(g@ z3v?ZPHyr4auj}Az;Xvni-{1#bJP!%SJNyj#8>1N*7Jx%Wdzklw+GxUHX^CQ3+7JQT1}|bnpdjzf(!zc7#E6R!%F9%s^HOY0gYD%2FqDcxijGM3v_D>xLL6W)ENYqV=(*R6`6`` z;S>gjU{LyWTmjXMls2Iva2H9-?uNPO56Ekv+DoGZlqjJp;jU_cCR}J4#=!@Pba*OP zkmZ>QPvs||iXo}o8)Tq3R0G^Vm>LEKcXy~|pj2)*6*ZMBLqh|c%Hd^LGE@gBt;c|M zgfKxGX2lE)?kz~NRWMmlw**weIX-~Of^vc*%pV{QgUU`$1_s9akZf^&DlA*vhi8lX zkZh63!1#b4bkis#%{<{}&{qYmveX8rD+Q=e5!qtR3|O{+sK^7YegS6-2!DAAEL-S+ zP52DrgR_M$I9q6!!m@=PI9tpqg=GtUXu5}H3j=UUegTpOXA489G&EZnfu)VgVA;YL zY#TgVm_WJEY+(vk4e_F@tTQNYuYh{(11MWK$wp3RV9?z(m4RVG83TjvvZ)LVTgn(1 zOhB?md!~Z2#s2z;V^bLz_CfW5v&9W42c8HY@!Nn50%eO=Ww31V4yp>CURLl!N=^m_ z%Uw{p*QnWIKRjE&?1N_uXW5SFuxxPxsvD6l82BNH8SWx0*-J1NiIqdEcBgU%21|aZ zO1P^8;I0DuO#>$rjIMz_P`2c(!;B z&lWF`vc(&I2K^e)bd(o3T{%E~ipUm+XT!1uL`6qAtZ4w@A1sGu3vaLqycMvffe$!a zxPtiL2B|MNTWqU9e{f1>s$^iW24{-^s5CTN1cIe~K+@m_X%N^pc(w?J za-rEG1gsj8EmCBiL3#TG)N2Z$J>!Y81+y3!bWhG;U|3bjz@WQ#1_Q&nO3=`#Gy{Xt zJSKWZ7 zbZBki0hNbmi#S=U+3;+k0QCYSThxOL42Nof8wgXwz~J5vbtouXp;#HN04M!z+~as0u~6MoZtxa2Z+O<(g~WQcn`@Ie`mw8#d~^91_t0#S2TsW{HLz@v50!>yivqB88b}(PEegT5!LvmXlnc!k#bDKt zY|$+149eRtpk50AWs3&c_PGoUx({YEFdV62V9>oXn}Oj)4FiJ-NY?1pY-qOlGMjx6OrR z3l6Bsh-~2i6@j~`LY843bl}RX7Md-}Y8eCkME0hNbmi(1*hdGKuU z0je01EoOoYEQe}<8wgXwz~H_d>P=9#Xr70fE$Xmli^EVIplq=atOGe)JVBD(50iyw z3s@k4a)KkwA0Q5ciZtkiK4r)S!Xoow*+N<1vpDFsV+KZL@CCx4zJ-baqHm!gz@Xm_ zI{DTXeD+HN)TfAS@o^z6TR>FosfE=R5dPO%ShlbOo8VXn%NF)>Age1ud~mjKkOTEC zUe&>}g`*rw-@-`_)VDCJhh+o40_0LHGMS28Mg}3=F!j=P@uaHGoc*0@W6b^Fi5SPkjXUd{?u>wwC`vqhj>#zJ_u z*a1}x$rhVI22O`+fEx%?!@%Hv9GZ|o*<#j0)NC;e8XBm5i^otMplq=NtOGe)ut4ns z$zFrW!m|Y|5I{M>5#|pNhe1UNv^qHmk}b3s!LmgV_#${vmpcfOEixGxg9Q+Mi*Nx3 z{acBkGn>H&KrMjUj>r}qOJUhU46NcBXmtv>wt(>Y8)4Z(94sFV;)DAZ67s?l3=ES& zd~mjqlm}%CmL^!XkdjBq7Si&dY!LvG1~;N)pwiH6Aq$qC1(F753pucD@N6Lun(w1i#pJX1yGlJRWk#FWj|CU+*J&4SAqSu0xA#B z7FzQ4OW@fe0IC?0EzW`r+z!2xs-t+ z4yq5FbSj`6cp{7ySPwD?lr3`FVA-M%stTT75`^K|A_^+khMFx9wFS&Rc((AB|Gf;B zEi$0G5!qq^R0QrK7kSs^&}=aS97NTJ3b0BGOwh#l`2G17aP%boENPtyCvW13% zGbnFoh(fXj_~=Ykg~uxy7<6q`FfizJGBD^GtYBaW=mc$amtkNqa$5n)7W?ZX0#-0E z)Is%uv&9T32c8If1df3W0%eP~PFS{B2UUegFCy@4Q3aJ-ikdADwFS&Rc(zbe5LyMx z79CLCh-`5HDgt+rtU}BxXtvk{@){^x-0Nguusjb{33pWoG^Inc#S5rBJX9gR{j$kUThB7=W`y zP&X`F7(&xMw6-t;r{sAcX>hhMhDt-Tg$Y>t1xOm4Elk0-!Lx-Klnc!k=3v#3Y~ilp z49eRbP_H?FPLy>~__civ>NfY;g{%3Z7mR9zy1$K=T$*xg)6A0#RGQ?1N_uR|UF4>p(h? zvqcM%tPxBW-iU$)0w^aq!u$c^FzEjS9nIMcKK@q$>fS>5vdB}LV2K-|!mbyVxFP)V zURdI80h@3b#0Qu8t%~N#3=GPBu-S(;MNpYP6~qTGjcQi}EuE6?XJ9Z-0WFp40P`32 z!xDd|BB)Map17bMGHchR2&xpsCct{u-B9@z^^j@19lH0FfbJhwV_*mZ%^n|6V_+x;F}A5OFsubJR;e*CJe~-;78R=ZF*xUdRxE(l z?}64^1%j4UK~^smK^4KOA;ztWg&+$+vwo737#J)op$b4b2z(d_v{Kjr6^E~-nWMNH zq!E;}Z6`td=$RlB_Cr;|lRbwh%r0?jO2%0pGcP0Bzu z$pR`5H))xoKg^`tFq7CPgVupTRl-d=fND|#R35$#Y_;M{m`Pfbp}q?PnUoJz2{%bV z4CXucrO>DZW$No2L0J@$sUJYg2XLlFSqEmt7|Ap-g2DX-R5vJte+BCf0i}P)l1O)6 zXsCi@g*SoB4q=|Sq8=)%2bBe_Br}G|Zm0)O`Oc_kaQ8=&4~EI_fXL6OXKgr97b*>{ zljnh@ZKuK}P3MDcgV)Impj>FNun?>oQYUXzat4*X8d8wrpa4`SuT$#V!oZ;Wd=mr1 z?5PY4y0_N~wo zKyey0xA{&34QxPF!d*21o_N51TLG1a*U77uBDTWo$8oEQZLww$RW3XJ7bQ0%oXbpiH>}tOL1DRzZ?I29t%?$*@2GWfDi2KR_G? zl}6BM!j_P1A-xTjEi45$L$ie?JX=^HWeYn&2K^(T4Hqrovjz@8ZHIb_fpN}{9k6Tx zQ89TMEL%YMXQsijMJw0@iRrLm@HSG`OkS1GWvGEqb9`XtwABtA=EYdCJb9yzLkpb0>$QBErB5)T?R+ifV%@)=( zq1hsRCTL&-suJ!h1#ws<1oqnrs60Gd%uvqX0nZj0P{ojJ(FHQ_I#dJPK$sfPzy?$f zlr0wSK+P7}(9l557Rpc^plq=ptOGe)xFE^ig~`IR1uPIiIl&R;4-khzg)M`Dfzc6? zEsS=;vV|i&TR1|pMKJ@T6Ic->&A16N=qqS3Ffc9#r>h50+Y#A; zSh^4-4bB#;!M4G(#TqCVnl09XRYSbESJ@eqw=-lQ+2R5yTkKNavx|X2S9B)>!}-|^ z47!{<85lm#W?(P@$r{P;gk}rPoeT_hP<`N}GXu(jCqfs&`5=Qp*+O#;EL*IDs)DDN zjnIU^z+hPgl?%j`EnxP+v&C-Z*SlcZq64ZMku45DMc^*lq-?qynk_=-K(j^T9MHf9 zR3+S18=xs2+J=1rm4|1G?aFPt;o0H`R52u5ECCt#9jXCtAWRKtU;`=#$`(6zqh^aa z(9l557S2!|plopstOGe)q#((Dfyu(N1uPIiIl&R;4-khzWpW~>)0hKGjl`UZQ!LtRwD$_n#wpanxjmQ=cpdxS=v8np(gJz5T`Os`Jdp>Aj1F914 zDhCOa+JZw468G?I!L2%XA3R%3fGUP$i+vyi#i1JD2Ex>U1~#CUfwINXL0s)j09AW+daTrt%fHtX|hGdI`{jh9tT2NRL zG7Nqio-NKGWs3`f4En)_3=E7S;5+#gpgu)p3)@4mYyna6a6T+sK=@J%VA(L46Se zg9%91$QL9$aZi0j_IO8WLu(5Ss60GdXsB&D2+tN8P%l8T#SM^w=1>iA17T`F3o@Y&1!aq82SLdg(YIiH z1R4EA%@)m29iVK%atNdYIa@42l2wAq!fOjyAb@g$Bg`Kl4ueXn9|Hp;vk)k4F;*Ue zWea8@V`#Qu76PS8#mAsU$t*&MPB(`TgMJ=pFzXRGWjR3oipUsYM`0NQqT=@=SjK?x zO%}s4#$&Jv4In-^V?0p*5S$+F#F&c8Rs-@HD61|8c@2~?&MyTm z$%LweyJ`Y7twS@02UH%OF}|stJp#`d51@)68RHYkz;LJrxPdS=pe315hk`Q3|0AGe zj>s4iSTn|As18uZkUa|0ft)dpAjw+8WZ@YD76_oc;0W^vh{K?A8Z=9O9g;C79ff6# z>q3%>kow{}BxB@*rjc$4A(q+P5n|Ba4H`mz48DCi0qR#xNX#-Y&dEOk%NP(9qRU_z z1H$)P2Fn;vz$VNB@xdA6sV2zsOCUZtV?5IY>{UB*@#&`*p zhGvXcVCkD6X>i7P4Ym!QG2TGA(2VgGtQwLr{%Sgda`zjk*Cv26#xG5o;|vVC#YY(! z%vUfl=%ycKV2EA;+8GSW7_~=18DoEaMCVZk1|~&FSb#Hz29yI&hPQ16s#0{?_z94$BzgP)S6_2!M*fUGzzFD#&Y~ zjIkHwHBiQQxq^YgG9IcD?y3ZMb_M&b0xA#B7~eIY9fxO(4N%39jKQ;#fx)sJssU~w zObuvJCe)jtj3IFXl*|zs!w74}I1JSR${1!~9mpBu36g9GOctIoV1WS23yv^QzLA(S911FCZ%PSHkiIgrBhzmM;RqCTs!m!TBNxe3H)_5FeZ`f^|SHh+PHC7a=+* z`65&Yh&>gGxj5ML1acGe{boFCxIU!Sh8Vlnc!lQDD`Oe37N&49eURN|5w@ z0F*D%bxck%FzEK2U|r26o5@Q2jYYCMIks}u&ss7UKD}zMfqA-z9@z!d}u?e z1e}x)gQUUvq7*6(%@<{0Y2J0P*^6?pZSZ_i0p&vTMI~4@BwuvuID;~`g)$^xd;s~j zO~>af1B338(+mvR>p-_gon~O@UkBRq1&eIGGeo%ejw37klz*FG^VSSK6 zpnP$39V}neK~=#MjDsvZUwA>~6gS|Qy@1&V&ll}F)n{S(A_A%#kuN4dMc^)~(>V%r z(MOQiK>0#;Jp+T~e5gvet195R73{YiP-hJkFu!ivXxmsQE$|Y8oizr-5}K=L-`g*%p{AJYT>90hARSVg3Md7*wRK z7#J9zLh{9}bFh5z6rL}hLh?m^0R!VRq1=UADk~Hfb)gI23WqB2+kK1H^B16Bxu5i=8MVTqMWz7du$rh#pP=Zoo3E;L`v0IP=NixoP~pv)bi0?8K&pdD^Yby6-cFzD_% z$H34EnsMH6j)7t2M$n!wIR*x!W9Ojx;^H|5hCHY~aN6mBa^R`(g|IuwAW*)zzY&%% z=0R1#6HI^{JYQr%fD97h4B$tOGe;gdoYzfyu)21uPIiS-}zJ4-khzqqd1K=$Gg+FfeY@2VI~00ctxUTb#QB%N7t7 zwV*XS;8pw({AS3=Ed{p(^37Du8EJu-|?_<>A@ll)mc~c(ynIRSd}%B_IRY)gU1XHxQ0u?h*mXE6Rh0rgkG`kwYR3Y#dTo85nrmL>L(K8`MGl z6>xGBfCfMcBpetR=ak)m<(8{p71KdWMZmcQ!oRQ;mRqiYFp2Q3`|pEGd>EIkDz4eoW^1zX27(F1an z%{?eL0CJSgeXwdsc6qPw49fE>p?+%sWtTVlO4mVKz^*Vbd;(2k-@n4ZV7&vh7YvkL zzFq+xeiC8y{|YGEY^jgnzRJLG7pfnUw4fY#B0V57^%}HS-m(K$oA9Ya!V#XH9>B8= z*c1z>JlvFhBDZ0tY=E2M2UP_(r4^do7#Q3SLfz51ftlg|HE^nA0 z3d*K(H^2b`KUypk8gAg)3!K$Jhl-U$b%3&|Em#NiP%-dfF79)Xq@y9yOz`6`9ATjV z%9@TakAOG~DxfP183jZ^bpYdr8?byTAPPFN9aMb@h=OVlQ1vA!im1LsMH%$PB^ekP z1;EGUH$c6LsJ?n{!!jg9h2KtCs}90%-wDf*f?yLagZSW9osc2OYVKXIR-LdR$N@FG zU>Q=x5T*JOH3T{R1V|cOeThM(q1Bf-SXyW|too7w+XipdNkX~M>Prf&8j>Hi44pxx z3Wo+Ha~uHWM|DH1TMP`klW#CE6oBTc+ix&1OxX<@1pwv8xi>)ha8G^2@*4~ca!`Gc z90cXSQ=zb^EXW{GDm=9tmJj`)s^AG`g95zzl7h;e1?LWUzCeru!0dzP3k}1}Td;hg z0o9Gj7YR@ixQpZsH^W@?4dgXYzEIi&+M5Yg33rtM+*M$|bwK6e)t8DP%WZhRNPsGa ze_rpnMSm)`47ou|Vwt$riz6 z!FdXFR2nQ0Ksmt?<_{2uK_xDUfq_v1k}WRZhGh#0c(#y$WQ$}5MoCdb^(BRrMHEFD z^hH4f$ok+kwgBo9M4hE|AC^TRDl$L|R={-@guiePEQ=U`O?V69gR_VsIE$$6g=G;V za2A=i7nVhg!Fd2tXPJPLwe&t%on;D^hu2wVVChtlG`P+(2U`cPvn-%oXq{yVRt?D` zo`%k#jBW`X75@OrBW{M*?lLgwuDZ>@a2PakJ?}OH1J8a21{091(U#kgI&0r;P+r+m zA93n714AxUKO{XtIf#@g>IpIslvhIc!}7{Js493OYEXpb6|gBMpz?52WJUX7rZmD$ zxd&ARHwCHA3WUmi-@weU`7SsqV$@lgP&Hy3L1Pd&>nxc6;Ca*Cko6ucZ?F{|zL2K=``=LFqoBJ6UEcZiI!rlJ>n%$sNIv=3&@H)%caKSxz-ZX#)86MVAsX`tNq5v&8H&eB1W=D!az z5Gm_A!a@O*A01&H0dW{q=70{%NP=X}l>4yEnFPKS22^JyK{97517k8$hD;Y_(4XeR zz`&Sk1nP1LXh8~gM4jdM2$mrsD&!Bq>MRI9;s7i|W`Rvu0^)-+WVR9Lo|rozKDZH? zV+5+Rd=J7hWUdiPot0+!A7|IS9&ur@~axiy(tQ`Ed3jSU$W5RRvEl9!ju$Xt@e1X9~_8Sn4d8eeitI zXe9d(mM?ZdbtCe{2dD_#MHNQr525+uILK?DI_vWx1_sOjP?d03ZGa|qXugophQvL* z&Z;%q@erOb7@%H&bMWbgm-j3wWJX4AlY3 z7aWg3I*<~+`xGQuHJB_oPl4(zSRjCMf+NfyAP$2HXBYzmV+ABz)I5S^iwf|SQ=n{7 z0nHXA42+dX`Jz^oK|h{{fr0TTIAIw;y^6>ekxyay0;0n5Ff3m{_(g|d`QjMZgnb}B zIA0tG-$L~b#0Te#6X1N2egu{;PC^qtbcF5{I4N%iNrUsnX{a{wQb( z04QH%Jc8zn;ztY&=b-w)`QinX15bt3qAwpqQ{mR5uzc|kstTT9Je1-2;uKUa1XsR* z*$2-Tca2P+!1BcnsBT2Q5YU07X1I&47}bNk2Fj@qL0$vp3*KW443_dxm2g*0fF^Zl zzOaDG!}G;WqYF>q`QigqF(h9YfDCkpYJeLEQ^UaEUJi9AC|@u=1*LLCzQ}+Y1@7a( z^TlMS4p6>O0P8@`7h8~I?O?L-d;tptP*!k+`2)mZP!S7cU|=*C@&%nfI`t_mUziJ7 zLYDxTgO>m(C+8G1Fj^q3jd2rV(4PUi<7tmNsKN+<`t=ATv_aRszkp>7h>GB2u#5rW z_Z)*|jJ;qJZh-jUjImE0WVyg`SPO2yI;g^EJPykk2h>q2jDzZ+3gawD8k{i>L8YM? z<1koS`~<9%a|CP~JYyV%a-kXHm^vsVAQ|JDx-%$uKY@B}0w`l#R+oRyz@S_9l!2l2 z1OtO^)>8(CnI}L?06-a|;VCp@^gLx?*ry8#3vk9*0p%#bI>=T+Odx|m$?*IMSjK>9 zfu|S?6?n$@26aXqwu}+0#=xKfwF15b;EKB6a|VXsJT(ReM_s7oijA-nnr}cw;2Gnz z`V5$heuKOQ${6Y=85k^|Lsi0EH36Po!G2@VgLo9)kh-A$@;N+Xynrf(WDGBmf&5Sn za06j#7#Q5AL%j*g7}76LGe!Z_DAbIx9jXJAF)YD4kTb>|B-wD7EIebt0s)j49AW+d zaTxSHvOq)E;M0{Apzd7=i@Z4suV9ILBUnZ5Nm$~B@Ryy0CGJgN`A;A|IB{k|}njTjgh4Z)I-U^L@pVB>HD z4Vp4XPR(g$+;+{6-aH-j3JsD3p5) zS&TU!az`ahCp`LK@(c`?dQgMTffH5;IAK9HxMe_P;N^*o_zRE=K_%R&GtiFNw=)b3 zmgP{DaPv1nlMv{rcW5Mll849}P?RI;MHZ+t!P!57!Ce|^4k&pTfz?8j2k6{dcMqs6 z=)M3?uxubG*}$9$Nj9@97#JACz$t^V^9?N7gdruHaIhpK*~IWN=s#y-U|_Tb$JYX= z4TuEO@eYgQ z%>a_TzzHS*%7G`CNZy@qaU~d-PI!bOC76FuH@w40FcwfN;0eYFk38sLTL4OBmV`C;bz6zi=AQH^Z53mFSQ6YXF zmS7U1_d^}1puoVuST4@+fq_AH<~s(4MHfJm^9l?MMoZs863oVT3=C$5NC`#(%7G`C zT;5mjaU~d-PI!bOC75$iH*jnM4I3esFdR@T;0dNg+zaGFPziGkH0us(v3$A!I)wtN z5^jC~BEigunh#1aWgk!zj2l{l*$q_-N-$HvYLOGn3#cq8!K?<$f)fnPnUDmN4eEE+ zfzt%z>kqI5Q-_pb>cNtb1k=LHp#K)MAY>{yz8s+bKqQz~pI`|FqCyn3Km=SRLHK?b zVF_j$*o0XiJ~+Wl2Pc?I7hwrz25N$t2~IF_mtYBI7Fatx!OR9Hn2bxX1hY)s8I*SW zp$-fHC78wHhMyQ1bWeR?U|4VoG;Iw^FxNgn63n9y3=C{WND1ZxR4Y8eH1bM+!j)iP zI^hwDlwjsT4N}5LFgKtw@C36!yc*;}P=Yyf30lH@xCCnALRG@eUw}w3`A|7ff?56v zHNhmJC75ogT2O*H3Ra7pV0J)dK?&v#SQeaMV9taj7)8+0Ad|ppf>Gu(EWu1dN-&eb zl8^*5gO@=cbQCk=L2!HtKtmXjV5Gmo5)4Fz@MTyD1L6Bzh9#IoU=wD5_}~O{7@S}( zT!tl>Bd7`HC^*5$fF@nRE!AUS?eGM19Gqa%uD}w^C2?m^+Kq=gumO}{&Wk5~0kv8` zF)+-#0-Cr5B^dV4kOU+AnStRNR5iFtIsoOs6Uq%)2WL43^uWD&gjTKqMG@s2nK4-1vf;V5-m(Of*z2D8YOK zt3^&Q9Z*?Ng5muNG6|euV9taj7=BReb{RNLF#3LlC75MM31&H15|Uun@G|JfgHCsO z3y!Y`P#X{l#^*aM!9Y|9UWFwX2;b`}EWx}3n=lQ;2Pc^K-~@B-DlEZ#Kus_o!3jp{ z8mxr*1lA5uFrUE*CgmC|!Tb|<2BlqnV@R2?0hC~Vi%jK@AGSNH7&p8F+&EA$}L+LQsM^ z09pnBs*>JZ11)=ps)U>GpayTl@I&Q535N4KD4G$C-YIAaMjNUYlwj1sYLOF61XLE3 zVC=xM-~ z^n=oi3TQ?elwK_5p{n3%1(6oP2|ENT4>x5WZ`e<1@%*aRMEC;`&rdjQo9PyCt^w_u(L0L_O+y67Gfx@Wc=HkOR~v_z04|gvu{yS71KOq|+di{Glr0COv?g=)QK z@Hx13&_DtA9V5W=;|UDz7EpJAGE*^F2Xvz?qXC0^5>yg&zR*ONWB}Lz7Y6smP+3q; zTLG2@=d=S*Vep|sumA#OJ4cuwK^z7ZT~`JM#w*~g#mM&?mItpO<-sdRdGIP&AtVnz z;APO40~O%G;8fcH4G=^g4EYDkgAf((Zo%>(gs*-ZmIp(?CKQAC;5--#&V!dgd~hBN z17{+aJFq+$j+zG}z?o|M9atWW1oK7j!t!7gSUWrqMuVl{c`ycSIXn->Lb=d97zfUS z-gjYnuu$Rx)T7RzGREEvl8+QX_j~3_i2P+>(0%)hfnhpm8vEfd28LaCK|K~l1_q;_ zzaV*#^*1yR-h}Ff=bk&fy?>yIjTtoa3`(|^@1d&T2@Nd|s+dDu05|0Z@2x-ZJjnJ0 zWkkgasu*q4;n!wL3vR0AIP{6rit)8SPYc~M-YcW5$4KggB2s9;iLl1 zg5Ux>fx&$?R4pi-C4ki;H=IsDWkE@)94re?N-$?a63hub1_nkZK2Ym`aXSMeg913V znfO307f6C(21`N`3B{xK8KLSJxvDL?}ikzlql!4k|)u!_?UU=nqDM*#VV-CzvA=?o7}GQ}qbiHCy(Gfx+@PR3+Se1r1n1 z=k5=c10|SZCe#GufRdvuy%NYc@0i5GEZO$=9`2wDDB!?LduK- zpak<-LXU-!LH9TlBSXj&(0l|a!CYp7B$)e5j11eLs=*0n0h9wzFp_){EVvR3OeZ`- zkrIp_%%IKaqlFz%8F+&EAW;EwA*g1o0IdK3l`u=5Ffdq7hpL2|Ux7$4@=!TYf?2|X znqcD45{xxeEhxbp0;@$%Fd0x;P=dJ$mIWsmm@^>>#tL*@q!u_$FiNsALQ5Deqy(c4 zmV_i213m_QfdmEyMnOqXckBSv21J69V233bh>GJ+U?mKM&+rtMV1&RX_<;D}1S2d7 zD%4t@!V-*#B&7QVt&&6~LEWzlAZc)d5d&+7Cm3-_P#G!p43=P&B%MKNm){DKU_O8n zjJ#w#8zY164;Ds-pl6`@2vCAyVTB|Z0aiwaHmGWFf+>J<;0Z>T&yx*Tf`RFTM<`N) zk%Jm!fRSJ#pfd0TBO|#I;(RSc0(wo8a{vwpGL)oM4)s!xD@G zYJzbDCzx{}X>fvZ0&9mS7-w*Tk$3@1Fn*HGptO4*>Oci01_nlN$q5{c47!@^j0^!U zK&^Wv1_mQjc1VJ8WM^auvxX!uaDs7wa^MNZk*|saSAv1*ghwb+f_VpZgBM1E(STY3 zPcR;mw?HlgC76;I&;+yK1p|YnIaDRwd<9K-m9!sfJ}AMkae|^5(T16ZmSAo})q)a? z3Ro?2g5iML0!lE}U|Dd2fjJYBV5XITX2`*5g0Y$tmSBRA5=<~y5|Usd_!#u%L1(RH zg5xUzY6Bv{RB^)+3`E6Y(5fGBl?37cc>zl>Szr@9UcwSgHaNjFyo4o~9MlAp3r;X+ zK+@m@lLyugPcZr51S9qemSCzRok3}LJ=B2#pafGcd60{dK{tw%k-_g3Xg&gzVA41t z38sjXkwMJ{DZvOpIq(D%%D0*eSAv1*ghwb+g4qXkLkdQM`2m%ICzujRZf{k!3raAdV715z<_1(2lwfkevfu;* zb0#FgTn3#CmIh7}jBB`I2__9G!K8yFAqggjk3s)13j+h=RB(J5K>dM8FspfC2?nC# zAZR5GIKe>pzh1!-%rvkGZm(epW;!^*)V+o!m>H-EW+phnoB~OM6U;2Ic6frB4Nfqi zB?S|JG!wXF?1#h4UX3iT12FvSEm2mSv zAQDVGR1TD2GI&uF%mt`2STCs>suq-B+Q4d&6U+*zEGWUu1Et66TVmGbru) zLmjvQlwi(FD)Tck=MCrH8{a+fO6mork0PFA6J5b z>4ZlpQi6$t8Z-kV!OVclz!S_F$y|^NK~+*7XmJIo(L3uM1B2yqs7ko`1zPYbNgpZ) zN-#6|Q4@>@R2eM6cth2K63lk6TI2*%0hI+Mm~&uRaDst36Ov$Dl|d&UfYSsc=)zV= zx4I80!SsVAAqi#*AA|m1&>^00!SQtgY6Bv{@Cv~a3`E6V(4ri0f`RbAy@Mr~cVH8o z-op~idvJoOdJju5A5as_M{t5U29gFRm``Bs@C5T2oL~e#z!JnmA8YW=m($# z^II}RkdZ<6Ek7fJ#|O|X0Vu)z-h!KrRF&m>kgJ3Q&TX@d31I3#t-s{tIYIU|?|n4~+y+f;lCK znqW>qmBA8>G}IhWf_VW}i=1FQpt7I@^B*h=PB1WMLK4hYMg|7PMc_2SXfFgSVHP1J zn8jd8NP=0x$Dkh-#K6EPDFx~!Er8m9NHBIHuml59u^Y4~2b^Fa{4XD12}TNRg2P8x zf{~U2^^z(+!V-*(6iP2iRtnThIs%dgCm1=fc6fr3mjd;Y_&&iBjFyx$DDA$7I#2+V zE!3r2g&7%iL6=LqeF9z4q|Crzq$LDNFlIuG3~Ba|Tl^Y=9C^j~F=d=*p0i&2CETM7mgX$H?lmaJM5|Yrk_z?+B zfS*C%3v|}teQ-P*K;z&NJfW#d!x9=qg#c(_54e2~$CQ@ZjG=a6&u# z9hT4@p(eD);Djdm1GbRo30ONkp*;mBwB#SKg!Vzo8IR34s|UP)~QIT)0de*S=Vyi|XJ4zPi$gxfL!k(T12 za-g*IPzs!!;c4j&T3V`xss*K`|6sMqX=xQy7L=C6r9mbIf)=?u!rTc-Og}+gbrW!U zVGNgsB_0Vha~MpScQvakeX4p!mu6IN0|_>Dhd z3CIF0e-^|CCm>5{Q2J&41xr9ypu6E1m?lC;kFBLa?V>=CG`OU)0c(dRAX{lrc9{JO zmVi8@ok3|i9_qjgpakS9Jy(X2LDx*0k>T<$&A@9@HQ^Xu3fxhFJlXfu|TJ>E|F9f>O+P&{`Hy>7)D`v~&=v z5^nwrM2fM8%7Idhuq-H^5!D>WQ^=YEa7s;JaF2$n1*I56uv+94(*czQr5F#eEV!D3 zITMm#RthmNFnWN~1S9Cm07%cv11Z6Hf+Zmd#*d#tp92*5$>8{U0JQ;;VA|zj2?nCV z?KdpJK=}2)VF@M$Y{F>}ADm!P!3l=>4=ll?p(dDgaDwp%NrMwi23R{h!DNCH%*;Qq z1XCjI3`)ED&X5$%pu)hwSSWo?j*&q(U6zsI;vdl62PzB4ZlpQi8FA8We$%U@D+8@C1`5Eg=sb+xZGwj{-_Cihn^%2catA z<|pXFt1Et}94NuK$fG703#c+!g3*Sm1tpjyuv+8<69JV4C74REEI7fyoC!%VJ3vPS z#DLQT<4$>42@`{qU}C|NkOY&&&!E2*v`VfY9A6utHXstr4nEWu1dO)!(e3C0&B4Nfpqz}n#nW-2(rO#cTg4rO?$WZ12NnYRtlK|zw6HGk+O9fmB z2Bs4pp-2ga4{A^mMuPEx%D@xMENM?gXoC6t4_YV5|7T#Z42P!K?zyf)fnPnUDl?R+NE(u>hPV7+)#E z5=;S7f+++`LJ~|FKZCv~=njkB;P`5Q+JHzfFO^{l2BO00KPRUj9Fs-%yg#WtW4MwXG0!BQQn5^jEk9=wEE4>cc@V3sMP zCKwB-GFXB+4OI(DFh{^@krT`hs4OVK+y={n6Aa9mkOcD+bZ}M+I8880tH2UW3sQn< z1xrHWp^KkEpB>bOc@B=R0H{9@2}Vi{mS7+%92glHY{89S2)~k%k-;2%vHc6M2}ePE zaDsUWPB5TVFxJ=O7#J8|p(dEu-~{8z1WPb)z}n#n<}EnEOlE>5m><&4ptL(5>c9!0 z1oKroQI(NF_m?sw!)Yc)1{08=5t|Am!3e1^GKje$B^U-M2cBTs`Mp(fB^a1ac!VM) zm~~J$EW=1JFQ78;1oKIHHOPga1oIxW*annfq?s8REWbll!p+|RO$iJP?)6YPP=dLx zike`Q(AqGwp=v=1<||k&a)LPll?5dj9yO3j-~W*{Y) znP5psf|MgpuIo?s+pK#kssEU*NlBI687yZKND9sng6 zMVU$Jj10QkYK#mgSr{2iK!Qf*YLEoutj5Uj45}JjC0&4W;0b0ne~mh>1Ow9vk5Hrp zQwKHZ07in@0hNI#7&)0cAQyrX%v;c68&HA)tz)q~4pj*^pFh~u-Jw0mDypj8w{4u2l2tN8v>5qFfh2Ehnf#c7EiTNlSK*C znXu-=Yp7aKvS8K$sf8vB&{&7NDAX2E=9UJ_LdHN~?gXb7xm-|nu!*11TL+d_Hi6R$ zB!z6_XV4D` zj11OXj0`3q!3ZZEMuwA6Rp6BH0Lp=%EVG3_2BZxXP4Qfe43IM&zCl&Nqi7p6>=_sw zk3!{Gw=#pqzd*-?z-)k*CS@}7br~5f(8ZBU69G?1`h=GzMKT|B;YpIg08-vVN|SCb zMg~iHs0O&3U}_i`+~+~v1WGnCdZ46$C{5-;oeoYm84T__plU(M#tN)9gsGeXw8sZ@ zCK$|F;N+pt3OXox132srpw?GHoXx;EhuIJo`4AQBxL}bF;XmhsMgB&x3C7%v4Cdg- z-y{P%qG%F`58gSmSq8LwjfDr67`8wY19bP=R&ZiS;bDaCUfTxd9|9={?_S#u)(+n} zvjZ#*-#N1rY&m@A%q}Pwx^rf?%w=%~h95kP4B_)Yr$%3rX@Gjvp;(21;USbG4omVU zWmf1jGU)cHFfh1))|RxWFff2tiCCbkG@7LX-Y;PJ8ES;oR%XzOd+-Q7@<4om7bHaC z1;+uIParEmDRLgj3Q)muikFeWG9IcDo&o}(sRTL+^MVEhhl z%QAW!!b-RA{GjLtonZH!ACv|mlLkM)$`rN57#J9T@`DvY_Vg z2B>wxkO*U7oRe$<%K{J;Hhi!w0O6PN!LmRn*o1>1J~#_>$$}j66T}B+fo@rlbHe#y zS)fN2REHzZUG0?xIcqUU9(-(9A6OoK?rJ|+8njTt8eCpZ09yw?cXc9^8vxm|IY}1O zdiEB8Rj`X>ok5w|!5iYS08nu{UslhUkwF)9P(`xlZBWPk27K9bFFfH&HGejEQ6qO_TXFyJwTFaVg#rN&yr(cSOJxX=fdf- zpl!h5c~^VT5)V)=%ob#1u-pz+3Ag12Jnw)#egP^EH))RSAy7zy@^c@|q+KAB?n70= zP0}!gl=`5%TA*nSlncKYqvpcD&{PgC(ZPpvflf#jhnfb;g~BEv9VjQHS|CXqLZlI= zdb@W(WkLDS11t;f3cx}Il-nF(9s_Y0RMbQm7#NuZKnaO)wFxYLF$sW@H7I{E34jtb zB!4l3l|k|sivTDgL-H4w0E7NY8wLi(<>2%w-~&m2i2QZf9G1TzD((ux@)v|JCIri0 zE5Ifsf%xG3wGy1aW`X$N{Iv?4zkUe8^4Dr`{z2rgHQ)^7BFxBO4bER{!Se9@wGJ%Z z2a*Qouk~Q-;Q4C~x z5)(!S4-rNN6OgP?mkA_)O*3I+s1spih*)I8$Z!d&ADqngKsoUI#UY>yG7yx^wur#; z7fcI0iM2qp3^adv_(Fmlp1;@xip=2oD-Eg`n!nuq$fRhHN%l~c zaFY(eO>z&1x)+qcLd-$A1<_*ig?bw`f0aXZfbv%{SO-e}nt>!e5h9J0zg|FPLHTP1 zSQebWV4(uaQjRc>fjA5*m$?`i7(b)r>sGIaGp8|&Qoi| zV0r2kIL{#R)M;?0`2ms#=czMbd3c^W3zl{ghxPH#fvtn*sq;`SG*4Xs=cxtausroZ z))`bf{DJy!11L}3mF=-)WYB$J&d6{>oRLBIia8_0A8|$o6OgRYD|2w3iuh*E$ROkg zN%!CsWdY^DQOQxmp~lKLIKax8bVnHJA-? zpj8*3G}|S?$Y7ZdRSCC+!w6OZfd|S2kyfagU?$XesF~n0R0k*%RDgA$WP&e9($gT)NSQzb>J(5WSPPZ~X98H@ zfYQAq%zq#bgZ={0)+YuzP~&R>R5POg(QFG#$Pg75Kud+dgVhi|rz9*PGlESB1@Xa^ z1Ct!6ZrTFkgA+2d9H{@HCIw5#EOMa67t=&&|ASQyRAseE!TKL;VEz-3a&Z5H9jqPR z|KI>i!}}kcV9VkC4=yMd+W+8|`z+4DASDg!e<;a)fO^!yOof3V&>xac;r$OuISm^| z2Hijv28J|gMh0DX6$XYbX+{PMbd^T2(Edj<)QCCYv>A%g|2P1Zho?;exgwAipwxc= zWCbW~zL92Rusjb{2~SEZpvetdnY@6?!}}kia(iGV@yjqW=z>hLmH`cjLsh~}`T#e{ z{SDOrphWr31~pNhLr#pc>kjbDho=I z4q#bu|6@K>7~KDW1rR7nI>P)2;#dj=Kw<~(4W$0ZQ_wBICg6gZalb9B{5BB)MK`E@ zWP((Fn}U@=%5O^n27P}}`CSc8C<;(35aoA=11#A?RK&@^%5MmNx(qDY*MLoU4B~^6 zeJwbxNy{=aK(3ds180LCSy-~K2Pb<(`P~3c$TvXp;PSf>EDtZgo50daav8s zFagOLt+Iub-`i{%84f|Ufy=}fP!2o=n+q6#%mJlfA$eH&{ST@N9$CMjiHU*1avxOg zAUKa;_6`IBArTF?p-XNW%mzEqG6Yb{&XNa>(m++hZ80!LDZeeC^6>JzU+%d*y!>u} zDu$Hby&wbKp&H-@!qhM@xTiy%4azE#4xogOXv92(`VO`HZiearWfcps4wUkH36gXe zL>j65z5$g5WtLp9EV%rJ1r8`nIKun~;xMRahBGiQ+JI9x;}Hi~Ca{5L0vn`EU<+0T z$pnr94EkxHVcuEbL=*rG14Jh1b%tdEhzigO1IR=rg#S_=mI-EqO)yn}HDcy~GeIth z4=(%Xf-}KA1z0AS2TtUOOfVmu%C!_>9fJj6d3YvR2$n7ZNrO8Ei@?^wGr?jg7n%u{ zfHT1*MOY@-BIgXs&Uc{x696rv-Y6I7#K@q#)Pa$KONo&|ccud)1L$5}6OgRYdIv}* z*zLf`@Cm97oCzd?(2}UVfGNlvP$o!Gf@K0ds494BGI$Q_Z(F{D8utK4CUAi2g=d15 zaCLsi0UIRQ=A(261hDi6;D>*U@z!83t`2_(lrGQoY2f#pyQ za06j#Ku2Ihoejzaa?YrkKp5&f)J(7$ssoe>Y{5EEGQkxj>1c>FQYPSlIt7#oiomkq zOaKcUP`Y=7`47Zl&~E^pX?X>l5E`JG=R)F&fpJcf8!RD1R4{>-Y=9dv5WclCEFoV7 zo6rv8gG=*k;Dq}O#0Mwj>)=)h=;lz!gz61wLWMSBZh{l)4i#7<<`$SQrpgFC0_-+e zJG>Ed2P_S5#M}j24sXQVgL0vbnEP_V5)2GJs<1}PZ@CLlk2?HRW?(RccIe@an749o zT|h%hDhv#u+YoiRR2Ufcs4_BGpsO@efHq>Bp+@`%r%lX8%mS!9JZ(OaGk1k{3O|7+ zZfYwag5TKcO6zZOjaoVj+-Z3in_V)a48eD$7BMIvZSMGtP5^ zmF(F9po9Qw-ee<{>^Wd%kdnPXfI)wSI0FM?u{@|%uK=|IQL?9d!ZHLz#RSlj6mW)s z@K372GDHd31W|QZ$zCcCvfLNM2WN;fc~C2VpE|5$FP8_k>JcS-g*>QP&!qt?*(<^F z@RGd>EFA)p2AAyBVC&!|dkvHeE!k`3LCu>@8nBYRSKb+vwbwxX=Kw0%yX3V!7#Vcy z-542OX)rSA7P~Pr@M$tKn1E!BI^7^8`&2hZhC?uIprOeZP!2pr<_YkF%mJ0`E}F2C z{U1~nJhD1WA;l=Dc>|TR+yz^m<33X8JnWG76-au8tZTSFC z^Wa*@0xAzL+1urhdB96{2dI-EB|GTGDNA>#2DpJRHK67V)Y+g+^uq&`t`W_fLTFM% zE!mr)IzX97+!Le&rDR`%By9?jMk?8FKxIMM$OkM7F4+a3E(B*5nEyZ=29+(KlfsL^ zshe@JCoB^b!!toKQYI(?D}!W$3IPWFqEH3~#!28r6acjWkqL5qV3`1-;t^<_1vnEx z_|jUiOfVU2LOO^Kt`(+$Gr@ciADjuMf-`}DHY^iN11EAsCYTOR4XqcqW()pNh zqr=FcTj9ybkf6iJU;>gg>hpwTf?1x744~YP(B{o`d1DC%hB|#%^X9Ys z1*k_I_JEo<;gF;WZ{9qWKjX{DpnF}Jf#DQrCiko|1H(IgQ1b>&rO{($$Q-dVRH7W5 zHnB8spz`pvc}HH%58Av@0Zs9O(kAE<0?YMKmGGqGVFs%P!1?F|R32{9LwV5n8+e|p z7-kaa(09x0P?d0#CcsT{KLhnYC{a%G10^v;qCAM4C=tyYsA-@?xgM+oxp@PX1T}BY z!Xy#R8>lQONj?F~LYg;FVMy}^$^j)wN0=YMoTpG3Nb?5Dfi!O-An^wG;AN=G85mT) zfNo-61TL}}YyDv*`yv5QLI5>y79o}Fi^0kuCHry#2K~RF{nptE;N}g~3Pj2776i)> z5EX|EU>O3!|7`%v5IJBI+znyPn_LBu<@F#wxMa^$05xww=fXqU`}qo}&6@%RQ1iyd z2-dtQ1k1xq_9C!!8%P>lvKND`gO}_jP%gA&FI50FZ=M*zO7>O-XHeE&1NGkmP|4n; zz!k{Ipd07U$RGon@C@>2WN$fW}B0-$NT z%otX(|AVT6M^=V8EL~gfgUZbT=N>H0n@C7R!)>Ti2m!eiRCg=_xfGOp&KZN6H&B&u zTQ0!UJlL%kP4Q)p5| zE!mr)IzXA|8(0TQ$-V?hS|kW$AoA5tH=wehY@`jA1()msP#1zT3(S8Y4uihF9Rma7 zQgA{DfU3F;2}uUVImbd_2^pe71hf*-T&3`4Bzu6R4(ysz1T;_=@>LHgttp=xD1~XW~UIUfiP!BoWdM#Mq z3nc#-G?2OuD!-%NdaG>4kC8=Ra&naUVIx0ly|muO8yELec5f+y-73YsC%Z1WXl5hzhB zT7VWTKvlxiU;5G23JD4P+@TG z(hB8(Y8S^9P!1?7Il_V%%z=dxh-0aS9uVR$U;}b0UqQQpb_g(pF@nw&g;hd31VAYe zR0-_>S3=4;42(O$iXc_cUI7L+4z@5x23|H%1_u2<5ey8BZ^4P!0BRSaHo6)C%ft{B zk1Sxd5ri*o3CqOqz$T=F_~1Jvok7LX9;p8=fNCQa#Tns@ z47!g)85wM>L9MY+Mh4JIBf}-AHgF051ImFX;oSl?Aag)<#WZVJ zZNwLYmVDLVv1WM=YQnQ^*gM+_P`z*){wO?x*>DKtQc%WyXU)i9sSi~N&mSR(n^0$ia$RZ!DEA<8T>;c6)Y@n= zR0k->w19P>)J9v7q!&S?k@^`=p|YU-v5Ku&IkqIh-T4d`$^58rm1eS;A0b#K8 z50ErC4~T%RgXaNJC>NRs#1uibW|$o;52z_RgL3m5sQ(y1D|eL@=R`3w=zfY|WazVF zWYB#U!N{=5j*-CxBy0310+I*VA{iN&Vj&3{oUSyW9C!*nEYJWl2b2e1*unCE9aI%O zF(o1Lz(1%fxVK}^0|8LIa2uo*pTTSp0Zpud@_>UqXw55BCEOMZOISq#c54Mx9-apj z6wRaId0+xmF(eNpgA8nkYJeLEQ^UaEz8vaoP#(yPM$H2*P~V~Efx}Q8pghn4)`5}- zo*+ptgGeLg0a2(^KzU#vSQe58V4(v__Kq+Qf;bHN#h_I|Zs3$K0je9ZAYw`!EG0u! zwAsT_GK3F0`3ACk&mC+6w*#zi;Q>y$Ss*?*C3`A@I@@;~U@6%Pno^;iZEtW&wQ+>a zCHR2(vm9Y_3BF+M@Xod$SeglTorpi!a(HJu0Lq1SwgVN-l^Ga7CpAD8WM?QofO^!y zR+)jpF%FVY;hpVxMax)52Hgl{1_l|>Y?Ple1A~JTBZCFHN~08T`zpgS7;1zTIB{Zj zwl_fK;fXU$u^D6qDEXIxtNMNW(imiwV9;c4jsG`&GvRyUyX@C6akif3Ua9fg_n z5oFSHs7km=23D|S?tTaAe^9FYAB&nQ>yc9>yt6F~u%ru7Ap)A;1@|T(d|zi+A)F63VJ3(VE`$rf>E)s`EU6YklWGE_vrq(1 zd$KODMGD1W?eL8xb&3wM$_xzgPzQ0s98|3c64KRDW?)EnVPtU3fGP;s4!RH+wC4jR z0^jpdqc}O9k--989C^>j1gI(S!m2{?Nj$u;S^$l5NMY6O!pLAbAF2T!o-j2G4DNPN zIZ(0?NB~7LqQY)~8U-$_G8o*Wp=v=1LKmzSy5|G5gAC>>Pz*BYYh*JpFdheo(gvt5 z=*4{ujB~Cg!J-MGVx0@Dgn{s%yTGF91lR;)S6B&i5?sP$gZSVQ=9HqKG6Ta&S6H;2 z21grWT>1<+-bCGCoxHPPd3arU4lErHk_Ok6=fT#&>&gpIF0`(^s8}q)z_8a1R#!e( zbhxj?z+jO8Nv-g*TZ3k->60R3$vB8LVL?0Jx&t0F{TEbX!p} z3EJPA2Q%pu$fW&Hm2i_h;3m0$hejzV(Re4JCYo)~yb4Y<9-w{^s8=NnH4T(>vcNh} zYB&=l=?;iAQVo|1l?9b1^TD#<(gYSNpcLl_^B9O@c@L@q?slXpM>Wvx5U;?4ZH$7+ zu=d|80Z`n6x$OI6~QJHf%xFUM@b1}`6>_}oT`@Ilmky}Zv~n_=75szLN8bg za353^JhG6c9P6NR9pH?CIVN`jDi60oU+D$RhO;1-f-2dcUW^Qu_n|7`w#?iD!fqr096brRAsyw87$e8AOQq75T=HK!Cf6{7$|>ar=sSMb6E3-GgJpC ze{_L$pyZDfBH|y2anOVcZ2-oD z6Y5uxa`0?R0+=7{3u^!-g0;gNfJtC!cmpsQY&pCEm;&WO8-S@wlO-4!I(=cYEj3CB zP>(t+Qet2@2<5;F?P4XXbVdfXn;+^va03t)K%gY)2=gO|W7z~%2KUB4sQVZgRAzv7(s2la3N6Nq z8L;x3Ll6|*pz@nT5L9?V%5P4vGD!K&E6AYV70kfExDA|8CP1w~l;5+mVaXn%0yKpL zSquo_v-!i4{dTYkpos~{M${ePwAKZZ2bbSF!P(%qKP=hr0w;S!t7JDgA-e~_S|xkH z^6>I|FIajaNE%#z?*m&0FTeLgxzO_a0J!{q9{?-AFDN;KGO$bvB=seL%I~vEaaoKE zx|=c>8Pq{j7Rxdi8N35Q$K|LqFc|I0fRx`SG8h?*pxVG?Vg!@}Pr=-RP9SqYDY!8Z zR(|I}Rly?*=@bh+sN5NF9>FZX8=&%V8;&ZifZ4DXFTYPI{mg=w-x9Wv354wUj+1Zo*bIs+n&RDOFvWkH#x9xMwkzhQv`$`Xz+|A9CRDrZ3xivogI zvKbi|K?LLsIRSVk5CCTa_v6OgRY!E8uwIGxSNkOb8RPGKET z4m_<23kHD90p*6vA+X#q52^~DtgN8vg@M5`4l4H=M{d{vm51B#L}?q$hW{X!f^vg) zC}_w4suFHX0yKfch76$c@Z9iHi9HXV8&*ISLvn*3$iU}N4R8ZtY8V*Y*`acv++dl9 znj2K1zC+Co%1|AkA%Wl(hlT1N!gBO#^?s+8u3!SaB(GHQ!nLK)Pe ze+`lc=K)EuJUkCbfu&8uVZ#s7VC&#{KnBW%<^fq{(3tMza9AGDRdxpD=9)A}9ykEX z16s-j1&j>3$MP5%PJ^ZmcI7cLyblK*Zv?6lF62S-z^yz+hDlIu;5@Jc%7Ld)Wx)`T zIiNhC8Uf1#=b)xs znC5(A^2AV?mP2aG^fLKvtvNC$0_^983Q z1!zE2LjsV2anAJ;SWbYbI1HNc1Lp(?A2bO7858yco8S=%8?o^Rr~Kt0KDagrPzH4g zB%@$CArP99H4sP*(6r^A;`g?jJO%(U{F(#emiqLRG>|`T;k|JqPMuQ2vN5LCqgIP;Z0l2zcjW1ylzpe^h~WAayQILM1_+ ziy1IUc;|u<>JU)wSPzy3cP`|i!r;yYEPz0n!x8335QjlU)`Ee7F<$TrXvLvmDXg}L z7rZRN0M0e>;9R3z%D|WaRs_i@DS{08J)jnooC>J6SOB#QQCr+DhvgB7ioh6Hw;96k zihjFCb2O9>;x z>NrLQ-4`W{4CmuOrz2@FFc|$S0p*5>2#!)l1}CUCaBj$eau7*NunA-iD3P+p!)l8< zs494}x?m5lE$pCjA>eF)*?*V-m51A)tMUS7gJC>0@y5o3#=xN};kH=7Z2`M=2UH$j zTNtTWmcw&y2UIbnwrBtucpRz$ZXiqz1B3f>XyO3nhU{|G+%OF}H^9e7nW3hEazhtb z2TEaTwS*)I=E=RFXgonbHL_Kzrn* zD_}Vx9i9`?AvvLlfiVND2$BY83sO^ZH@U#k+6Cf%U$HQ_0gnuU< zmJ{s2CTJwUY72XCPDlXp!L@}0I44|8faL^7a7st?9h|^PT{aQclXeEn!*hZQSUMdf z4emR*f~|w+1UD!bniJerKn_2e2+IlKD$Wnf85v@-AUPobloLW!R#Y-F=>98bWcUjj zf&Wy_$e@@6I?o1_6F4d$IYFp`k)a5x4V=1WKsoSqnkCo+G6$3s!joV*VI5QzB1t*G zb3z_et{6v7H~^K0+u)<}31&kxXoMeBA*@dV?Z1Yqgxm4~nz*4A!V9Q8JSPOII9I`Q zf&EYEnJ;j=3BuP)hUJ7A;#ZzH9p-jd3brmB+57d7Rpqx;wvZ;oVL6@VNk-im#AF2{=%L-`XhUNr`97ue^b3%cNZ!J70{D3Nk zeh4Y=z2# za>5m`EF>quLI;%i9bp~>aTwS*+(0>@C5C~4u^c>V#He2f%L(Q1oKOzQ2}KNy6<|e> zoKOR59f8_a)!>w501XI4PWagX%Lxz_?rE?(0m5%cgXM%8unA{Cd~i;v1?L2obXe=C z4xAH8(_uNG9-Pt!8xH3EDz5KO<-y63|Q-^8EhRqC$vDh(45e!0vb=s z%z)*DsVdH2YZ)2#K>fD?loKYY?5SsD&=sm(`l990+2bNobWpXmJ|4LA!!hiq!2mb9Mpu3IC6plR4?3yE)}*0 zXwzOj6WTfo&IGLygsOzwk^r{_>{bt`JUl1#tAsbebHWCwVn|LX0~r_&)c`jTrUtY| z5bA7DPUvhv%?Wp~=7h;m9iW`B0;~fiCu~8IJ_eCS$_Y=QvY?!B4=fAG39!%sC4NVk z2SFSL{nwyq-3m?#2~dw9HY1&DhNWbPikX?Plnmit%7mrlZD14RvS6*7?ckJK3*v*z z^c^aozVQ1jSW4arO{vhn@Gfvl_0NX&g?EGbYqMd~V|&2b;eFw~U}<bKL8(%{88uaYK~9zM9_$OK z4p6GJ0qa2O!E!>a0`*{{V3P12tP@lglqL(ovfv(UJX9FmgM|eUC`mfP{0Qb8g(?Ft z7rzPRfR>9}euHx09^`lhnXO=8P}$@Lx*=FF3e@f8Xn_^%Qw2c@0W?@N6;iM#GcZmA zD}og4vjiFR*>xBg7{ydU-Cl-#NWy?FCSzcn^Q;Y)9Uv;yb79#5!Uts_$k32D*aT2G zL#E9nR6$lh$%Pf{lB%F?Z)hGYJ4mUbc6+5&LEYYkAbD`XE(4Z_7wocN>DM4>a1%!k zY#qE{mxpqp1-pVO$l;#(u!7x8)ftqrCqVu808|3#sZMQWWYB%x%*fD~&&Z&AznPI? zX+G#68BoFgwHZ>d|8HhwSO(PwPLCI$9QfhDGXyI^=73W6yL?!|{tT)L9#sy`uvBfi z2rBm#oOdw0y$l7ASb(Q^Rn_}28>B#^rJ%gyR=~($$q!Wtw`B!9#e?0d0hNcFq@}9a z2CX78VJ1xlnPd)C2{-8n+$48*sCz+qC%O$a?}$TFAb9i#-rr4z>Hy`PDzFZe61@XS zdIm%qsYE{nl?COV4PaSFi4F@Ea3+EI48*aFKzDosJh0g~%0w6#RQf^7!smkLiy1ZA zVYy>2Ja^0m=MKoU@;tCINd8zP$e?cr+F@Z0PG1R7ix3T;kDajm0a3A|0G2->{Er2& z>cR$W0%&v@l0R(0`J=QDmOt#k`Quq3EPvR8(?6o&;{eV8Mn$mBup?L=oJEsf4HiGX4r2P!SY9_s`HyRMus;~{|SJua|lx1+QG=6%h}GzAXLoA zp!>Itk-@B(k--EcYb4wb$sh6{x)73JA!!cEfv33zf-^zpfbvI9F)V-BK~=%in}!QK zfBb{GLTV@WNrV8XUbqcjs{dg&%mBF*ls}FXgBFHERl;rQfF}&FTPvXQ@P?1SYCtEv z;d2A37}D^04l=MEssU~wObuvZIMmsoY|_+;noabuW|PBE9iVKo5Uc|wn><01-Vc#R z$|e#}r+~7_HLxsrS{W8Npp4-N^B;)Apz<4ZiTqM<>SomKf@OlG@Jz52DHALMD}!W$ zRe}uqrWp(jj8Wi3Gy!TcA`^V;fn@@S3NFxm0=ORx;X9YWGC?%hgnkeo-1?6JXM*D( zKDf;k3(f=RGmTDS*8e@nn9VMP<2-~BZDqq7b8OoXy}oti;D9z zsB|$hfN~~iWiU8lML^AfC(#vx^FZc+GQqwwSSHAWs)DB`0ath?(1XfZ;>ZLIP42c8KIKovtW0cgskp; zL<$mIVR_K zNpyqYVvsqYOdwDN%LMbFs^F=K!wsGZ;-GR7I5NQos65<;4pq*6XeO|!f_8y3sz6IB zpeo_EM8LBe*sV99^6*U1s~Xi0&jcHwiXoYx8)P7;Isj#NxPdS=pd}Sh!$6s!ryn&F z7(k6e%>>F&9iUXc8mt2)6SyErpM*#wWr7N*EGQE^0Ly|i0W5Gp>E03MKM;pO{|KnF za0#3cK0q}idcYSZ!4fh=#X8V zRfQ!P7=F~idcZ$b9ZDc6+Cf8!fngz(1MdO9R*jy>$e`=2#K7QE%gCT>sl>pLQOn3+ zfv(cXAKC-n3^k%0oHj9gzyhTZ``~Hwk?K;A6`<4)8t(%ibb6{5bf5rKB|Irvz;ggN zZGx&pP*Q`N^j!5l%%ry<$AXG_$vV)jG3X{Wz)f1B6CQ1+F<_Ww9TmjVq zN|bhB9Y{UknNUek4>$%U3GV@4g35xDWHDG4+yj0O6$bZ!VF3h6l8!Jxf;pj3Z-9Hi znNSY62iykbz=J9l>T(7Kl@8Ehs+YkbhoCHobyGDyjOLy$rL zBM78cmmW4M9F@2Dl9`lRM^zP+V~KDX&o#>n1D?<2;zgAH>PSJ%YTCS;0$4= z2I>Q6)x$D`xf*I8*g_4|2i^vf2bb)YV0n1SZUvTRXn>XM)?n-4CA$rj3oY4g)j)0h zxCU6s?yKeu%GxqzkaW2KRI+=i&78u>p!;?bBf~V%*x|!Tj10RPK*w=_O7`!QASFBV zWJU%fs5WqljDT|BDe{_NEyx^D+Wyc0E7|j)s^F28;SMj^^`LUoz_|yr58MEihuh$! z_84Y^Oe3^p_hcd9YhoK;_{jyStjnRCviQ0d*3jWX}c}xE-niZXiqz zsCffT9H2~;HWf7!( z7C7L{0`nh;!=SQ~m4Siry`V8DJ@Ze4tP^?4Az9!HXq}Mg zG)4yAG|+JX>(Uq)7<<5}=>XIPh{ZxIGhw*_qN1-6mKz}afuQfEH_MmrgmucFcF;EU7BIl!z3_&Q8R3SbTU{wyn2`dmWEdkQ^A(Q ztA}Y&F0^`>t_BLB8_lrlVTGDNIV4*+gK~Qzlyd=;N0z7^nGV|LHImhO)do&?3Kb9o;fsYn39gt9Pj?C)kgNb*EMx{%3{P_~ zc?Jf{W~ixK!C3{fqIm$7hi8>JYJ4-Gji5S^gF)59sus|iI;cvxEfb)rA37rjn&3vM zXcnqPz)U&|GwCPDByn{2-hi9r&I7dtlt;Q|pyrVdC$9lS?WX7W#2m9VLUs*)8Td2m(24wi>kB^+SsPatVJ9lR>xf^wl%3AZ}P z;Q{Tiszh4d8B`41g!+#`i-CbrLOpIaBZIEuOh$%|c18wW(V2`4``SV02xu`d7-`RB zWMH1SrQXPVCMdr|L^y*247w``oL>x}9Qe4ZkkG|h&}96j9ag8fK~=$%Fo!23BSDjJ z08}1sihz(f$XTG8NudK~N*YuZ+>{&8?8w02{t4=-b?ccKY-WR#FZeioXdMOe1cN0f z)PP$^c@nAsX=7&tR24jD3aGDuImV|0T8UM3fGRPlO1Og@;0^)@4ro~lC}+a=2#Koy zoDDBbCO{QKDzVuh1Gl3a2vftr;NA}PAt-}t&jI_8`7>jP6=Mh|^IYT%3Lnp14%Gq5 zpk81dD3#b5BWPBf4$7kSVA()M0|rM};D9osBg}sw4g(tpC?lyns9<1V zlo7H7tz3OJ2U3;Efb%1yDw7ccEqPV=y&J3y#8yscV3ZXCsfFZBMNrOc17*t3prw7* z;6!}@8Zt6WAV)DUFwS|n0G25sD)x22G9`rny#tmhZNMftcfx8kTW}^k4dR0{rJXvc zMzigLol<2F<{$2YWl{%lCIlTg1lf4*2+oBT-LNXo3Cy3?4Xfgu!TOmeZm75ZW5&S1 z=mM5!p11&dN|h^^e+H!96*Q&c235bK-kRB*fq~H-EYCb~Nj>DoFAuQ$;ip)6Lb=dW zth}IHXr}j82L(M-53G(%Qa7lA6l4wy<}fnsf^zgB!N|ZEtIjZ=kwN$P97cvD&`8wo zIgAXTAt2q2a~K&`^)ND6pldO@JO@%3+?~V7@DgesIK5wna^M#i$qU_^2Q3WF_Q0|+ zOba~MaCpHA1IzPJ6aHi5&>*B7>Huxmz%PdlQ1=3P0#sc51bG5fcdGV+7TQ8p!tF9Z zEqE3{<>7ffRDBA}B#&NbUM~Tev>vJwZc+uRNhhH4@Vp+S{&YS(uOEOahUE3>AOo*M zHNXvosbOGn*MSBDD6h*dK+Wshkn=j)IavizeV`m~4b}%cCyUX5!F?iB7E~uk!(>6n zWCeoKCWAX@=@uwifaI%T@}P6F0zqk!!Tmo}Kd4BU1eOQa+Mwk`AZ_4U8x}R7BEc~W z>P4_jJu(Lt?qFrGpaXFjR8E18c=80-)QqwVVI4wGaESt{+&#gC38X^z0xN?Q8-79z z`cpwSpx*{(hzC&X5FNtji(v%@M8&RNSb+iIf9{197tbn)=Eg*J4kFk3I&cG)9ut}eXV0m~g{s=5x2a*QY;*Y`B!E5m+P%bn>KLyv~ zclu$q_*ZpjP|=qIZJro_YVl9%s}?ab=>A*4$RIKSv?+W6BZI{R&|Yv*EzYqJl3zs@ zGBPwlwSn{N3MdDj`hA4@K<0q*QQibte%%LE1y86q5c#zZDyO*%`=rkWs65<;SL$D2 zHp~LK6qHksO#tl$hpL3zVgR=V>{igA5Ga+wJB07mT^GaiR|ixvB!9gG8OUA>DfZw7 z!qkBFf<*)L^sQK$0a{hwXrOr?tpqw=gtOKPk1r3LRTnCcg0+B|lOQ%9LgEGz; zuq1Dl#w}gmU0xeR>-1 zOBoq-8I%|p7ENYk(EX;!z;J9bXfrsPN+TZVSl?x+gaL4iuo;X!CCW5R0 zCI3GlD?o`;V+v?9I8-G(Epd3msw{BgjDX6+P14f14Kv9bGynoJsT^cdI#eaxqyV@{ z?lDmJf>I^#GSpQ05;;}EyI?b*IzY)z3#T?mvAKt+2xQqi6PRt72BbA%Z5cPle6FqVOn&<3a#h@$<_N?3+~sF*bcmLVYg z%Tr((q8x03{8U)cUI9*p;UGRZLsWt@$cd@23{eHn5QtfvYH$)4nFcG`YryjGqP-R@ z9S4#I7wvUm>)=IuJ(LSA+8Z=L9ke~uU`6``4QEi+wyB4t%LGu--mB5Nf{{V@<}yZx zFVh$qbk8nhWRRE++9nPv+8-~26z%VqF){=}wSiM)1(X9%ky%2SAag)z+iyCoXzzon zf=8By4=i0<`a$J#!MO*sn*>@H2}%NR8(KBa!)&Mm4ZVQU{IcnwdpV#c!EKoUPxD~6 zo`A~3i}o%Jg_ZE4{Q*=lq-Z}0GVnT71KdEE8U_aU@6f~n%0xjcQ8SU)YskVY)UiHc zsA-@~R0!6AQnZ^ON%upfk&5rd5m#yvsmcqfTuG2F4BGsxx5OVI$atLJ%KZKWqXg{|g{KI6G_x7w}FqVcB5|G`T|u z`?i9U`=Xh!`e7TG&oB#CKWqnUhu04~z|!#gVJFyfc>S;o%7xYsyEQ-|WHt*{Kb+Ap zXn^DkXHa&Z2<1!wWs(yb8&@+j=-yq)$j}5DeYm`mkzv^^(C%?iCV9CMlu0roKCfhC zSPj(%PIwND5Ch?ZedR(^S0fT0G%r90`@*1#5s3~e&%j{07;0({IIm#VGz?I^@Vs(B z<2T5`pxk%|3)smTPSm6K)YDlpiv5tN#P)q%%Ljb zCe47Gp<&Y)-p0&najwa%e0n};m2G? z1{091k;Gb1eu#)r0nts6EC5MdP!2qCbqmb{nFC6udh=kFh8nxX6Ac{c&7 z7*c7h1R2;4)c`jTrUtYK4C-u9erR8hnjc({^8>s;a2ToslpmIXb)ZxlPmrXKK%|i> z4N<65Ksn+LSQb)gz(NO<@Eu_u1aTNtWI&TU6Tr!v(Rc$a7fgWXf(b~uU?NxkppawP}5X1-P0(EdM=mhb>b%h2v z7yOzJ%LSU?RF236THs{vwgA?2(gw@JbAb+6dICrq+;q|fTL;etdQdJj7wCg?!Mg>p zTwt&149d?k&5-2$0h9}DG!JY9-9@&6kwI-CXj{|YT0w3DBo}CHU}P|Y zY6GXN2q*`hMkfm`0ht5J1q};fxgZa!3Z9%g{NTAj4=Q&7M=of9%EN6i(d60$%>`>f zE(PU+8w)`r%utnZTV6mDHgw>81ymlM3oJFGH^Fm(1=LB9T<{xY;C846xPdS=plMQQ z;sE7>-c6{vARcQj_zcwn$^~n{I#6LTT8O)yw;-`SFOh^G|i+hV!B*)b{>G{UZ_bkpz?5&sx|GmK?@B~uMxa5 z=>^E78fRdgSV-WMi3H9Kxo>6bb z7zr7`4uqNjDsQa8CWL@83h2Dk8U_aUG9>9JnDmc2nDlHU=}MS%1NfQF;I? z43l8eZ|WG_b)bF%m18Tx65#eNEapKamm@6lz#LfkgE_Du1aTNt%t{y-7}pAc2IUwH zcEGCnwcrvD)Yx4MF6=-B**YNu5e5due9-c#^+NUx3=E3kB5(s(Go&y(AjF_@Gm3$M zk(~$B1ZMUFndlChl49op&Ac(FtY&9mVEn`lmTd#c9tMrRf8qwoDhMtBOX@N(FeoW5 zNMT@L{KowUT+`@RFoN!n0cUN7Hc0kzfftTX_rMCrrC=4c%VC8hguiV$tZ-ZgmSc0R`$+SoF&@M&>-QU|88RAwkGU&eF z&d9K973in|P&vf9162G(81e4_l}Fp_^`v(&GKj5aWQbJX!N>s4dkyW7(1Mpg+l3fG zCW6YJORHh!&orniL^cV4l|Nupen92n6Kz|B++n8tgPX$F0kI2i$_i*MVqkDz3iZ{d zmCOwByTF-^i9?UU{TNhSZZ|VS7gQW{KMkTO56*kr>lxfXK$UvzW@cCmQOalqDg=wc z_t|jhF<7pGx)Y?tiZPVYh=GB@8I-PJdKZ8Kb+6`u-HZ&njyo6`wAL^(7=x0j7E~t# zXqy`+k8bu3Muv7!M>l>4Bg4ftj0|=OIt&bDyBQhuDt0h3=&faBaL5&BVAufF#|`R_ zf~JpuaQp9OWUxRNho0T-XaQBvnZ>|h=>_G0aVT8zyL8C7MwybqZxTtfs95M zhZ=2}0W~dWH)w7IWE!kecmS1w7r|N4-mPeG&+?LS9YD1b^nN0>)I90ry9`V0(=kHOs-M#a6bV(>A% z7<>#V2EmJZo`97>3cMFW4El-%3=E7+TET1#3;|HjB6>To_rr=Nh>B}#VMP;!&%X{< zG%WStcMj%GFr}{LiP^Se-l8NL{e+rK2TSGFC&A& z2GIP-9!7?c4WKbF9R>y?p1t6rDMDf|Bf}@CHgHiQ(FrLe;A!!h&?JyKpaQ9D1FS&O zgQ|ijn}|SIfn@m(YFryQog)egSZa5G>V?}NsPzM8!#0phLA{+v8$h%5P?d07c0dy_ zG__|y<-v6jD07QxdG3d&b`GeMAgP^oBj|K2s0O%!Fg2jtH=)i3rS|ImsHuG$*3`Zk zssohTXMuH~r1mRF(mNp1h}7=R3UvyoY`FlIh4gk{p#w_zjxY~`I1DN`(is>Sb%jCc zh*A3hBZGW?QWi)6l(gl`5_3RokhpRg1EapM1p@M9(qF*9=q(IN{PH>Z zZH%Z-770c@T) zsF&mlmd^mm&jrc5f#t#GZv>r#=?<1(0FnpyvOKgvEjEySkY1K2SpLmsSTD;9>>p4^ z0@76RhVnN+ds#kE{tjp_%NNWCl`h~;mY)_Vx-7Q9%8LvwXHdz<+y%+&2SDXTm{#8* z(4fcxMuvhdptEB3Gcugm0vaO(l@}rhK;?ypp4?7#V~>Jr$<|j0{=ZKCnsR`Vp>G=_gJn8YFN&!Spv2z@ zmC*vZ9#n#)YVjRmWYAr5fRORSMAJ(R6^3L3TSMg9W-?qYV&y zBX%EPWLOO~7+j`Kg>vA{Ge6;HAoqjHREO=bG8Lu;UN}t%f|scip(ZQ^m#NTBCiuF* zKqgRgECL$-;7i1zY7;;q6{{5j@(;+SobAw-#Ek8X43z-@B|nIg~)NgESD zrbKAXf|;@#X3BGrDT+{4a8oWo3ta{VuqmKLaiE|*05T;>>jBIZt{qUfnC)O>u=GSX zB>?UgaQG!a<>56(u$IPE2q3=E9n!k{vWQScb7y%P>DbV1#|aBz96SOi+$6aiKSDNJL8 z8C14|`g5MVpyHfaAEZ|mboiAgFQ`6X&{qd7$7}+Z4ilhZg=h`worIOI5EUzSz{*z$ z|KSc;`PvLNL3bytqt*g0U{XPRaQWH_E^n{xgq5Fd;PMkx9(93^vug(zZ%VshjiC;x zv_frgN}q1*^XaN*ViF1sNCFh|Q7R7uG6Koai+ zde={&X1y1Ptj9Dl05teAF@nMUGgJpCuYCpUK)&Es1Zo#ZR`?{yJm~BrC?`6?0s)kl z9AW+daTrvDQW+Q+YlT6Z1Q@eU!ZKDZJY&^@GZtj>unw#YlCGMB8T4DM85kJvgVWvt zsQrknV{!(Tbs#DjL1P}^?UoR})gD;Zc>p${4a5gGO&)@?&OQ(yoOK?7vyT2=Sk`$A z&Ksa|2-5U;0?r)OAbIfQ;Zv|YC~JZ5=XeH|J_?cs=bh(Z>);)U7f>#=Bk>a4kxc?I2SmA<>9%&2`nvs5Y~Qn23rTu z1ujr7G#9vPgB)IZ5S9xbb4eVBqeu#hJxxin0#yNN{cmP!l z$puXy1I3{l;0D6ffDU7aS_aAmr_O;AEg~00K#f8jJPL;D0Of+0U>zvApae;p{XED( zm^uTfT&n{2-cW_@b4Xh<%MXl30j9? zjj0%LUPuD*!8JlGI4@j249g2~;IxjY5#qs#UH%BHHI)FChv$VvuyiI!8eAhJfvtn* zg=8ofnio>EK|yi+2rMs@XgfQcXJl9d^1%Y2iyC z1CjH>4X7+AFKB^f!TXJ2fdfkYjxhg$I1DPQK;x^6z^R+D@e(W(EP`i(MM#-oF<2QS z6D${I(02h{Q&tI1L;=t+Kx6{1tFTM}QSlu#f&;D)Abf*kuuM<|HlYT@2UiHy;7qUu z#0O`B8gM31Jr2tRwctdK$OLuZR9*;@2UiI7V0m~ZXaGy^21$c6K_l2YcqV9qa-o@^ z8Jr0uPrx$4WNl|qcD@7k-v&@7n4ryfg^@uw=@KJDAgGBQc8QUp^aSWcQBWqxxdh1s z6_*$pK0&pC6PCn8v?RJz_|s)*CRhz>`-76Gr5;ojJb5Bzf_G5k%5Y=?2dG|nCg{+P z0J#*D2|$f+@We3lNznKzR3+RNk1$xZ19oc$R34rQdbL+xfoFmRP{oi;pawFq9I63s zAWRKtd==_!P$sy21(aqHnLq<-6lx~e4AlY31i!#KP%^<4Bx#AOAOn#z0SDA6piH0- zmIY@5Sm1!ty(7$jAP$56OF0Gx#!T>Ln+B+6#I3a-ufY;BM1?1)s|-%a5Ps81SVGPM zn{W=q2Pfog@Ma;lQ?P`a1Ks=sy|p$My!j^xBn?iOd0_4ETWc%T9pn`m7|uf-1i!Vm zOkMOEBZIDvA_K#mQ;ZCbH=qh8uY%oL3lo9Qe3h$vUSnjiKo^HjPk|$xVG<;9!Xvy` zebzO2gnxj>5+uS8oB}ORg=&CDI7|%#gZnzDlRy!E>>4P35D_km+z!rQa6b)I3ySK; zV71Vp70|774I~YY zCT)oJi5Zak%1qngz5)XS|71uqhewmKcJfU|2HiIb3=9{}GBP+yKouaO2_^!MCKK)H zHyIf$(8ZCX$pLB#Jemx&U)+R8(*%@g`f?Uj9z!+2qY0*lfx-P9G!Q}2BytNin*7kB zi5qGTD4GnxYLTJ|<|;@u$%7Vr8~}$>0#p|wnx5T;MH57Y_&Hc^g75>*!J_FP*n~MC zJ~%fW(!Qy{z;NXpESe6ZM$-{+G%1{ibva(^+k<+l&ml zXA~G1vd%LyIPQQdKtvNv1RhQ2wC!#)GFYICBS+H%s44JhI;Guz8y-zBP@<{tJg6T9 z)c}tsm>LEK_dci`D4KTPMvbOSv}jrlRSSxy8(_6a(FAi9IGR*+Ku3LT2Y1C8mF~bA zXxoKBp$r-j-VSb{DJO$^>^s29APIhtFoXW17|<1h;1Fh*0!jFY2HKl@uqeI?R;c2SQK9a%bQ(*<;3gYD9!`%!40$<;0D_L3$Q4^3663^1ML<#&b2SXdMUTT z^6&=Q9k6sMNE)1T?}Dv^H_+}uxzGmMeQ=Nc@gXDrE!VLQMpu=CjfK!nHG!PKE;NwGBE`X?Dz6>kq zAbgw4uw3vJY(fW!4=(7wfpftD5FeZizJqgt;T2de_yJDkh+Oa!oXl%M^5AaDFR(m3 z7yJfG9|uW;bHN|5b?{v97s`d^f`8y{it1HZF5uH~2Ic2HQ2z;ldUf17TOWYV|Gm%1 z5PKDL{_j0ThQ_O)^MCaj7>tDPLvn%qeMW{$P;KCp^#jU*r_n>gGePEna>4eiuw1}5 z4U+!g$qA{Oat><3ejK?#0jd{n1GCP5m<^9XE(PTR{%fF%51=aHwp76L8rZEKPzvA zU<;D;euy+uE_eZz1?7TkU|DcC1r|7Mb~RGwMEqWrDZDnf)@`0S(?JK*Io$3BEmnWdewbP*9f#Topk0eb-=_U>(?m zTOd9-6Rg((SuS)PHh8;12Q+xwd>xhvHtL`b-fq$X4c?vy$%8Y&X0SXw6KnxXOWuIB z4z_}=gJ*(mP%bnRY}Ww|Se4&^WrE{6&YlnD}Vg7!Z_Rl;pah(f6fDxmW4OmI*q^a(r@T!1Qu zWP%ouf$dNYa06j#K>MFy&R)gL(EbE96Bs~^Ld^t+p*ldBU>R5kN+x)MBz*)Tjg$#M z`(FHl+>Md9n;MGOV0(`olETDKB)*9jm%fqvP09ZO3Bn_?y1i{w9vw#qk z3(W$;x}ZzZPTq!P0cBlhP-d2y0ZGdRpe&%EyZ0IB^xvn942*Xe8Fbm7GBT*%0iFH} z$^x=aAz48EDIwNpmx8ju<~yJ%Yp6=NEfLW$TflBz0hNbm0a@LM=kP4B1F9I3 z1+IY%+z!p5B$fEtCG1wKP{fU>|!unv?gAOf`vBz+trjg$pk zp|YSXa33rSX$`?b2bAm`VIBl=7*y`LGB7Z5gOfL-$qQH>;D+Y`Zg?Kx0V{&!0Ra&P z{YKCp01a>&Ismm7kq7?0g5?2-il292c>uyUx(mw#nqU*^KzwjDpasqY+dzD99?%Bo z0rh*ZJfH(k0fF4*Lo(J^7(tAPD;5=Xewho>L453_T9xwvu0qOg& zJm94349d-*nSN0ET>#1h_PPgOGBW6jzhGntzR$>@%l(3pq5MARoK#RAP<#Q&1KKYb z8Ioo~vJN<1bwD}r6v`*E6l4x453Icp%LDVEs^E!9AQqN7E#sha>U(j_WJBfQHkj*j zzk=q0n;@5h@&M}t&`dT|CES(-cuoVm^#)WPo(F7nV_w1YzyYXYNFLAt8TcHk0d62n z4FiKaJ5&yo2l`&2<^c<+QK)%98L9)6$k&2(pyUA;B}`UFp}13`-v|GU)QZW@I?|2y_lAC=aNn4z2S7M=%AKovvsKo!V9(Akn84R8ZtYC!YYP|HAh zVA5OEJm3H|3N;S|Lv?`iz(%kRlsr&^Bz+ztjg$wLLS;dD;5k?pk_TX+14{OeFb{$_ z3@V>MhZ<;#fUm{0c?Zh_n(#cJ3CRN`42)V}MUXt8C&Hlrvz&o}aWOazDa?kX5kwwf z`v}Ve5EXMD!}0)xf9)|W4=e$jp!5XR7Fr6<15qG8xE@#r&I6~P!1BOya2iMCffe9H zF8&nO7Fr3Ghv$J+VCh7VG`JpE4Ym%R2i8Ej&^)kK7j!M={->}!uvga^l$+N;{l@^h zLv)w!$@h#5y7KQB8NNRSHHzLbGDtrIElM_EU@+2r2gw7*?-&^lLA8O?)e9&Go{Vj07o7Wm;;FgxDA_hg+D;^KrN`*1K6+`mC8IXbQPz`VcVQN70*HCAJ^1!qYsCmEx zY7}Z7Xol(l<$h%{0jxC)g8<$>2=Sx6p$g$^j$JHk8&;xMTE25lua z6alYja`*_#1BUQCUKLb!6xT$;g6C;DJ z@<&DnPf%k<{39bn?n}@TdQcwF{Rqhe<{uduK0&pC)0G7D)H!$xH4)heG6$3g7Qck$ z0X?WHcw#b0faigCP~-Y>=$e*hj9g7B+e!ScW( zunEUNd~hCk49)|f7Kk}G4?F?qf&AC7Jn$5p#u0ho890&e2FZi-z;m!XJP*78OS8U# zwS``St%K)*S5PiA54;Aqg_7UE^1yFhXHahDnFlFh6F_<3hwjC%j10Q!Uly*FU3ow`@DyqzvK3?wC=Yyn1Iq((P*w26m2g`+;5iNK))`QFcpmtwoAV8x z2VOuGL-IfY$iU@L4R8ZtY8V*Yk3$m&C=bm0hMETwphltQfyYoCpggb_tOF$vus|&X zN#BM@Bjo{8s4OTCdw&Be0P(?jz(Ef*E_Co6EDt#9p^ghV>4C~%fe^i`KNuNwwZAhmyahF8(wAqc7soUSUM9C!*164?nd2b2fgKfv-pA5;}QF$E;S^MD^z z?ktWxumCC#x4}nG{wFjK6oVQ?pgb`D188*vR3+S&8StD2cIyeKJUkBs>J|Ki=Ybzk z#gII32xQ=Os0O%!Fg2j_1fhuolm{05M9l*QP@_=ufH2fFP$EAF)`5}-Opv7SL!^=N zKq^!gln1_pWg&S07CNA0?+EiCh{K?A0(1mKI5>GT`uu|BfpB;p2#4o^2(ThZ9*7oU z&_Be)z`z&-PD2l%_9F6t*k4#4fT(!-0hR|Ke7TRXJP->uAq&I@*8_3jJg^AF2j_u! za2^o;1j_>n;53e?2NJ=FJRT$ut_PC9^6)&643=IAk_P926tH#hJdg_ILi0cxI1jLY zhUI}WJ!epE&RGD-0|!8PpjhwbZ$<`P{a=g>_MoPQ@-Ie)!aNA#FsP(+GB7a4ft&h_L4RR+AP$}f;vjhdyj~(6tPGM1l0+Ev-&ip)Fjj+8 zkpMIh5V=5xfr-KPS`u7^IjDyKZVf?n6nuf@f*P<1dq8|}E~o|Pf-fLGI2Y7`b3y7? zST3jsr*cFtXaFbkjUah&E@%YH!*f9sSo$|e8k`H7!PdcZK?{@%%>}J`pdLZwH&`y1 zs^<*K&vT&u`vA%Xll1QYV`R`Z{>#WP5!BdF|I5g*^&4oP94Hsq{)OZM&%cZeo1ogj zDGPKi8EEhXo<p2+{8wgXw zz~CMZbv7s$toe_c3noDAMa>1pP#vILa2l)wB^OLVl70!1M#=?8p|YS{z{tSF05ZCm zfx!_LI-rE_2=gF_!=SPN6s@TuSF#xy7{eHt7$EoJq{4GSDkK*aF)*fq6+!YqmI#CX zT+lrfv%qP{0U88|JfOe~%L5PNjz%m*iO=AW?E&;qbLJP#}cOZ$SP!Fga2*gAL~SPbPt^S~0lZORM` ztAE1sz!p7cP;Nd0^`8Rh5}}QHj~SU5bS)W}7#@Qf8@dck4D7!^d)*8f7>t}5m>8HQ zMnw2BFflxWY6Is1j>TvxG(+S#$Q)1}u>A$g19DJR@WkYi3eN-gpvIZv$O8sYz3@D+ zQcstOi9r`+L*_4N9+>(IbT&CuCES(;&}0qG0})Vpcpg}%SI5M}U)-^ocO&f8_@qxvvlE5fng!0Feh&*kE};6s+PlsD}W~0}#H*Z&)4>1Is6X z_~3d#Tpwih3=kij2PE`CJp#@@usk5CkJ=-U(g*bjLP7H2dO#X156=TKVCnfFX>cBp z1zQKt19DIXkH~)e9F94JWbo8IIFfr)bGBYul{$*s)HDqRD zi2MuM=?2OJ?#z%p5Xj8LAhZOM?7?}!0?L7>&>oS~Aag+Js{b!65BNbP6NBO11b;C1Dg6xtnfT= z0je012e|$*GFVQBYJeLEQ^UaEz8xBPpggdJ6_h9ud0;WtJa8GR1C+=wgLRhJo2cANwh@1zR1Ihyr8JHM?K`GR-4yp>Cm_8u#fE`q>7)KtM z0F{T^;H+=T0nG!vpehG!10xfICFp=mP(cf~#RF~&*sVLDCc*Q7hkhFeJP#~@Du(2N z7?6R-p&H-@!qhM@xIc#`4p1K0!2wE?h&*rrYaU>Rng+@P*TFha@_-7G^jC;9QXU9} z%7XF$FDJ-4NFIQN4k+0>!aNA#FsRfQGB7aC1UK~=Q#fIHU?w~d%!K3taF1XXSQ#XB z%oAbIcL6P%3kIj63s9R8xj>HxmJ1*%>KU0BY{4}FgujE4iNX8{=!BgRun88<>&nT*aFZF-EaGHh z_{+@1U;>gg^5=x)f@n@Ah9amoaLSqi<-pVE9FeOab3nPkm<5&#)&?T&pbaX_?Rl6O{MnfpOhB?m zp*)aWkjTTtun4LRoU%?pIq)>PMC3Ne98fN3WryX0dr(#I^og{1bRJZ0BaU3~0V)r- zp;6zF51I=$f?Nv91$Wt*7%cxoRl;p~08QA?K7qsvNPNO`L7V<0K6oxL$be)_NG@RD zU}CUThiZTu2vY;vM*y`9lnajVfs!R67yQPW3zDHaK)K*CSO-cjXhD)@8C^PUCYy^585m9V`#e0yDtU&q31QieM(#I(QbC z1?57sz-)bx!(F*xSzx2SGbl43fcj4WbXLep{a*r147vgQOboT4lH7%#iD4lZ6N3pz z)+mY}k_A%unHa7?wSlt$!%9ftz)w(GBXSR94k!z};(}!XF{mndT9U|wq&Efz%S%w> zIyYb+8ZvpN{6b1n{)wgl6yJSy`Z#zKoFEv5ourSEw-~?CPQ_A(*Aw04wSUN14;TXL>j3E z_ym;&rF~H$kadt602VBu^zI1r8Hi)qf$n%6sH+$l*f`q22V%G|Ffbkkm;H>rLa^L% z6rMYdf^!F?iZ}*V2FV|%L>Tm^fx66J!0BrO)FMO`5he=D9}pE=xMBGN!hg#R%O79C zCRp;o8cN^5`J)iT2j`FP;QaB32bMp6fYU!BfBXbz06ktN21ph03oH-MAHTuU6(DJF z{`dp74xT^$Lb=fV@ef=@T;ql14?Y9uKp`dunN^Uq?EuOj+y+u2ObohtLQD*Npb|Jq zh>5|7kBPwqBx_V51j!#QAW@h$&}m%}P!2rJoe=pa3{AZme6akH2UP`6Z%9>y9#rlE zjw+%7Di61T*&qevQcw<@0&*!Re;nXrVzBIos)XB;0ndftyu1P`53eFP47Q5EtB3NB7E2gbXOCood(J#Y@#3? zDA_~=Y8gmc1tN`WlUjf3m z=Z9s108=#IFl^B3VjT%7m;3}dNEDz5D zWnk%3AZc(GC&2&*Fh89641{091QLQK>3$%+eF(g5?fs<7SlmkzouSFO^=719DEv3$9iTMMDFM=f zk_B9lq}3tPNLip1DhtX2_F!2^O#urXP_lP~c@V^5P^Z{xwA`iq#!}0(`MFgnX1kM8x{zM^I9_R;~a2Lb} z=Ya{}JRmB}1ifZ!A~+AU3B&ThBybu>R0EU2iTn~s9-IfJfaT$NU@BNzMg-PVmggYLtNFfo=&VhDlIu;B>VE%7LfQ&mycKb3l23M--L^&Oued6H`YvJP-6iPwn4ej zT(I2$G-`B49F_}?8#sgV^BSoCE`V~u5d(EuCI;PVX(k312_^>Jd}$^I4GAU&6OgP? zt287ROq6C~I0V%O&IK=^9C#Z2Ey4{l2b2rqBw)GVA5;}QIYs2abHP5SoCS_tAg~US zcHuVcF(`!D&;@cSs3zDZ!Ng!G4^;`bWdk%}Lvw)zR34rS4jLSih35hesFNVM;3mjG zcc=!qfiN|o)zwgEgL1(aSx}lrto~52$qn&I1s>sU$29oCTZE2;zflf^*|qk_WUw29`rLzzu|{ z0j;ivIvbP+ekg!aG$Ieo#hM2;Lv?`ifVd(^2TC5ef+TGUkw(e`tWc+b@_;W`7Lo^G zp#w_xjxY~`I1DP9prIcQQShMAB1Kpp;DF}=4pDF(NM>N<1S^8%0bWrC{db_T|JUF& z)Bv>?F=*7F0?Pvs6@D^I(AfhBzg-5F2i|~9xD4Wh^T1nh9sm`Qkk!@izwIaq7xGuS$K9{2*~Li4~^1JIyRfgCIka2q;j zC^9h!Y=ER+15h4dHq=*UV$f|>WMWt$$HbspsmR1|MvjTW1SD(JuL#KlGZmQ_)S%kH zc_0AFfe#vSiwc9x0p$Tvc~~AugQ|ijrU!^Tpahlsj$_cM0V)sA1HTL^VKz9*Lt8_+ z@}N#JR3+RN54bI0x2}N7!%g~ca2RIN1ei&CKqhU6s)U;~18$Q0acHUlCH}9M-qpu{ht0@8t!_&K1KfuxNf(nyKl2`URp{9a&LNaBYD3n;xi!h8neSl)o@ zfIA*BXrxjBniWtH4SL1Ez_>>RmM>J``9cMpFCfE3s$gZ1oS`Yopr7T-z`*Ed2lnM#0Te$07Fp4GzY{7*A9V(psdNO2+J8k zhNykzU_($}IT9of&KV(Kd3eqU1xqgmNrP*LFtBy-oDmM?LUTrhAvh?MU^yeh&>57+ zb2dV9Mgk~jq#AmvF)`@QRbgVVRAOS#ov6aZ5Ua$*U;>ggTCM`g8Jks@7@DBkAgK+? zfu}ZgQC*NZpqw#T36?YVK~=#c3u(Bh4k}lMqjtCem5199Yd9HZ!+wxULAAqcB_;;T z`%sl|TMY7Gbr9ICKcMpP+9ApCi5k3i=zuDQ)DC>gObnLnn;-!MHxQ-L6Q!JNF!yC8BkeJ2FU`;f`^M>fdk4Cjxhg$ zI1DO9pmwhnICV1~REK2(E74-;aFG>KCa?x8gJc4GQ3m}hpv4)hz==oz8U~0=(4_^- z1P~R5%CJlT;b$qsGQn!F3EMz?a3)w|2(tVgh!4&LYr&ZyUIms3)`1f_qIy^lPUWjX z^59Ic0W1&C1RKH9UqRB~Ot1-T9Xu0khH{~qU<)`C1gpX_!4X4ePiyGWghp<9)SL3f%u6T^B{CI%CbtkD{ENG8~!&cpz^HU~8J2ToWwpk~06sI90G z$Q)26c&ZA^1n;1#;He2I6Rd;EEx?fp7&b#vBHV_ZhBII`2!e70C==MKF)>*3Lsi0U zxdBa#&?b@wR34rQ_8Gp^gl7Vcd`OOgWP${cf#y&Ra06j#K+8>`&IV-yX)V-Da1m=Q zQ4G}q$^=$m9VnS#3X*gLL>eg*oPf%LGC@9A7MuxSfdfkSjxhg$I1DQ9KzA!Rfm1i* zB`sJcaDrz7CvYZEION2@!003jDsmyUf-_hPBty81GUyiwF)%Qm1Scp5Xm~)63S(fL zlc)#F5D*p3YOo9e;qOs{Wr$N?6FAgi{pQo)4B-IcgEPb#aE90j;>Uxov^on;;Yu2? z3~~;f0YDeILhfBS50-Za$%8Y<1*rUvddQ_E7s2uqLGs`$pDuy<%oEqtn}e@>x(w#O z2g!qT&=s)zm?kk9!TKaiqg1_t*#NYb2oAY)Oo^AD&r=ssE{nDm-@26sKEQ$ab|5-b7E z$*^buWnM>ENP;=AzyNU=RH~&I7#O|4d6aRb9xPjX!Lzj&QnvO&%GTasEs$*OFUp`l zm4$(U@gq1{KY+Rjk*zI^VA&d?;vT5p0}lm2_~P2IZ2bvrLNbUCuIE03v-NBcADpef zfU`A^4ikepI9q=OXS-S*ShoHK&9)orA=&ynSbh;m9-OUzK;>cC`X^Za4M-lGt$%^} zh;02E%=glT^_%{H-3QOsf1zAxw*Cj@LbLUMaJF8d3(M9bM$VuDZoyVaw*COh)`CW@ z222dP9ePX*AYbU#=`k@p0HyLGJthV*JthVdkTRnwdXQ|rK#z%G2UI^eTR(tu;Mv+& zG#O+dC|mpJ!Ls!?s494RM#|P(pmLA#W@~|M5J$r^Ba6{_m^~GG&}_X#4>Y<4RSCB% z0-C>|+1dgs4>yU^NYW6Rt&hV@`V2D39jX#;(h5|Q5}@+%Y|UqsWeCsKKcI>s6|k5- z6N6SS%H8dDN*?N&7DE}c^p*-)9=JwIDbvRTXC|e%{>qE)bHAvDAVA4Cl z*Gz+Q{S2ryC|m!8NrSTWIj96ETMHP0oCD6*uxJ2fUPo9+f;q6j0C5;pv_R(`1cCD? zV}lVaTL;0jbr4dv4noS-!C)zKm!kvt@+Jh*&3q4QXf`Z zL-_Henx#56;%oMxf^FHxM73t!0creYkW3Shkim0yQ9R7{Ic%oDrl?2g}y- zVEMlwd2qH?fXc(NwIW#F-H?gF8l0__z*rtzaJGg;11R%4!a@?vfdvML!=R!F8f=UL=TXLRGg!8cf@kX}q--69l&zz| zS|HgvUX(#UL!5zu(Gi@i6`-L#1rq2CjB}n@!m>3)#S~*$wubP}8pE=+6W9bv6IdhM z8Jw*HL40twb^&MWLm)o5wsr+)J7ZH=wswPNTUfSs2g^r*_rbHZFO&<-)_zbfG+XSSIzR+L*W$OS-)NJjJCtFX4>I3E5e6T*0Y`q6bx(_Ce$ksQY(x7a; z93~CQ)_hQ>g0l5Kumm_;!=eF{c^zRP3Fg281H@ra>6c|-U~CY5UdYJ6sBHzy)(xVQ zp#w|};B2j&TEf8CC~5<`XE}+1u?egmlBYXG8T2hc8=Iei)ARwTb8;Z5je&7ao-Hg- zLsYyshvjJqU&8{Hr=NmNC;{=oRrNC?kSo@J_~1PK9Gs_REn#{11tO1vFaLZA&V$t; zd2k+n1(t`8G`$8(9|cK+Yu-0t>)<0zZ=qaho_uEn3KL~3Sf2c66`Kt(X|1t(X{ezgsdfOtb=R<}qPlFygghVql)QrQS%w3X&(4te6=3q58pj zvJ%RHU*Fp*y3`uh9<_qyNthORVti1Hl2496O_2fB3ZUa_+eKf)O!*8qP`Qwe%nSlH;3Umd48DB1473{<fOxpof49T>4AOqE*8sG-P)PNR6 zK`jGi+W!zAGJj?av0@D2Wc~ro|EL4K>QEh^>?&sq3KNu0u@9284MZAgjB6287L+N2 z!Losj1`Lj{zyW1FN0|RW90rwU&~+KJz?0mJD{Nu;aF*z1Xg-_;$%iEjjI+UtAenEz zD1$z~4FdzCgE6R`Yyb@b#4vA>11$4FR7|vnWj+Z1lr=2#If6|Pvw>BpPR1as{Xl$h z=5sa%wUhVTz%rkUF{E9*1KQnnH3qetxou&qLEOOd@XY59mJS6;gEOB8*gAOT^MrDt zna|4@v@US7EiCgz89RfDkv&lVZ2)DyaAQ+@CI;PZTPB9rwoDAV^|nk5{C1!{M4-$! z)fSTH=G!tcT!Lx?=eZwH4m^R*6_o^;14^K-cCb9hw+E8=;Ax4Y6qZ{o&p}Pd17`%t zFjOE?7Epldh1=k7To1FM&<85j=L05=e(22^iBoejzYOb(y~jcAT4K#fAp0+XRSKv_T$tOF$rY(bK?he#u3 zfu~SeP!(%?Lh4z>=S2Qr{sXdcJ}=K)qn zSRSY}b_V6P+M!%%F6b}@tqTlyf#rf3#?GMpEVB=ioCQo7 z7#OD-+q*I`=uUQKV(52aV$f}OW@6av0@_4m%D`YW*BO!vRys2=7(umxQ&t3&15cxC zM3q72fO5f07g#RHgQ|ijCy#P?F3^L@ZO4%d8lduU8+wg9VK#_@7@%C>=n7g70#ymO zr30SVz;0aum51koNyfKa;kn=gR52tMq<{?E4%GlR5T*t+Fab>*pj^P?hMEf!phltQ zg3nMLpj@C0)`5}>M4*;|q&*?hNV&ijDhtX5iC|esE`Ws&DB(N8JP6`2s1)TfFfeWa z7xIkT++ew213VXOfaHP_2F8tGMUXtORg^*ht1|-w<6LkWIsmm7kp~()VR- zCWeRZOboicZcGfU9-xgvpggeH4Uz}ex-l^%!L)%k3Uxp^@D#dPR2^gvC=XbB!1BO6 zs4943%BXzuV zzy(R#7b1<62TGx`pgfQYmWAX2Sm=O~y(7$nAP$4d>u3fBMmsTZO|ZukmIv&_f~6rR zPTGlq8sw0h+3dl}Ai2OV)kt_fb4f}Fta1$P%h~8hUJ2DP*w2snF<~4 zVPLTAgUWrykqaI`<>5AbF`W#vVJCh=1X^;HT*m zZ+I>+sDxxpNG{;;VPdcZ-Bl0DHgE%BYCtnjP|HBMK+Fd<7c79o`IgLOaR!DLKCqN~TLzSJnI=MS`@ACqO1LJz zu+iMRVD0d0BwxrlP@=ES zmx;l0JyZic`e156H>^VCK+$*J7d84Sp@|b5eHjey*-*8hh%vF#upP+BH-T>7D4|8UP|AFu@&p-+D2Z5ko2UG(* z%wcL67~J)sa-cBR4nhs{ooHe14OI&Ya}Tguq%eoM3KHggptIy}D1g%W0;n!Tn4b!U zg*ik8Gbj$gVGiNj1i`}mCfI}y5Feb*Zz%*zFfbelf`$2Q1(bAtM*)=1d4gf-{4Q8K zJj`D}!u&ndLGUntrl1te#9)CgjvVF+M<59Y9_Eh}ii6={z5yl7!-7F$eozhYFo&sO zU~oSNbpR;L=Le&P`B}6ue+^X&3iCZ+wMbzOa}^}aWkLG|-zb8@+yQDkBFybVVPOtY zQ3;9zaF|2*n}T6s{uXS)KM)@r=I<1XB^VgYLSSM3UJ)hCKPZC2ya^-?PUj!N+TmgT zM-iOP`=JhkhxsqXX(3Du7U<&0VSWIr2Oj3%6rYE{!*}2Wy9i`6MN9 znCl;fq*-{FPf+3tV`8vC7e@~B1gIW(nD;0}hrz=<0VT|x!axh2pc>#|4pYOx;LZn? z1BH2e7;2a^zDHW{l)>Px4OI&Y^Ce)lNMR0h6(r2#LG6}&&mjS0Fw(%nOw_OE54fg~P(UNEs!}iK6f`s`gO9lo;P8Cp?D?n{Wg!!yUSeQdp z+zyAOa|mA~0v6_6U=tERd~ld^t6Y{~V3-jB3v(V7lrZO20fqS!kTf{V`M}!YVJ@ix z4)c7dgWzEm)w z&yNI!BVx)$8!gPUp=v>4-UC*P6y`8jLBjlqKLZ2fBe0hbKy@L)oG%&{<`5Nupf~`h za|pjH5*Frn96Ze5sBDjdhq*%ya(TWa3e-M_YJi71Obw`g4h<1dm_Lp} z4RdF-Fz1Gv0}6A7XpmZ@Fo(Gc66Osl3=E9_RYB=I0jdiT=1noMFo&o(9tBJ15I)Fk zbMQ%x3~C?~e4=4(MMgDYNd|_NXjqstsiCBEW;IYczW|a3hdB#aJ3P$!)xha|J=8() zFy~df6%A^iqlqJj`2(mNJj^-Ov}535J^>}nrD8zsbEpP*n8VaCFu3$T zL^_W|3-i@bwV*IB0jos{bC|0jVV=jwz`(c;>}7@%kYt4j^KY@RFo&qH1;qh4okRF# zF|c&L9&ExP5Feb*H>ep)GBEs#fra@-)G*%!4s*9ySX*&3SUWt-_d&wkAL<}@nD0^Z ziv_jM(ZrF%ya6f)5Az*r^JC#*{s1M+C&Yr<=THssFo&rDwa=k)pfEojiyGzyXkqRR zRSOF97httWVGeT@B+P?NK$~bZK&f;AR2L%5v*KZ4?gUn`859TLFo*Ep#KOYd87yxR z2TSKJ8o`nb3gD}TiD`3?Q^IGc$mY~fZFF!i$Gy+ACDU5?Py{C8mbl) z<}qNkNMR0h6(r0Tf#$t0fxYYiwH*=W_Yz=X4pE^AiUV+%L-Rn|7@oz$!u%>~m|p{jxlsZvonHrQhllwiNSOCS9Rv^a2O4Gxp!PYMIC7XDfXczc z{EkL@0zAwsP{O<>0n|Q+YJi71Obw`g4wVCi`Q`-FFrR}K=G{=WpfJAxR*MwoFjqms z{Gb--j7BX`I)4Dwg$VPYBv_c|f>q21#Q`|XA^aN&urSX9%c~^9(s{ntWJv~wm_%5Z z7igiR^Fl39I$r^j2B-5Puy%Nu*J^>&x&A3gnuUjXwbrUcQ2QKB968JrpmOjqFW35= z2oLiUC}I975!611YJi71Obw`g4wVCixq1?6m~TZ3b8VaG2*q9Rv?^Cf!LXp!PYMIC7XTfXczc{GZO_6nL1gfCdy~`r}>- zsC^FA01tDR8c_QjDhCR4{#4X3KZ};mv!QB1VXgyKixlQCS3$x&7_<>JAAD=z0jMrS zn6FENg*ik8BPb5QVGiMg;snxGEC8F(mI@2=Lh!AJ`%+cPxPKSqi1WK5nN(Z&ip&H;}4pRea zpF`z9Vg5ZGHO!emppLh!hN=aHxkv^`EmD}nTm=d97rG1#j1R$HW;hE;R){d4mI(`U zh>ExAus#ulua*G|^G9G4ia>mDm_OD7-D9#U0~Y2_P{aHwILyC+q`_hS46Gd<<{u$p z?hkbkJj~zgy~_Z#&(Xw@!@L112M_btdiI&{FyDX@<_?*l_Bm7oJj`KgK<#s=94O4Q zGeO~q=o87Kg}FCWEhx-8z-p1g9Of!WnESJV>QL~RISZh=5Mj=h4GVK?u!>|*9Dvg~ zgg++}7Uni!`4=EQILvLqXZ|Q;!NS}QdWH`)o!f)Y@W}y5gTvebtQ{Wa-Ui@w{vPTe zc$j+{BxQlx=V;={VXklvl5pT*?rN|(3m)bS4ajB7wk%Nl9I62x<}fv&_BqrdP?+D( zLJe~>v@m}SRSOF9KVY>;VGeT@B+RWq<@r?b23rTH?T9e1%YlVCM8(Z4SUQLBg|cB` zJ`HR_9EcAN^XcFX#?!K4VLk)AK^Q&Su9E|5pF=gk!yKjt)INvGfx?3&LRAc99Wod1j}Cm z@xfuf3B09*Cl?mxo1t4opy_-Ic#B8`NE#gGTfy4lVSc~}9OnAxA!!yK=KGAiazX8L zG;!oGPk_q7!+e*~>|A)5??4IjMY*8%IaC8Y%wcLk?Q^IcD9n%NqK5els8O)CqBc}5 zD9oRL)gpyC%vF#uUju5NYlGLGH9&PC!aOY>7UmEYM?rA_4s!_qUoI@nb-*Tg<-yu5 zy5O~QO?j{|*MqKwgNC_2crDyHkTf{V4ZzyrVQy^<4)gO+2f@SK(s*AUX#5;a968Jx zE0ctxU%x@LI!W^OkWG`giAHpxmhlM!{*n|ThJ~*AT znp~D*VEB;_3v)ITls*x=38+uxQUGf!a)7nN!(7+|9On5@2f@Q!(8Qfa*eoxnB`1%poc^g5m(2&LR9)1+Xwr0GnV|2utUQCZDAk z81f2XVV;B<=E>kN-vyEer}GrBc6gZQL&BW@A|%bi!#vkySs|!>jwX&A<_=Ifc$jCJ zd@qED`3saVXDI@;&!HONVGdIRYM(j8EwGmppt=xYeyA80<`5O?pf~`BIfS231Pk-qU=!AW_~3MY$3$3~f#FFJEX?ns zhWR~km>U$s()oR`c6gY-f`s{csDt2P{=!767}P#T6Gsm72T(b9m_ISeE{2D>Llbh@ zQdJCUpF=gk!yKjt)INvGfx>)xF>07kK@0QMP_>{i-vU;P6y`8jLBf0ksP7*NUPZ}p z36iW3VQy9m3v-Byxu7@zhdG3Qtr!;OVPF%KN?_?c+|*c_fg!2{7UmJqRh`iCJQBRB za~Vh)oX(@b+TmfIY6>n}{Gkqlhk3GTUkRvvjwX&A<_%Cec$mkV-YkVFri~PUn}vD{n89!ovJAYM5UEhq+7{ES+BkYlnyV z14x*^hdKxz=J!mQ%0TUNG;!oGSGWvGIPfsPWg1)t4|4&~wnYX821t3HRt9RHLp8v| z9Hs`;K8IQa3iJ9h)G+@5jXqc%`WmVh6y|flYLUVm<|=TQtE>Zs`5t~<(B9WuWw4df zd-z|dFfb?=GcfK2OF>pgAL3`w7Zqe+V4MtI(&+$o?M2WES&;rYB9*W>f~e>O#Tht` zApApRusE6mHi54kmM^BtfGl(?hsDt}=$g_9$az51!D~t%RnodGa@dQF)=)Y zss^uUz5wOGcNXvG|6dN;ASTDaz)%5O(G1f9kE@5!gwMcWIS*=r{6Wwm9wAHFfll8f$I9Xo0-A!D3oJ) zkeLCxu9@KqT0)yAv!?={&wo;UVf$<$5D5e-gDq$(@9Uq4(1N6?vUwlfC z!{6_bE14MnRf2X^nK3X#7=n0ERdxzy3=D^>m>7(# zDnaQn!N?6vXMmI93#cmi()Z7NT_E#7i|b9QU`g>GR24inBCOUSKI=>U)~4@sF+Gcg3i6Ecb< zG$AKcF)=vyKy`r=@&hObDIp77g~TX)-}*kO)@pb{eu0vZr&obat%7QRdmW|*)B=W@ z4@$@ztHEA}?^{0rH3}RA5e)7bP_>|hd>*V8T4FMRZu5pY3zCpiNl#CC9q`}G939KESjGZMwzJWN>Pr@0L zn`)qrYXC*Cw?tVj6NB!%Y9H??U6HpGk zAU(_L0Wt=ZdJSt}sTZaN9)(D$cQVw3{!Ppb3ALao3}Kp>z+kxyDvq3DAqDJesLTeC zvwbAa)FP)#6iH~hjICi}aNGga1x}Z5p&X=i$#D&mF5xw~hlFe$JY9OUAh%eT*MK&T zKsCTU1yjSo;2sAxACxXV>p-4DjBfr#E?^@V+$*4JLFqCbtQI+4!kh(3mrp=d;d*d# zW87Q^OPA}B(&YxEbh#0%5EA9vc^UM@KK65QB zU9x~p@CWh1jW1Skx@@n7rAs!{bjc1*mzP1(;B?6W)(%gXoJi?XM8X-AJ#wIqdjN`H zK?%MFCI;QMIwl5Ce3*a)jV9HB(qn?rJTRRRv8s-V;T%*KI9=|5a^UH5Gw*|XXa((M$Eht_72dhO+moR5R(q*s%0|Vo1aB^exZiJ=F*+}Vf4pO?D3swk8 zmy3BB^sPV}v#*PT%1Z&L;}Pj{X)`QcLR8!Wm7m}SCxkCt4@;Lfz$V0l_~3MTQyf%Y zPOpch%Uj|o<>hU0PGG~PqP%<|?hMKvF;K@jfFk&*__8J@23@&E zCI;OG&_+^o1_mRoMo79e1JfB1PK`_q`(V028%kF|Iq-BjpSJ>J45+*e1SJ^IMmI~C z7I+jQrORTd2{xOU874G=q7afU+o0l@>9QLtQvh=IGx0x7$mtSA5}Gb+8<-dzXFzp< z)8$zx2Ps{?fC|CW0q_U=@RBFNV;rg1r23^lN;mXW>~t6M@p9oNa->WtPqke(|H;6PlB4G z!Qf=Ua08O55b4sk4VEq;Dqc0f%1a1exe=BwL%=5FgZSWd846C9%Nt?oG7L3chJ(}P zXOJ{FT}FVl!_#FXQo2kLcLrq-AE@INfFd|i+^&_0L3bM{+cbeXLZEbcs2P$j&w=TT zh+EA}4C|n}z~$u(CggA}GOt(`*G+IGX2uYV^ zP;tz3Sq+uB0CIMc_>@-UbcrGfO_#k*Obm`4P+j13xfjYoN|!gFLhy7MC;qe*o-Q3) zk?Zm6O`t;$p&H9PYUU3P*MLegbFFM~cG2Ll7+YH+gn0JRj6F0XdL(j`R2uO?W! zgz$}~shhnguD5?#TF4MqtMnq8?6T>{HE^xZ+fO6pJ zvYU4W$QV$%ENp?*<1j7oC`3w^!B7*FHZe0CXa_|hBweOK#WB-mHdMv{9QCpU6vrF z%TlmHNV=@%WzaVTU0hfXP8JWKmLk$+ZZ|AlLR4_I!qO##@7xMYmknSO`ayhfx@-ie z%j2!EblHTOE}OyWQn(G4E?dCb;pws!DP2wwcLrq-om-G}nE;C5Uh%vxCI;PS9ZU?d zZJ?__K2fZVgOo0J zK!xDxvP)dF8=fv7prp(1ZA=W7$DtbFo`R_X&AdY8KE+PEjc38Ts0Glu$#0RI#N^rmDdOIv#R-yKLs=@sp zr^{NTeou#lGbnq=+=isf1E2_Qm0;^-V$iMYW@5E9wfT9qRF5RHwnCa3R zD)Ry4>^6y*UgUI%A_+~GdpnpI93!B*!0ECV%0Wt(E1*K~blD`awili*E!vRlarRCo z2FvYG4RBAv)G#o(|AR(5C|%y{1$hdQE*GGsO9`kspmg~itQI+4!kh(3mtmlx^3~ws zOGf8DSh`#d9`J;W6RklS=UfX`2uYWl`55#;`=A;1!J~}_pq3)i<=hFdbO}+R)d@?N z5PoteEL|FaO;``&gVUuUc)0UN%gNHkfyI|?k1gssNE=`e!I~}B)LD_@n z4kTSFfHt$)O3mwMV$c=qV`8Z70-cs^$-rQw)CWnIdSE&u!m5vnArGnxoGv4v9C*51 z&sP9422@@y1Qlf9bP3Y}k3yt$sSGvY%w}eWj($)SLeixfR2(y1T0><5K+d+4`qGb_ zE>R?*>GEtB6N94%R2Mj1)2d~C2%avjq+BP!)8!77bSd7=#9+A`ssZjPm>SS* zGBnyj>9TMF$Ww@PIRR=EtUt~HH3yU~`@w3F(nF(xo&%gZ@8x1_s8j;AF7@YUxM#_@&ikSh|F$umaWV;Qly-U)&8# zm*2o9><96|>GC@`U4HL|rOO|v>GCHyT{`!`(&aC(c6hq{jg&6gq@6+8gW)bDT{eIs zm|5C-5)*^&x(Q4Sy*;4uOHjJpH35+D`3#Xb1)xTOJsQE_{sXEOlsI+3YLOEs%vq4c*$-+aJ_eVCj1`k%iSsd1 zq4@-<(0mG32uYl;`55#;^ZSf*!D(Xy)bWVKd3`D@aY9sh^}-5G2*0@(mN@5uO*jwY zgA?a`aN=a|gC))dsEKnSIB^Dpq`{r?MPTjl#JLzLajuhc2IUckdyvHW02INirEW}N zV$k)T%)~Ia4|M({C~<~Qh9u5pFr5*RKbeUk4XO*0IH4SP;(X4x6l4r2aUSf0Cr+p; zcoZThPN*DcYUt1uP!xtR8iC^slsKW1m?ftPR7L=FaP=A~y{RaP6Il|PIN$bx5+_sw zk~pCpZy3i25uaZZ35 z1@f)f0%neXu5jA@L3|QiXsEF!^B~A!`az89_YJg3+58{IprzW@uBt8L_IJHoF zK-%CQP%=mwoH%vB+Tn>)7pVtiA?^&yBY&Wda{xuKsd&M3CI;OXQ<)gnP5_N$S}`yf zeVGbLoD9=IbVdZvG$w|)`;a6JPMjW44m@$%@;ZTx0VU3h6JUuGrUf2_NQqMzYQnjV z%nVV}K~V^)(bb^hnCVg*DpLS*wwd^`>B#94MG~4W|4v|HaI}Ex0;kJdCC#ACdn(h;s7%ZnlHNZUuQv*tu(2xbCOXnFNPa)FfUbL3x52#vDx=aMC zMNXG6XF<~CnH&ZNMq_YU$hdX}EL|ES6`CeUg{CQ3AtYT|^D*cj0L^ErOM=qn0;uB= z>5_dmJY9lSWKD#pOE7=&M0mOc^WTH`kaQ^tYEY<8f~QMKlo57qNze#;F-RJcF2ULn z=~5Cg!fqky49XsFppIJrieOVoj#;3=^%+bIdnbWLGC}FGX9g%eCK$~C(-{#GF}J`&@XsbtPqke zlld6*l|bX5^^&0SvH|LNM7lIz085t;6~RL_xCBrH_exsKXJXLZFqetp?ljOy zCMaF*nF~pmC%|+@#Fe>B40?}{(xn8H15cOnd|V)7KjO1s~zYaS1xl$UGE(@TJ zN2JU1i(u&zqGHx`SUnEmU!D$2msMaB)y(D1@ZTXHap> zbom-8^8w`S4r#ST$mtSA5}Gb;XEHH3{($NNr%PR^vysxJ2UG~2E?cC_7s1oz3zT%( zK9h;TG90P_?kSiW1_t+eP|HB+a`7UNrx5A#0Msa0J-!2~7L+azg4H6YOPI5u=`x&w zf$=Z67-ZyK3`>`Pk;=<|Naf{!utG?>WaVejHwRs^auVDpPJlWd(J9Ya3QLy|6{}{# z(j|odcqS}eo&uYoKMR&FPlG$<>9b(z@(gOH{4BUrz8NG9PM7Dv+TrQ)JW{9pmXtFn zdu)I@P66cJ>r%N(m>6^)EMj8#I}0?DX~V!^^kxwxUH$;m84)atnHa>LAf-zNChL;%r#DglU0CAyT?L19b$bQy#tq6ony-Cg3f(Ehurufz=`>PMEVGiBnk{w4f4P7Ba3{ z3QL@}NQI^yQlV)NRtQO)uKW!8@t`v`eZgf`0@U${LX%|$EOA0q>;iR$z?C_K|9Lhn zar%Kxu%81a7^%>VlXeE>ku^}qHGm>G zTAFn^6N7H;QYHr8xuD@pP~z-d3Q3$(!E{E%!lg_Md{2=Q=MSh6@WkoJe|;IW^J6#{ zmN;Qr;8BQ_I1fWju-n4S@O~L63L%B&HK;gdy1WgQ*#L5OjCAyJGb)%fw*m4%Gnn6if{$T|zAbrOO-3 zL7qaS%Wr6v`3k67P`dmER*Rf2Va|f2OFmZy2F4rUvXIef1uR|OKq@qEA{CmqzzQMh z@&O-%{v*(yuXb>m6##WSqR?Ep3YIP*Do%kqL*R4?;WN*JrOOVm3H~5HxbxEqE;QTc z!O~?HYN6Q;E;KKLq`~R32do{QE_;y*%^6b8pzN^#>bM7>2%ajnXeASauILIT2HE+b zkxWp!R9pc`m%3m&Bf@e869d~bq;&ZKY6Lu8-r>s!83QUb9YL)>a3d3@1s;V+>2eR$ z1W=*bvl0}AA&iFLvLu1Q@;p=$GjU#l$_UspFfdM&`mqu@aiT~<6KC#xCI-icP+j1} zDGK!nQsOj#3c(ZSBq`5T@WkoSja+DMpU=c#X%E!^_ZdtL1A}`V)G|=wEL{ci86t5i zK#c->G=jl>22?F5aZU!SMNXVBXF(EYI;an$2`&p6@2`R-PEDjjQwyok)CMbrBu+zq z2K{;Y3=E8l;4&)!>Uc!rv|kHLoDdba=EG`q2w!*sEO9DT085-I zsD-8~xX^qIk_IPEHL!Mg;#5Z}G>xR4L3v~W)Nu}=2-cT&Si{7iyJ-~@gYH7mNTw|V zgVDZKki>ZkOlL$~TgAk{_8d8JLXChYPF;Q;kTIad8MqKuXu`C>qYyc9LQMe80couP zMPUdNhXOd#K&cZZi!ljw1u9bja=d}`v^6NH6Il|PI%^k#QYTabk~*P2K~9}eA$aQ4 zk$%1go;puJlN6-rJiZW=I-wfiUW2ItrB0}2pwub67UVTV>RbRd3hYr(>V&EVrA|Yz zT9niYwFQzo_k$Mw?FW~LjJ0cFsdGP4(Rl!==sXBk2uYpC`55%4fv!D>2A5m`P-78Q z`t|j&)Cp1XY9TCjLioyyV5u_(Y(hSW4^Ex2;G%Q+B3SB-LoGVv!A0k1kTf`TCV;iW zQ)ePl(U~LV49X;PppIJrir`GC8|#=DbOS({XEA6z6O=k*)C|S$I@DHjB zJkI_C%7LfO!+fhi#(+|1;$m3pglU0CAyVqx3^n1|W@d&%>p)QmsnU-@#W73G(@>cU zAZKSu>8(djmnf3ZblJO@iNWy(R2Mj1^1guNFr;*;0TqI$%QUI__3(5V(1ToRUSG__ zU}+B30QVG34JchgEd!;?)$2i?LZnL#s8R5A2~`V9mnXq$k<%s2S&(#T587cN2QCa5 zMK{3Gr5sYJDUVcYDu5M2(xn=uH^I^+M8&VguyhIG8!v&S zOCGQZ^&mbtUGjoU&FxEI>5>n%)Z_=3n#@aKGv@+e?eKIdh*WCINIQeF#{{V37(k;D zlG0@xLGwTBnHYSRg2prLK=VKAA?cD~1BlLu;Mu^$@C~X9oGu?gIq-C;#P0+$29z!f zm%`E|Oba{;k<#TFs0pA_Ghrhr3PYGU;8W-OVX_#d<`Jlj0m$)E(q}dzr%n_}XzHB1 zl!?LdB2*VRb+W$1NSz8$A$aN(la}2CPn{Q_NeWVGK3@vj$qUs0_ZmzM=+qaeWuVmQ zxe4SoMCx1tH42tGJD_SosWTm{7CCjooCQgpX$A}ojMKq|A>-yvu+%vnsnnc-RBFxy zD}QpEOkOua4mzSP6*$587y_OfKBKJ@xiH+6~IUr_R}Y4>m(PLXDsv3b?@u(*ln|q|~_> zYQm^2FDXnUEp;27s^2@ zH6>m_k}5o1@=0yo0#BC_y~w5J_vK6smg-Oqa8JS1fYK$@d{DZ4xCP`XM7lJ98U;_6 zP_>|R`5&wnIbFh>1xc5kpq*uv;KGp6dn+tmRw9*}RY;{~HCQ1eT{iMF=&Lg_Ffd*O z7g`2T$0O3^((SNx2~i;h>ac@5LJ)rN3Rt?l1U6wlh!0Mem%*jx^%bymc?Gr9yb3Ng zl~=;jY#lV;7J$-sN)go(smatU9JYJ z&;oVX!RZphPhJH}mutZC>p^^Qx?BsM1$w>;mM+(!&H}9m&jJ~*hNa65VD0d9xe;j= zXpf{bD0}2U9j9Oq+GinYw-dDgXFC%^?P}1drac3L(V^{-ba@U;XGGlE&ctvIstcSh zcR)GtbXm+N05S%YE*Gwb)#flQ@F+w|m(5TU>NYboXzT<H=3hJn(h;4YA-5b4qYY7{J8WjEzArDc7vi2k}lhz;+W~O8!EE_p=S@p&H@ zp_Z57;PNsUBn|GBM}W1%(`6)5d6^>V49XrpP{%z0MR1~I-agR&pFKqQA*V|eNocx!yB@S(5~>TFF84w?Na^whR0y6f z<0M7*!_%cfKXQ3#z5%pf5~>02DVQ42eo3etC|x@42YCvSE(JfK?l1R%ss*LXc(7XJ zbP017BwbzrU0d7>E(RG_?}w$!UZnD}52?KD2P=f6%c=Yf`fouEpTFSZ>I2kLM7m@> z1WT6?6;T^t=@P=9yaARj|A9@o58{K<<$rK_DZUYwE*YQ)8bObhVFVv&lnjyvr%NX2 zfkx0{Wtfo;G~$zS24xSMcaTiu0E%F48McF<{XhGe7}jnCjb%D8Fc@|1hosACU^*jW z(S9a|c`#j|{pB4{4m@2>L`QLB}@xE3X#%fFw}%)TbLO>8~{ZjBweOK z#WB-mHdLkn?U#h=0;kKhP!3YMJOLGgr%Mi*wFlwpvI8Yu zx^DvQmxOA7dkUrov|kb`2TGSW4}v^}NSFF(>CytK7L+c(gViFZOPI4D>GCR9-5A6++UbIv<1nS!Z=ahMi(6e6WdXP60_nHf3`gQ5_UF2kVWnCUVaDsutkY%a+!hmq4IiX=2$ z@@@g`mxSs9r^~re4pO?@0TqI$OEyW@Bk*+j03}_9ZvpL>gld3$3Z@3MUlJ+@N|%L4 zK%PRROCPj!sR30BN|*g$waDob<}66M~xg`D~I9-}=g{8|& zsOj=DI9)b_q`~R(3RpY59>0pTU-E&3Gbnq=e1N1&21f=4#=8<`$3XjkjxaHt-3l7T zbYx&K+I0kyE{}ofjEGA|m>BAyy1?l&1ImG?OMX5MkTIZi`Ee^OUBa}$qYx=w8beL^ zvx%8O;TR|iA?eZ$Dvp^hy`eG&AZOo`=sAX*E>R?*=~8wZXul*>7dTz^LODq3as^Zf zo-S`m+&u+$q$p#73i4RBAv)PVL&LZcm&E;)~bJcUS?22i75?Mn%$IiPf@ z4pxhtE@94sq)Rhi1_s9O;7t>Z#m8ak@;lP*iXXg)eQ7_z3L)w8KQDuR06PN%<6>~K zH~_U2kuJ}kgr!S}imq+2bP3@f-UdsTOTZ@ZZ-*`9SPD*;?%QGMav5s6Tn%BBjeM63(FP!SfN4E)zfzyiwxZ2_^%ip{UK*oU5CF2fQx`b(gM3*Apt`{6vKGogN|!UBLhy9CPNMWAJYBAU zCMHO~r+f!!za&%x+*2?$p#752Xa}Xsg(pFtLZr)dwDOVzY7Qt}?gy(yPM0ueLDFRz zsOOR=tOgn&=R5^VmwCdV+lu517#Q<}#g!Ns6!SqfNj_K^Bvlp*Gw9pOGcYh7&;qT= z+yJ#w6B7RnjB_~7!crwf#jG8$R0-i<-T_OM2f-%D?}VkwL*T2D!a@9~G6n|5!&)FG zoY)CVrAMGwCpkb4F+2*sI!R;~6NB~LI0gpBV^C=i^M246*m1CQ97r0Rh);lRW11LX zeM=d1)E$%yZJVCb0)+&`i?_6#L0QY@6U0vkK#AkJ*5uPn47#sQGBNDg#l)a{?<5n$ zgI%COP*CFja*~OGdE);12!>Nk4B=3H;8a}zfeZqrYO&q0RNV$u1y2AA zCPE4?1_sMusGJ{kR;Y}@iZKvm8R(o$dqoDgeY`L$ZfHF@&BPEKpvb`B13G~d<{-F& zDq-*`oKCx$7#tI!#)A?w^x(|}P$BsFjOVrV&Olq#4WOY2P>XuaZqQNPP?d0ZGr-*q z_WcQ{JUk~{(W*ZK&j|rg#gLqE9%SHks0O%!Ff|Me?%$zF4U`i$o=?LC0nkH|XC7hqXuAy~zy-LR|! z;cM@KWt~M}`En2++yGt-&N}Nsd~nuT0?s-Ldtq5;DKu|Dv(7Sb-pBz-gR{6UptJbdb z@T{W&^&}+gXn+i4{{jhfxPdS=3=Hn-P{Tl3=goQ4tTP{L)^UdF0A(Gn3m_fHStkWa zRs$vr&pNO`0Ob}(m_I-q1{E#PZc!!PCrk_sjM*1pSw{)n8dA<+U{vPy1(jm@NuX2A z?udbE7y+oi5UUDmFTt`2M1?nKoDJMhgz#JT!?MX;un8AHd~i0oCk9&e&v5{jE$)j! zR{cW{Dt{mbTJ;|Sk_M;yhhXhY6En=g2Qohro5jGuFz)~p^km<+V$Pr(n+$cJ0_akh zS7PB8nHY3KE-*2?IslrBaAIIEO1J=O`@~olK~+wIrpJjfpoD0!gOQ;dDg$r)ycW9x zGRJWTBLm+-CI$z;9gGZVyc(mN$mtmQriGB{y$U$JgLFdFfh2=L*+n8 zE$|X3;UL;8E1*V!+tC>e?$J=SphT7rRtsvd!AfA5vmhyFA?WCOZ2@q~*?9?;aJ`AxD@Pbo(wbF;pC3VlV;88ZEg5N|g~28!j<1n0fO6mkubzMw$Q)3?d+-RX%>vT`PXxE21qlO#|kVAe3Xg7@g`K3GmC-2@->tbk<7qg#Ry9LR*Zp+1`G_~y9XSg zK8Bxz&&b1dm5IRuT^xE2z9lzQJxcz9xfvsWWk6NKD}`*iI*^Az;duDl>4*On}P6Pm(T>JAW0PQ!}87A(evYG0=GePz`VcVQLr{+{2+k0?Mh3 z*HClnacEqEb1JxCpBTX4UJlg(%C8Dw9U-994b8CbbC9I%Akv8DmLn__K-t+5<`EEw zL4_4`JjOYGaNeGJ4VJgh@n44K?Q{H~j0MTt=fTP#dHV`KgZ@s?IQd)fK=THuXAybZ z^cE~{LsVECgXL`qzwj6=Z@&YZuouJ!=k510Aj`jk_~5+#K?dZ6%Hy!S{SiF!4e~uC zZ+`+0eIEtMgY))husl3(e*sJLpMcFIeg#_x&)eUiTxj0@E(6NjxhG(Gn_bo!R7$yg zhh$O(ke69xm2NOG=$2h$V%Q1lCTCq^Vz_$(RO2`^Fc>vlgXHa=YfKE+pxVGG=m3-h z&)XOI*+Axi(xMuu`wL2omM|^wWW)X$GLyl;U>OEA;RTMo9S@Zc0J)M?R^TQyZwH-( z=ItD)f(0N2oIJll)`RkP$4O}3zI2j_!KDPM3YNDw-GJt8j#Eqwj+IbZaNh2QasdAkZ^pgmLr+(4KbP+tj}6+n6W?oHIZ z{TXZC7KeHpl(+wYb)e*J8zgC|TOb3G+Jla;Pyl6TN0>)I90nDiL75>maL3|0oo+Zh53`dOeeokHY6^>zT%vxvMccn_AhAu8scg5_-p|Jo^7 z-VOzupmZ8G&JZRKvOEgJ2j}f@c~IWIcN&(rBji!*?MQi0y{&ZyHqa0SmWSuPc|2( z!0H&wZ%|jT9|E--G3)LBP% zRBxM~V`6X-fvSS#?Y=wEyq$ZFiNR44Dhtlr+E5PmyxjnmMatWU?l3V}po_!uwl`Eg zO5TRK86$76fU1V)?PPhgyU@Dl(+Ys0}U)gRl>v60UoB{n7IIzhv)4K`S!c; zyxjp+49VN~K?dH3YJeLEQ^UaEJ{=kfpuD~FE+{J^TG;Z?avHVX-VW6P%G)=*5NV`(8x{(n?Cc2h2#CX=@($D{p9#*ijK23^d3&Y+=#)QDn|vlx-kt?k2Fcs= z1sL?d2r)1)`Y3?%_6MkE5qbOVLs;I1s9-t|%i9pX^?6v{_63{J4&sCBZ9fH&<@-T= zaNhP;0Of5^H_iGwXpk^K0X1(2DuD8K{{>jy4g$-=^L8*;`X)#koVP>3*1_|3D3lA$ z+hGczyls9FmbcRsoI#~j%P&YKJpjrS$qKjbGcoAu-D6^C26f()?lCbezX%#gbYWmH zvbYDy+b;K*7{q=4Aa4avIbXYB=)t ze5m{fkSkLZPCS6-ZJA5ZyuAjhzyY)ckdtRKNC7Bs2VH{J+jA~4F}Q4js)FV1tOwA% zed!VtgX2!9EI4nUhH|jyZ3U>0k@EJu2TTkW=;E-v{TgZ-O5TRK86$6dKuw3|?Pvv| zhtRzJ733jM-d4N}+L{hk2@lgB(CiAWw+o>1@VuR%ko*vyw;iTJ3Rg(pb^#e!57huS z5T=HK!QC9{2~ggi`Vcj5n?U0dHE)MQb%65r7O)PKyj_DNeF-9sl(%7_0LspeFpq#Z z3@VPGnI}&{aGPB75iDO@0b&9XxNJhH{~K`;0OuZ_l|3%iDL9ok69P z${$E3odC)dH=j{e4 z2cEZm1ZRQF0p;z`Yp}dM4XO&BY|5eOfq}u&3@Yb}BX8S7dc}sc86KLLUyavtN zK2QY$ppE04JpCXApuBzR8Z>W?3eU#t&^Gzn>!1aPP?hj7 z?SN)iXxv=IxKHP_&PUW zdHXBagbENJoVULzfh^wu;)C<{cO_8XHn|DQ+dq_0^Y%|AP~L6;$%FIuFR(m3Z~q2M zp8`pP>+L^a>)?6&FO&<-+y9h6d0XQaEN}BEJA+E8BT)Y(fHDQQ@}%dWMUzjN7)ow2 zG3efU%EU0^7HD?cje)`F!&6A!{_~WH;TBXIIBzTbgQOsM-j)_D1DOL#iw|$X^0paN z6+GE2gQf=t2Fq(u<5Fq}+e{2DU!baBd0XiPG;hDU&BWmN6DkYN+uTssV$a(NP+6qB9rl8W!2(?zmbbm3 z>QVAG%*_~iy925kp0}BmPk}rH3Qt~8Zv#|sTiyX3SqN1L4^xS0kg6D(w>LoL;dz@w zS^6bBZzn(%L-KYE$iV$j4R8ZtY8V*Y%c0=|%GC)`60@ z_aI4kL8OuLHY^lC+1U~15fFz#Wt$=c1LJLQre%Ei5|+1b3kpj@`rEgW^7b9DGDzNj zAjqKK4$>Q>2FlwPpq@qK?Z&sTybV!Ne+QPgA^aV8V0k+jYy!(&SbsZ24P?0uh!4)& zp=zMKec&!EZ-=R&=IwAbP~PUb2Wyi@faT$NI}$7%29gHn?I^Hy@Vp%jTQ7X_R$BRZPHMc@GxBg&92bA9RQVw=j}AL?{DCF`v+7pByYa}85j@M z05=e(hJnFd9O?;B-qw7Jnz#2s;}SJ*n?rSg^0o(92TIRCkI zj{OMB+YlApp#C>_Mis($c?iqfOkfixfcW5gn^_5D`3VpooVQuP^|sF=Sl(s@*UN}; zayD?qJOd;TuD992^61B^WHKs^ne=Glio5hYE$P9)V6@^bByVqd%fuiB)dtSn2~Z9^ zZ$A~#1DOL#i$5R3@^%?i6+GD>jire}7f2MIB!Gv7oNiM_Fk|F zGS6W3_C8~f57$@MK_Mykac!1)8@vzJTWKGf)KqAO)N}|3TJ+^7b!~H$ZvYmynWg@4&-A{cy4?N&D(chg66lOD&b)&01s1e%uImF!}In9ZelDR09< z0hFB`VIBc-7*vu$Yi%2Zz5yX`G>EbYQu(EK)36)bO0`VP(8Qtv?X+fZ3>-tLBS zu;=XuP+6qBee64Eej803mbXts#ZmG$%*_~in}Zb+C-A)eO~dL3G(2tJLGyOzJJ9?# zR3$u2e?YSg`AkkmWfbJ~(ejf%Eo<_prPj4bHoWHhBy<^V)oX)!VUPd3fHA153Ao zq``SR9&8;vZzn*x(7c@p&f8Bu!18vXhBK&?dII%d0Vq@CY25q8#Gq^RlZnCLBNKzJ z+D|5ikdI6ZCLmcOo1c)p?eUX|;TKdJIBz?!LDC{TZzl;I0+|D$MHxAT5O^Y*e&p!sd6EI4m#Lpj*xC-mfu)Gb?QSup+SjKAj^M%_~5*~Kpj+X z*L;EH?S<;7_4XolP`!NuBoD5)7lY;Dd3y<1TIefm4s0pdI(Xh*2IWHY_HuPlymL(?F5h1!hCN@I7<8HbGBG^(3fj%>&A?zJ@fVV} zRsJ$D%z|kH?f!ZI<-qf{kOla0V^c;21{m6JP+eeKG8sC)s) zm0Q(U{D_Y$;dy(F`X`WwK;an* zYLS5QcH?)@{5Di2JWMN~*%g|%3!w7wyuDG~nSq(X5M&Y;^9iV8NZwuoGO!-10d62n z4QPHF8YG~+UCIE8C`7$&fi-W3Lv?`i_7t!Vl)PPoB)thDjg+@xp#aLxjxdjaI1DPg zK)1NKiGkbXoQ%v23gGPMCKfCW$=hyXpp2!c3Oabk9jpwJx4p#}^qY$q7#La1KzVxt z)U$}Zy^;l%w;?Kye~0C52%q5xEN`=dP4EHn!Fij_3}kr=h!4)&>}DV*aQuYjZ4NWk zyv=C_%G)6zd2rt50?WhmHaA#$9!MHoZ}WhygXe8tC>NTy`OH9h`_E5U-j*|S29;7O zoRCy1;KRVcC~Y>4iJ3w76$3Ma2dLt`$H2^x^NWeW1SD(pg@KuYX<|eK10yqo6;vCz z-fn<$;Cb6qtO{ffCCIFfqK=a#BIa?fg+a4HuY4AFvLTyuAlW zItwC=l(%7_0LspeFpq#Z3@W9d4e9FO#x~ZUnc<6_}VAAZt#Wz-?|s-fjjrx^qDC;Jn=emWSuV90z47!Urm>Cj46~#0TW(H6lX#$cp zTEhX!+dDXz8LFV#z$xeelmpM(T0%x3b3keFEHg6$WCQLss494}LCV`@P`PRxdAlAe z{{ZC5Y3erI(7gSVnHjPVtOu&V0JNfzlSdt-0F<|#SePOEzCA0H{1XZ% zYJeLEQv;gchI#^&w>f!G^L8WFyloEE0m|DNU>zuVI|fPG4I+({w_%|G%Fd23kAOG~ zD#td&fBw8L6&Q5`y2-}Z%c48Gq{*QRl)N1Exd3z=_E>Y|4 z>rfq_yv-s2(t(n<{~$>#L8OuLHY^lC+1U~15fFz#WnCl#10xf-Bh6ST0L$AvZ1LJ!WP~H~chopT(-j)=BDX8GoY&BdHbJ!1wn--sNS~aV`i`{hpL2!slq%+RSeDB6QJ_&yv=OdFAUGy z4N%39yqy3ta6VK6+(4Kb(EK(uNI-e}pfD(>BF55wK#K{~yj>2}0m|DCz&cR!_8cVX ze-LS;ybTKlPNq7cBqVB{bI$=g06 z%nVUbZQ#7U0Lp>q?L@I-Aag)@yHF68x3@u6!IRBHMBWaA%00%Bx8tGm0U%danq3fs z*4uLhp?Nz8s$c;~0VmIHkOENNeg^UesK0F>#LVDQ0#yae+eKo~yj>;4%-~oFl?CVR zZYT$P-hKd;MatXD#F!Z@(8XbS`!rNNO5TRK86$6V2tncmp0^9kq{N|ldoHNd0_E+K zLd*=7;!u_FF#Q3|uF$+~0F{U5?NYOBad_TNm=7rkAbI-@$Uu9j2DpJRHK6%zXjTB_ z?YZKpdHVv?UevrT4)r!DZ|?!?K*`%SNYb|;(nxt577C#3>cAx5JR~b~soWByUHHG3Y-8?ca6+=ZyfUXAyb(qBJaTLsak!!}2zS?;#A! z+si{50$T^q+ul$vG;jNW^R|m9EN{n{IfF{6Cs6-=0A-3uvwf1x47x%R%nTDinUhU| znPH14GlL08)<{MIlD9P^m>GUSwSn`tgD@m5!t-{d*dmZQpuGK86qdKcpsL`><{u(& ze}fvwbOe-Suy;O0HlDEXF5m$C~t#uFZkS*Jz~rZ zE+SA>u)OUj1-+=l2BQ2-qwb4u;=Xts4P<6ZjxeVus|1w!dX&5kb2CQX zUIA4N&)b1!-#{J$g{LMclY{bhs5mo&<#wn_c$hwbW>;w5z5tbn=j|{vcWHRu_E>eh? z!$JX+ogHBw0dW{q^guhJ{lJ-)kyi$mxBcLG+Yc#k`-7E1@^-KogZ^633J7Iz-uM9Z zEFy2Om51eRh>F>udK}!}hVZY5!}7KY*aQU$SiP+Z&f5_nJ~(fyf%Enq30U4%2j^Wx z-qrwTUJXfDy{!qBhv#iAuyhef8l1Pa!Pdd^whojF&D*-*ynR6umbY!qoI#~jiwGoN z9sp$uE3;X$%nZ8kq?s9HK#A#*G&6&P6f=VfNY?0wG$e1c$S^a^f@%Zj?FUc}JZ}ez z)q%_brNvGuSl<2yRRvErl3yWfnHU%>r$OZuapdj!Q27raS6Z8O$U*b=Rw-!SUISI& z5CraT7lRal^7cQFH$e5ahcq*T%ND3ASl-r>gXZl?(##BwJE5}RynPzV!JfAjL?NMu zl(*yLm>De4#bJ4y8>${9Z^PV-k+(gds^NLt$m|lxL!j{7Dh=&#Ka^%>undQ)goo(~ zXm*9>?E|y!Ws}C zTyN)?f-HXm;)C;ct|_R$ogoX$+j*v_{q1~HP=9+1NFH2o7l7sAdAkrS{SPD!&f7&` z>)?627|Mm_?GjT^e>+AFmbY6>ok69PiWnr7P5@zK57Q0M>0^*R^h3D-$)Bj5Fye+T@Id4w@87L3c05=e(1~k77 z%?hBrZK#Zzw>zNrqUP=IP#vJW9RSvWlDB1`t_MlyL8OuLHY^lC+1U~15fFz#WfSPm zO$~6SWjv}3%i9|8ysd$hw>81aAbDF?j6uK6hJk^x6Pz~;pq@qK?NoJG-iD~ytN_c~ z5dIqlSl;den_!^`tGBzsdAk6_2j}e`aNho+2+P~O;Jk~dxBI}E*FgzZZ})@c;dy%k zSh@=&4bIyW!Pdd^_9Q45nztu|^Y#lRSl(W2>I^ESjzImF0Ll~#OoLRJ8FZH^Gc%Yd zGc)MUQf6j|0C7OFMjMnNd3%pCGs7*YHgMinkbtBhc;40)GXt3eN{b7XVR_pOstTTL zOc3?8=&&=ygk?Swi-Nd-+(HH^tWYH znHeniLp8t+gsB0|Z$rZel(%`+QS){J)F{-vJsqk8lzDZ)I#BZV9wccmh%{2(hJ^ws zJ3GQW0^%^JNQ36LpMW=bF)mbx|3h+2M zB5zlM2g1*Q-p5`%IicrBsR} zBwreUmily?=xH)D=r*Y{GaOK7X3#BDXJ&Y!&dgu}k~Qj4hve-U>dXvPP;KD6eE`aV z=k4dBLLhTMX;D`Lmbb4#Rl$=DQoUUUmFvWjx9g$u2_RSYn8;{B^LC^LG;jAn6>I=0 z;N)QkDFEf|MHczYSFd%iH@jp?TX(6EwdKl?CVR)ld%hyv-m52{oj={Yeuv zzl|mi%iFJ^rlI6*n42;3wg%J`c;0R{NdtKZ6rPcwfCc64R!z|SHdG}%Oam4}s$yu~ z4uHzT^LB^Hb}eSun$rzX#gM$c0%Tx3R0G^Vm>SUhHq;ZKy!}!OHE(M`jY7@a=1?7= zyv?Bv(t(n~F$Vom&>5+G;CinB>RCkImeq&lZHS7~ny|bL;j?JL@-{!%ga8m9oVNu`L33ao zAU-&63xexy9&K3O76RAHh`cQfu9zc0^5DEJ0+xs8ZBelFB9Jt=-WCH}2hZE$P%boY zOMvTb1|3-5)-ZJjl~OFykW|VL%D}*=YRaX{%%B^m&CKAV!_1%?q|MAwpu@~y0+Kb# z(1zsg5^ZJ%DX2DZ-cEpW;CY)@>>WruC@t>Pf#vNos494}L8`aKpmLjWrfq_y!`~M10`?&L6Tw$XP7eUhCoc#wX4ZRcMFIZaI09I@N1KS3_6XHLV8v!|;oIxMd+paW# zPm@EF9{ zW5mqBZpzGH0+KblWCW>c?-?;OWI?rov*iXT2VT>@7mWaE2c^LPQ&>%V45|vAYLIH$ zG^pH1K6matRf)s$V<53U;lpXoam>FDJ zpsHXsEsH6%rj0aXW^n9;%7Sa!*-#Gln)U-!7Aa3#m@+e1po_!u^lhkmlspY{Ge(}4 zkcY$xJSQ$PSp)J1C_HP-prcRCkIPPT&OZHS6@pu7&w+Yr9GIV^8Wf=ws}@xfKClnKc4)gV5&6)g?U z+xix;ye$LHyNJ9k3(maNAbD`!mIKSf^R_%#`Y1>m+=^BJTL;hEicl^zZ!3ZGwz4HG zZyT97gG#9{Q2#lEgEq061XwUL=q@p1X2`c>X3(8s#>_C;l9|B-Bx|(J43f8ZnK3hP zDL|4RI0XejIq%IU~&vl`jCf z(!j(Hr6|{#u+lraNMFpw~mbV{RK=bxfD`o~q zO{gq5Z(Bn-*z@)Ts4P<67O`Yzus|1wZ!jr=q znzv1?nHemPLsi1VGy)!`;Fx&;m51kT9g~}u@VvbOsu^+JNZ7zz(f367Y~TrJ!*Xb) z0BJ?HK^4Oj4NRVa!EzDQ6;{YO8@Uy|7pfouqz<2Y&R3YWaWj4$V7U<&89O?KHYAq;7LL0XRP;bDqUA)16kZVBMPRbsd z?cD5{87%FgD&Za$fCdQEqzI@y+@ur(Pf)0XOv;3rG!RLZ#wj7NkFL}1r}9u=}@U|>*`t^!@U3Dyj$V6O-<=+}U@ zRUgp=<-!Tj5O9GcI0nW!%}%gf2vM=a9+nFs{EzmqTzC|0f~^B=dg+)Z$VsIjKDdHC zt_jM8KOJDX@PsBP7a}UylbWDxIMET74NrmP;T7y@u=H(^G&mcc0b2*JV9!Fi&dLHDsOGs9OWW(E_GtkF+f za0P3`YzNAY+w1lC?U)$?otYUTrR|{kQa}Y_8oa7KBQzhBpg<|>r!%aoRfDR6r>cS# zkPHh=pB+$n_&stbg`UDpQFVctG7qW>Zpw<+uw^FhrchsPTFK1t*B+cUnK<+q+{2*a zkw=*sG#nt}H4Ijah$`KRF^GBM_Id{Q2B=bx-OLQ$5T%S(ptfN#1L&$K4m}1-E2s-V zN~{<|8I53FZpg_H&8RrOX>j%0(3yob+!vjG33800>Iu~f6@xz6g!EQq&1A~Yo zwEhoug?7XppfkZ}nHMn)bFM`i{K zbaANBmOr4Tfl4UIU1XqZ070i<6hIvVFJ3Ndt^kDqC=qnKLJO+xuFMRU^-z`YD7*nL zUcl{x15kN*`{0J=Pe*t$uK`UpkoLhXkb&o+8sG-P)G#o(--PC5PU>zv!13732fTS}Z(n#$CSSWzXC`Xt_Kpcjs383Yb z_qjo%Y!9FgW`TwJoJ~&54AB(~jQ6=gg#lwIgMJrCc^>!{#}82Di0iYzxWGyyh>E|i zu+j*^H*eU#s*nu-0F;oc#VkN+7*su$xILEKcR8pWC_QdLTjH-~>t(yzm5-F;ETgD1fN} zMFCU}6a^M;s8PW94N{wciu{S7D1fR3ML`T$EoKxrK-GXUrF$Th14^ikU=5(yVsM1{ z01`E#N(>B)t^!Z17#Su&H6Ws9pBp4vd;$za1-BP0 zY9M?UFIdzZ1e-9y3l=qppqF?;qvkO963-JLX>imW0V{_`%~3g!bN_k4qUO9DIBFEs z(W2&*+;Vq#)NrhX)bWtg+S8kv!BQWp0Uk9lH4F^y=b*j=Ma^Y*)Tj|hi<-w!wVhC`wT<^xF7dr=#-2+w*kD$HsAm_#Vz#{0lJUD_bKph0nJBQ>wJmC@Kff7Nhe3%(5??W}f zBM7F3fx*2FDhG<75>HSVBD&kUXc06SsumPMGr(#wBWM9s4JZ-2Z-jC{5wsVq0TMwl zA3!3=AeMoFakaoNPiBS>Pz{I(0*QhmXf+~&o`E7r3#4fx_{u8=4M>7TL=ZSaz&Qw_ z;;auWf*^c0Uswc90-F%z3yYx1&?~o~5i~^sRJe74q`^68Dp)x@f~F~eoOi$%7C{RX zz!B5{br3v)W-Gk(f=5sXN(6EHF*8{9Lp8u72&RUC!Cen32Z|t3Z`25KL5m<~s9I12 zS%TGKMo<7$4JZ-2Cqg-(2#N!1fJ6|?2apJwz{$YCcv4`SG6TZ`s0Ks?&Gv>AZYN<8 zq~8TnZlnmRcQ!zkBcg`g7nXM*D)jtdQ3K(p`@y2d7;M63KUmb5D5BImri!4bc@2^V zM~xX+IXr626+zBb^oK=_qarwJ3^XBW1s*jviY-3ys5yZWH97vEPB>HpJZfNS7#Q5| zL45~`nhidvQ4@(4HJ_nsK~ZxBtQIqB7@&56lCQfUlmm*IpI{A;sDb$a5;f_dyB0nR zh$}NN1VA;g!0UK3Ur5w^hDS{zNVyyM;Q9opazxZD0!0Nl*FaSC`@^CJ!av~;iyC*Z z2_gZosPTXvoDa=4p5TM?eL&LSsPO_ThewUK63Dqt0kEhERRTxN1E_=GQ4^pf?+1?> zfmO)$&e;HF2Fv$Q4e+ResbOGn?}N&LqQ=_~HEK%GqGmBvEhuX8!D=z1W&>0WDEYb{ zgmOSpGX<;x5;ZU%K%#~pbkMS<;O%lo1_muiszXH0ML$T?XbOT7HK=<4Y85kDg75hf zfGS5sjcoud*FaQ!0P(@I0T8}sAS`OEz$TOi!lK4n8Ds&xwPpjpXKXD<8XPsYVCC?r zu~P;)_i-RBYCM#|Q8NMRAb8X`EAR4$M@E zs0KvTg(xE2J9npdb% z^BNpAe8I5Bz#Fh~c+|XA207Oy7#20(AyMO?4M|$?sQIKE8VHY?87NWHAI!{P=?~Qa zj~bX71_t+cP~U-~rZEr{+=#}&QnaXHhMEJ4nx$a1m{B7DQxm|<;I0VefTHFUSOX+# zU_O9EO;t7n17m~WtT~Jf2~Z7)s9_I+L`?%cYDz%L7pj2jngXbDMAQ@q!=eVFViSlD z&NUGJ>tI;aECQQg9s-M+#VRPdW{C=@uE_^UgLBPNuyT0REK>nFcWDSLYBs2VbIk{+ zgWyrKTIE9!JZf&BM9qf~W(LduPz~^?fvI6&aGwX214WHYFlyB7MvI!wP_>|_aR95u zjG6;bHK63{ei6z6MNKML10-r-K7d4xCn&7<30j?JWDwASq&h^@EDDB1%|3Y4ID?gg z50O!TDn~>OUnneU_JLIhgZSX6f$)7oVNtUmEI%U@7BvT;huA>tnuFj&Y%YML!BKMv ztQ;OShgCt&WebBv%~@4&)GUBH2p%;jRJ%jqQ6sS$xvmKeV`i{i57hvV8kiae26sQG z94KnGhoDBy6||^HhN=Zc&26w+%%~}VssSZm_eLlO6g3Q?Aj2V11M>kSYUY(QFfcw5 zeAdRuZ~&?S5jD1{4DSD+z5_)~P#7rm5Cz(6w5Sn=ngfcOGO$|As8N8b0VQ8| zLnsFnHM7AQAW;MJ0VHZdH9!~t2;B%{W+;GaKtv5l6x6$s5CWAqjG+u{9BHBq4Ep{c zP3qvo)f%9h5E0}R0gE7rimTzU8VADXi-1Lt2H1qC2v`JZLJxO?MvxZxaJNYyX>i`r z1}le0kd8XYc_$)Z5oD$gjvxj-vA zcO)!on4#y~L!*WTe9rx6kTf`ISi#ESQNyMIa;|0+ENTQaz)`aS>L7UD;nqlsgh$N{ zl&C3*0&PQqYJf)#Obr8rdmK~_6g7R3px{R29ci?vDTb;AMa?F#TFj_vfT{r{U-yYn z4k&7_gEc^+2Id1u)MS8$%nO7>qL>*jKs6wu1|$l~I|Z<)VdE$R<((vura9p7dH~gg zh@iG;SOh^-Opk))9SHwI6fA<~f=!T#hDFdk)Cignj-U{bG&q75fR)1|XrTtkd0o-4 z2wDw^pagxiyt7P$IT{{83Tu$-ovYEHrIJt$@CbsbVPJ6QgUW#-NIx1if=tmONExaY z6hXmYwU`lP096A@#O{tz4k&^uz#1SC1oHtTg2X`=Ma>l28qLhm0M&qqAdo01f@UHj zXc;Ji1VNg-!PA-(pqda7q#g^4Ac%?|pa=u!APC1x1Vw3rbC7@mS_Fk^E{TChPy$K>nZ+_QSjs~+z#|Bz zhJnF-AJliC2)Yo18bRJ@5p)@<78F5Wz-log=mAs>C=t7VgmOR;BpC}b91=k=A3!4L z3}~SFjZpGtMg{|@OA!$i77Hod-oPSA{}@QQh8Czf?EqDdh?+xju&9Bkhy+CyIBFpL zNwKh~(FB`tFBTRxT3RSkqpbyMPK(9C8U{LGZ>)PO`md8bVn6msB(ftVNr zgT4t!6RS2Tf<8bsAtER_0Tw|J6}vzY296*I|8pEHg4n<&*vG>nh+P|G4Lt8~XoDiC z93&0SJDgzU@Cf44203qSJS>7lw80TnV2GA?__ZIz!z1VgN(B9g2hHg~HNYbXrUo>p z1C;|s5KjVX1l6HMkTFy(D1r>YYB3|o0jdU+h}{FB98d&>f;B)Q2<8Jw1POqKm{$u= zN?>M~0M&qqAdo01f>t9UXc=f+nhT_9J~+GuHhzC>6At%F36f)QE-t<-Kzgh!CZTIAX% zG7&Vh1JwYJAeb7^%nsCdpa@!` z4UhtlpG|h1Ij^`AZc&}$$^!_BS>BcWM+mBPz{I(0*Qhm=o=z} zj)5YG9i-_pIJ_8)Aqf@{L2jwA2!g1%3(8U890cKuCBq`<3fP3CWLN}UMU9|q;0T%p zk_Jc6b+B@H1l`a9IqyO;EP@_EBB%lCAb14b)j6C3k06G1$T>(l1vIk*)c}tmm>ST` z4pa^lLGM#gBj^TN1UW<1f+9#b6{Hq3f&!pwK#ABr5y}BYkOf!+B!XZ*fJBhLDFXwe zh)8KFGs6O?21EpbL_rZGA_7X<;5tYRR0ny2H2LX*a?l2-CPV~1PlH7eM1@@nEC)gO zj}mH=IlHSjtpP#2Vgey6}12|-}x@CXXl1v$?!6&69Uy5JmSU;;@o z@Cb^~O-X}CPyk8<)ue*vcc2>J5d>2Mn%{x?4irHX(m;WZXe4Y#E8;#w)q)~u3s@~? z1TjGE0wrR1K_~|lK{vq~AQ1%f0VIO>^%)o#okYaanHd708W0f#5(Pz&6C#4lKoP_V z(!>tl1eXBSgovPy3|Is~RLlqEC~yvf@NcBTB8UTQf=U`J2XR6-;X%8oT;NT3F(7Gh z4&nwYher^P9>{r<(qIuJrU#Co2T%vWBS=t>H3J?&Gf*PvZW?Ho2dV)cK`=F-Ssthy zD1r<#P$TF(S_CbIss%+*C|E6K1Z{w-0VQJhgHR4Af~vq8AQ1%f0VIN$L7O=8M7C!z zGccG!(jFp$K%$@s%0ooZG*A!dFKCDT0dRN;Ks6yENHYr-K@b%T>98CG;ajG|BIqF4 zgw}Le1RX++pu^w@+6$5fN6-<9=Q&hpUKQ%=?~Qak06*D(2Nh%cc2I=&IW}cqKM=8 zfja)i3^fN7LDRu%F(XI-ss@yZ-4&r6Pz3D(Yk))$%m@jA3Ii2!zd3Th9GO;bx^q>s1B+JNrQ7x1z0&ef+`I`&fA&=i=bvha0GpTItU&?b%rl;;1QI8 z5` z%rgQ<%>t-{;88Qvs5uWFH7ih}CNT#z?*r8Uj~bX7(7X>+4iq)(^H8J40xfEip=v=< za~Z4_GinN;YCy@?y%EX*Ma>Ve21wMvd;p1>DWJo*dPPn1nHdg1H6Wq}Bnrwqy|AcZ zbJbftAA}h~F6Gyz*RF1W6i$BPhTElEB~*Bx0;m0FNM!4akMtpuLh_lLK><*sU=ef}sumPMqD3IJm=SaV zss@yZ-5)|Zpa`-8Yk))$%ml~Kt&!nf*}0*d{_k8gH1S{4~rlN=+0Yc4srzVyk#zcH4mJ? z%Ha{@YyxtgWdSULd`-YPXam$i@Eqh}l2#0lpdHXqg)HA~DFDp@K{dc52&M)!2LzP^ zMbM;TP@p542YzTpTrpHFD1x?u)nZ0a15^zt5xY->azGJu8>|5mK`1{X>bnO3sw$~pnWDF=glgFMbHUI1SMFZ<)FhR?4|Gs`hgNb zPYXeFKu`_v2!g2r%>hB>KoMkIiW)&_Xc43gRSSxsaIjj;2r_`G0VQI0M<@prK{a3v zkO+eL01`n$aQp2VD1zpLG?{=;V448cgoq&R za##dGRB(WbJa7(z@Ewa_5o8KBp|=PYL1xesprAR(9DD-QQIIq^f-Jzw;SpqM3Uc1> zB3J}Dn}TzYfHhhK*_o~=gGZ3VM&vrktr#>11l0hKAeb7^91zrZpa{BBh8jUlXc2T7 zsumPMKfr1+Bj^EC4JZ-2e}r;C5hPmR5>$)0o0|42m*U8${9d46k`9*1gIKNB6eR0<$xk+Uj@i; zNCd%r0Er+G(59+%vELQU3=g0h5D^3t1w~LgB7(kwB8U&9sU3V~_y?#aLXgO%2*{e!;1bsk>AdWK7Ob}E9Jc3|qKr=y5IZy14_j1flv-8g5tp%AQ1%f0XTwG+8h}e7`vH3J8v0xRxvXuoC2-S zfe6SKloWv^LA?_N@Cnx4OrR}+@=3*2AaPv=1_tFK2F4z+b_NwEQ1{501JwFv!i zmDL2OKm7P0tG^lNu+3v;u+L>+d~F4?O1q4i!9E4X^8@j6QMk@~wHX-ZK~0$eN?ZEY zC3VaUhNrX{7`T`j6tutw&o{gV<~sz#_>aK+1~~s6n14VBDsKWZOYfI90|QSv=wSLN z1_nJ=9R>!2a%Kj*4^a#Z0d>p_dICBO3=_(k8T6!d7#MDW7^*r93@R1O40a093=Fa$ zHAXrN48;}93>LQTkklm%JyAi=UWb8US_LzMp0^GI14AV2m6PC;d3oBgI=``1H+a|W(Ef}1qKF~3TRL=FrKzrU&G9x>!ZNH zpjrjm1FXQn-~nQoDKIc(SAou{$f#*m%nZjsjIJtXhL<2lT@^C}Pc`U*t!M@Yy>(U03@+8o49>0c z3=F5BCN+Re>Ssy@nY2cpfuX9JnL&4fJOjf#5M!D=1H)mE0U%|1$K@Fq%xah!^e)OX zFifjqW-!_=&%nURJmD^6l*eQP$eO$I3=EHJm_cFV@L!&RLEi%s*6@v(6Peb4Y?D%8 zV9*6I_!Sr!0&1BVEUlm_7p?(kQKnjO5Y#a+Fj&e#rC=K|L4pj%prBp=^~eK|N6uP- zgSZ%EtfK-0!*mb>6vT&XK@M`vf+_|XV_6I3fVPHPF@|#1GE7`h&!(iz#lXPe?rIBh zHAtmt9mGBL3=EdRQ1PX!U}B&>;~?i){)8s|1<)7-#a##|^8siL016Q20#ycvRZt@Y zK+}kp*0UOz8FXt^85jiWm>En!f_j~*3=AH1%nYWJRT&uc=BqL=ECnUs)v62(??8<0 zstgQ5^~?;;4yp_cyP?J8pW!DVyPhzlAql9ydYlN zeUzC&rXK7KPUa2{NSnlpF@({8!BGmTMtl`BgQEtNqqGVXh;|oZ7#Q}{Gc)KNQDtD* z1!7!KWnc(wU}n&}qsqW=xPh6$MbZ;uhXBae3=9nYAZ0IA85n*xFf&-HKxIGaGJvDF z3>4zWpi%6e$-uB2;w+{b1_n!JsA7;fXnT1L0|SHcLv;oQW2mzfK=b9?mb&lM85m?5 zL03-2GBD_|X)rLPG%_>j3287etZigwF#4qq3Wfw&Fa$ugUp>mqVAB8%hSSi<0`CQf zDYaq@WHhj13<41m%v@iY8QgQBn*SYTW+;VecJP9PFjzApKm$3M6&^#w!Ez5&qwF!T zjZ9^r0N`Z42elWh%(+&Zfq}^m5`qCB-^E+^HZd~MhV_VE6{| z;u>uR2Ky#v2In8D3=9rXjT1l`=UTZnF*E2Ys4*}kgBYS}3=Hic2B#VW!v&BQP-Wx` z)$#zOMT|=nq~K8%GlOI^GlTB6DrSZh5aU!8GsCQAW(E_GGQGD|%nTQrnHlT^;usii zfL!qlROGZUGdQrxGcdG6jf9tgo*b(inHh9-W@K0-z}omUtP(xGd1cpxee7+;>8a(OAvQkkSNk z1}Nu)8v*Vwpt7LGK{Z%5kkNp_jnN(wxuEn6w%4Irje$V`DheuqKnZ~{(fSxDDtgoy z7|wt~ND8W;W-ZtrMneV$utpuII9y|r^?#Vg4!ZccdH0D7Sz%=?nH8w!S;Tlt|SHU#KfHZbN6~Hvw zLp9EUibIMi1_s77>&GyS4Iqt+p$cFc4WJsgLdD@Wrd!K^(jCZ;OF$a;LlwX@!b0^T zR2;4`!#WhE@dQZYZKwj6Mp&r6g^I&9W?J{bG`<08{0>zB(`XEJDyIV^S->@BSs#aK z6lh~+uoQ+WfN6w9hbmMYt})x1p_Q3I7vx71kVbu|0+>cKsEv+Lak$1DYfG5M2#`i^ zr~;TqbEw8xs5o3>u5}SiV+}}SI#dBnqXkrBB~%=)G0%E6OydHO#%8Djm_|#e#;H(o zxW;_zCoqjiKpN*m6~Hu_LN#uLio-P)Sj)CS1Lp-u<8G(|m_{q8#3FqlT3 zcF>^?Pz5lJ)=-Tvq2h3jMb`Z=jRqi%pP>q18f~B&Ssl?5K(X}+n8py0Mt-OQm_}Qu zMn$MN+{O}X#&)P5D?l2xp$cFc?VuWMq2h3jrPfw3jdMU6-JuF#8bSFnR*iun5-JYY zSY}-e(wL{lz;FPhF&V00#YRM71=3gw6^Cmqw_XF&_ynY}9;yJQ5tOY!8Ye=<;TkKf zpTab9bTBhm&W0+0X#`~}kjAx8ak$1xYq<_)23?R-bwC=oLlwX@g0dAz<4LGETw|4W zI80*zNaJOw0+>cnk^^ac3KfTIthSy2(^vx1_#UbNrV*4MK^hsIAPE3o%GFq(glU`s z(#Qc-0MiI611ZGjDaeEX#^D=AdPuYak$0?>$NbA8X%1oPz5lJphOPR z*aa1bYizWB2Gi&R(l`aG0HzTZs>`6_aE(pY@?FrtDFA8Q0963f2r6?xHXeeC!!AlKY_n$WhL!*fUCa!YvQPyujiA5**{BZ{hih!Nwt;C>0co^^ zDu8Kpgccp%P;t1%4(n2wMh}q2P^bc!MwnC6q2h3jo!0AM8goDz3!w^N8bKuh$f?ax zak$1V>*p|y6F?e!p$cFcK_vi4<9w(%Tw}MjLJu@>wtzIQgerh(1eE|Fjk}@ZaE(3I zkuZ%nKpKxi6~HutN&t|?>ripH#$M}5FpYme8XrOxz%;@F=QC6suCdShG)$vHH#39f zU#J3@Mp)qRLl35dYwWjX>4o~y1*B0DssN@D=0|O)I9%fdYg?Gc43I`sr~;TqSXJ&0 z6^Cn_Xk7-=*aOlS2vq>n=m1SQ$xv~)#!1#2U>Y}oH0DATKs7RPC_r`ALnYukCtJUQ z>AVWk*$Gtu*9o(8HdF$xbBeV}A2fJ=fpji~DuC;3fZDkoDgoCy)jA%gQ@)3p!SWzf z0aPb57Xu?y>t(16Tac=Gaf1q*ErjHCrsmCkj6}?0+>cvqOFFC!!^#a{shyg*vrgd*$Pzv z(+JC$)1l&UjdQKFCqM(o6{K+?Q~^vQEMsnlio-R|vrdI+%miuN3snHs$ix9lKIvOeg*SW-cFHENkNM|Zk0aPbExt2r4;To4(e}iev0BLN5Du8K(CD+MNak$21 z*7}p6!P5iMI2Wn_rV*B0*F(kO8kbvVz%*_EY1|1_0MiJIkke3cxW*OM3t<|sfHYo( zDu8K(CD-Rrak$2n*0*39e}FW8gerh(gjMUz(9un}##PqBlc9c;>1SrJa8@XQ-tN?y#W-kfJlFiW1-^!G;w;iaw$$;{OR5on~-{4IzLO ziA@3f4(29xxFXna0Z5TGsv>*1BG}LXNKpc+qENUZ*f0P{Q7fvV47eg#mmj2P6{?~- zxFT5h9;E0zs-h`yMX;_sNYNKmMGN7IVBL0*BFU*JL9hv~2-ZahDY8RVbONpj)*S~a zN=8+51Fi_x^#&>GKvnbxt_ar61}R#Ls^~9V5v)rMQgjJb5$`Wp1jD+|AVuF%6{*1$ z!Me&IMKaS+g1`o@2-YnIDRM+r6aZHQ>jHxmrJ*XyfGdJ^cR`A}Q599g6~Vf;AVnKc z74^au!Md>^MORT3&4(+3by-1*exWMb09OR-o`Mv~Pe%!YLvTf~t|&;63#y{aa7D0g zCrD8ys-h=wMX)X=NKqfEqR((eun4H}{Y6!z z1y=;?5`q*d%|HnPd$=N4_Yb7V9aWJ(ToJ6R2U3)Sswf$*2-d9wDVm6?r~sA39bm%T>~k)jjE^zt_apO11VydiQ>B%a7D0g7)X&Ss-h)uMX)XxNRbz+q8)HW zuieTLwkRo4HMQ(6Kur3WqQ4y-57`P%>?**i2I;x^#xFT3b z1*B*Xs-kwdB3PdUr05~4qSIt*6?>rsFd1)wUr z4p#*0J%ALIqAGd~R|M-RfE3L_RrCk02-YhADcX;!i0>~ff?+)XkfJB3iWK0AV6A9Mk!WF?9>>x!IsEYF7ieQa%kfOP$ikjhy zU=4AQqC=>Pro$D%+T0*T&rubvfh&SFuR)4<=As0_A-Ez~QyQem5LM9)xFT4y8Kfu- zRnZ%`B3Kg{q^KHI(SNuiSaTSpXaTAs!GEv_hBbXbijJZxQh_UiHFH6VUZE;7hbi*Z zg*J6TiomT{PnZ+~_c4A32C;d}3_NWN3=C`x*7y7w7#Lr$f!4loGB-eHbgkJy7kT7> zm)vkNGlW9sdAUI=Yl2{U^%<;%L3$^1JI!Ne;ADoaXR$68WME*N!rdeUntq1O5rggR zpU2Fg4YD_ok-@spmw|zCwpkrmGpw0yT?RTh{&A`CO1{Gz1i5}NvU|?j2iXM=H zn!DMPfq{_&D%t=OeX9UE5gjV(026ft?eOD*iZZ}Nodp;e7`dUM2PC29P6Vmtfr>W3 zL_caXFfj5$MIB(G6G4Zn@j*oyV4_wawfs=g0}@bkb3k`=3qVC1V4^B13=E8dP*De% z=r7Qo5Fw~2159)RXd|gGRP=y2)Z8Y}A@L$m(FT}kPap#WqbO9=0VX=dkAZhQ85n&21Z$^r~^zi5tORsprQ;g(dVFiArBQjAObb_3+RAP1*m8P zOf(&o=M|x%4p32WVY7b`w4l;su-;R_z`&?%v3?P#uz@X><7N_KV0a2s#mHbSCeOgY z7;149tf~RJq|UlKm4SgV3@YjX6I~5D_9z@G$^a9sXJKGqjDU(B5QMt?CMeBDLPZ;3 zqH92x7DYit9blp&i3|*k(NIwam?&rk3S$gZ^nd`=TvbpJ8VeO|fQfd4;x!H`>Hrh% z_hMjRjE9Ocz(mzSx8@~4MGx>p&FwZ~U|>vyiZ;MR^*}M01Qm6FiOxu8U|>v!iZZ}N zL5E8)ra(mx@IlSp;=;hdmc$|e&k+5i*v0xhY|f{Hr8L=8Z@>9e7t3@}kg&<$fbP|*WCP;=`+l~FEKv;ih+ z>chammdN1fE#LVJ18Uzp`r~i(NIu6FM^6Xz(jR8 z7#J9fp`r{h(Na)~DuId~;DVZ41ajm7#OReq6{$6-Jm+S1}b`h18VM3 z&{ou1sAvOBGz(N>)9-y#$^a7;2bs_S6+OTXHP@Pffq}6RD%t=OJ?q54 zz}N&8b%2S2&J|;9hKe#kMY(H)7#RL9LzEw(pc=K+;{7sE`O&}%v4{JW5Cenia;Pc> z2J5q+b<4XfM3;k9!ItdC#wsx|u&vm^#J~_M6vDv3wi3jQV`XMw;BjNtU}Ip2d+Nx* zz!Lyw$L`=@V9*j?!Oy@D`&^BIftL@=ERAGf&{hXCS8~FcXJZ%`1hl}SYhxK01hm1- z!3=BFgVCL6!1_qrrFmr_;1A|T{n8_y3z@XCwX3B*! zFz9rHnJkhF3_3kvrj95BgH9iqxx|!#L1z+}nZwM$pfd%`+$_Pspfd~1ED&H|(3uTl z#-9XT*?5gRO$^j1Tj380x_AxHWf|AOm$h&*FMx_B%n@K<;9yuLz`y|7%-FJmnL!w| zwi`?^B)a7?FzDKY44ny+VMw}I#=yXHkP)PQJxH_>v=A-PRfvH>4}7}+5s+XeXoXmk zP&@+z&r`6{TOd)8Neqe89T^z(!8bSjhY5o16Lx22U;q;u42fHk7#Iw|`}svyGBbcr zP+>@xac5xA_0R$d@Uk;72t&+s7hzz~bpo*+Kr$frYcM3v6lGvAOav`D2Q8r!2CX<} zNStZRz+gBHq%iWwM;FEN6wJOMHSY-LhKF#`k7b+EyYL82fVz(z16K9pu)FaaN{ z_X8vY@;*bNHUk5L3HbCQ(CSvO7r|iyW-+96q%kn)_=5~djW=Up&^E$RwyVYZ?QC;Y^TeF(7GhWT)(8WneJR1qmijHDq8g&j)F5 z1IdHLz&gPML(*nY?A>JqrM5XRd4|O2k_-&m;Ij<2!33e|KUFX=7#VjK<<)U1Iv_?c^McO5(LXYMo5=I zvaC5s7NiUkBk7Y$85ojpf=Ua&AdnQ;L!j_rNHq6kU@*@E3FN?KKs<(|f3^$^Jh#BE zZby|$JXgTLpzR1!23q(E_63+=$hfP`zyNaWK9CL&Lm2E6P=}Er(b15BLDv}yIh`uK|Bhidnu|k#N{E;3=HBMLDEOy((jZR7=$6Z--|IY zh%W<4KSq@X>rPV6Vqg$w28SredYH%aLE6Ew3?dj3|NAj8NPyk14p(zU2_ANioD2-Y zU}-nFG?)dpf{nvxJu?Gu7$_1fbr=|gLqLuxKvfA!EDVXk=?n}aSs>|txb$>{J?l#t z7(_OMq}RcvqY%=b!3+#C;5a%9m*$_w2#aCMWCjMsB9QJkaA`0L98PQ;pFpnMCd$B& zxJL&x-~&=AwgDC-U>4LrG8>o~ctOXAC33PcFo^8{sdT`iGV!z(1B2KZkaRpYX~RSY z2FL-JxOE|XJF810;v?+2=hN!CD^t^R|^IPjhP^6 zQ&efNL5a>>3=E=kLDC^`X^{J%87;Gnfk70UT#Mn-U=~<68%O0vW(Ho+5fX{a4h#%( zxgdk)!c~Gs+NV34v0 znfC%D2+ral76Thc&?^Rp#57k121z@RJjW)Ox54s>pzRFeE+82tG?~ff3=HD4K{7UI zGDS`d3=&||!q8+`OBoo1!7>GCGEUA63?h~w)B52upd2U+aYl(O1A|CBNO~n)8Y1(| ziGe}H9VBxCE`y|zS&xB1WCck25nLLgF(#3LLB;_j^9N04vO5EVoD)b!Y%^+TILk6H z$b!Ss08M5?0Rw{qSjG!Y#v+J;K@sf06f_xr76t}YSCBn*XfkFo3=Cr6t)Vl}WXzcv z7{m%ddN!fS>`sQ?K)elZ3H4Qr5*cG#p}moPAhg54hhmxgFejb&g^2isVLCUdWhfx!|i(}N~c z=gh#M1U?I38JbM4HUonSSkD18nZIca49eipxP>Z{Y*)a*Af*2qBnokowF(1+kPXN$ ztXr8GghAD(@Kew#j6@$d1_mK;1WAEpKv{($ahC@JgA~|KOOPNqFM$&rh|9pn0jgOO z4RaV6r0PIs#=#YVB%sOprZ5A86u3aD#U}mHpMgPYB}n%iY|=mY7#JkMF4zs12D<>n zWnkj~)vt*svl$p9(?DiCfGYw?KwVH^!N4F1P80vJNtg37Fo;`#bjxjnB@D0?AT9$N z2dIutF5g0kI-!VlD%NI0v|1e*l+f2xDXr z1`{AL1~v}R^;LdBNquZU%7~*f>BT^N0ikgE$LVUlT|a98w^)!XPdK z8wcnXr$o~L1_tqeAj_A-6@gp@@oVBbD+UG$a8fu1mj>Gn;xe#tfSM|aceNN8B*H*u zyoM_RNkGjIsbpY~NCruB?SKaYsBHn_g3QQfWJqM;VqlN}mpl5XiohY22-?Xe40eYf zTpH{S5SM|C19Z1e;x9J_25In#1jTSgJ~J5^gu#wY>{no5kOmhylTf9>$vV+FmVrUq z8|2K5sM6qkm$*`ffk7JV$qT5`U|%GL#V|041cP+Hhf9MU2;wrZae&%diGisM3?gM9 zMIt+4Apw#Q25}kKI6y6~L^);#29Z{fB1=?7kkX{whk-%tJxDqNn>2ek1B2LqkaRgV z>6MNQ3?g$t($nD5U}u6|kSJZuz#wt}B)t_b4RRDTCV65Q7-YbO-W9ksST~5vz{UY; z5+*uJGcd^HfXw&_R|Jv}25}kKI6yIyXvo9BAO|){c^51sz$SqSkQf6S$Fn(%42cmL z3=DFWAmcpXD#1+^uyKjo3>g??!3iTBE)6yV#ARUPkX2$}NaRsrV2}m(KziVcKoSrq zC7z08U{C;?u^KK7HUq?EVB`3+l93^?&4qzM0c^$vxFV1Q)Qn5f3=9g1AZNbEChc0m zz@P{&?<97^{0nMpfm?3EpexXYK@tpX9H6t)6O+9d7!<*Y%?Yj?+@JGCM$9E^*absW*3k0bwLsbdMJZC_y8*nU5#U?Gn!@wX`4br^@E)8Zu z?b!vg2XqQ`Vyq=+m{!GoQXut_`nGB9X@!*V04G{o+HAqED`Vvrv%ph`oc z^qwsPgEhEvc@LKcv%u199AEb_Gw^DOGcY6`NM&Hqa03}6xgQ<`pl&0?m9upj7&L-F z(iYgH?SY=|e2LppBIIBNKl}2jAKTc*~5CykU*$==x3N`{s4SzWU zgDAMN(8j6eZ6*VQ=me0L{c)=KqR+q}x*wz_53UB>^#!*N68Rz+7}UWDv==T7W`To+ zjbriwW(Ho+*?oy@iVO_u-~_rIi^{~zXa)v#a0frSI6;&E+Phw;^ z1A{U+--{fChX{xT*3HHtbX7j!yM;zC~r2BAohe=I@r;66h+XzV=kFgpW-0eFnk z4pCB#+8waSUPJCv`z@VA}QZxxw5x96xdN07hASe!Q zFM`h9fQ-|DhYgcXl`$}w^)rGx|A#?ZK{W_NVp=`}gJdDdyhk8Gu%+N00Eo-L#sN~5 z*x=5Aky(5yFY+*AB0PT zTgDLSwG0dl>fj-i2XJX{l7dLvr7$okfD7*baA|OJ0V3^J#K0g6mXP&iEij1mk~{_mIbV>+ zX2GSA4ATV9WWK`9-i`vzPZ%mPcZaoju1%)omMG<39Aih)5H zToy4Mf%y^1mHL(p49egk7e#E+Rh0}3%HZ)#2W-+y{23UOz#X}GxHOmrbw=tDW(Ho+ zNoa|8SQ!|Uz$vg3i^{}}?hFh{;DUPvTpG-R8ng~%5a>|C#K%eu3`*cp&P#BW2%jfP z@G&rGf{VM4aA|Pu18S!+B$^8{Ferf|Mc^nbG!XVAhNdtusDO(GeYiB31$Bn$QDz2S z(BYMd)j13d8sJVuBwQtU2oYjWb_fH5BDh};I?M=?i@{AgMAI?hHalpJ1vCZ$Y9)e> z%mL4Vfu|3^(=-f86@?58W?_sVdC*Zc5GhcOW=K3A%D})6K8qBzQw1UeN)HT)-4YB8 z`lX<@9q4Elh|EQP2I!`jg<%0J0|N(R73k*o-_;BZ4ap1)51>bs zutJwLK96N!U|PY$z`%3`RB$qLu+K?jU|?qCxM9!0z|6${&4Ph}nT35JSdi@&Sdg_Y zh=GBHg*7v;gn^lZm6d^kfhpOAfq}Uh)OKZ3Ph?1$HH8^jmf;`L!;uvsBt1~dLFoHQ2oEI$^7+9FV z90$%I6_Crp91qT=3JeS^tYA(6r<5-P0}C6N6T!JDn1O+XgK;7g149C5b^!we3m4;b zCI*HK&fOpx9&nvdz&Tl$fq_K;%qijY@Md6O5n?Q0U|^`=jErSqU=d?oFt8bbdXP-QX$%Z(hM-trvf^T3 zU^51Jl&P_Sfq~5&l-!vn)H5)!s|zwSFfe(7n5JOn@dgG44hc|NViKxhVBnAhg#%Mj zH3I{ObR09t!70oP4D6e085kruWT!GSFbHO+Rx)tN*@AkP90j!u3>@+v%%F5^1Ja}r z%*?q2dK*lCct*Hae!RI#_@!ifk6nAt3Vor;qt;z zMJy}~3`}=G&eQ~{XIca@T-%ZbnTBtM~qfq_d=4|KpIM?xI~1D6s=Ee8YBt9k|oF6Cs9d6OC$7&y2M z&w$i3n=mkN8?k|d>U}W-1Gh1V&BOuf`Er|p*eo28%NQ8AJwa?X1}5WL1_o}gevmn9 z>KGVQxPAVB%;BtKVBq%s4Vnw)aA;;=;PwLvvoLUjtmk2cCM0$SCjUkT25$fBApMTj z3=9I?fi|Fm@iIs_2&9-nFu$Offjby1%)rJ0(#*60Bpo8o%D}+X)xf~O9Tvd~3Z6Jt z1_t)tY6b=a?ug~A(3t0r1o?~s9P`{!P9W1cKrzo9?aK;EBX{Z<7`S6VsYrq2XB7hj zcN{40G#J>sY8V(KxRaiMbbwL>cQS~{08RzmDIm9TaF~NMq=MKy9MT|m8i)b z+Cf6;Ag8e32bsnrr^d#>z;U6Hfq_RJq>@1}H?e?$M?nUZ?7+_AQ3NRgneY|ly|c9p z3_MC8COb<#1A_pMN({(^-5_CA5R-x9Kgcw2s^nl`M;CMNK zfq}Ob#AX5ekhcw_1mxE1Ah#~7XJFuM2Qk?V8$l*?+JP#3uq(Sjx)=n(uIvU2Gq7>+ zu`@6*#a1&g@b*ZuGcd3#)G{zA@b+bZbb;O44`MPfaJ#TGFjTN3-0D!rz`#4fAEf7N z9Rq^{pZo!k9+%Y&415Y;n>dQrGBEHdg4iq^pft<}$uAtbwG0e=${>#mFfgrcU|`@= zX$F~NTgAX&z%MV)!N9;F8^*xEuL{!3z_EKR0|UPih|MCHU%qn>KPdL?Lka-MoGbGwuK}fa0N5JZDU~Ii~!leUR1}xAi)`RA7td7 zN-&SqE(O!;*U0n%Qk=_q#YCj9FU0W zSkD0}QFi7qFmQH)#2q-&XD~2uf|GFohb1UrdO@KS!T}DwK9HIS2BsaA3=Ew8n?N?5 zo&pN7iK$!+4B*7bISHhm0h|~)Cxh559Nr*%r-0ZT92%eynF?a_fFqN08b~Xs_>kdZ zU|>phVqoB$4q~#iO=DmX;GDS}Zt^UUE(VARvq5Y~_T-!cR>Hu>Q3f*MVFUvM=Ufnz z{oOQB!d$@1&A`C13l!oDLAn??PJ;Zk2*ifEZ83<=!2vp-mU9Ux<^(tlLD9SvBrXAQ z=Q5Bnpv1caWXnDk1_sXMASQcS4ak;Nh1`&^UJVKB&$}2HIM;ymvp~Xe1BeX`_l=-% zgl3A(AaMmqxNiZmRT!9(K!LPXj+=pjeNhzy!v@ZS&!MItf|w4Djl?g3NFN22BMuzkSUm=^DS+cDC}|uA6$c3%yFhAAfXc-Q5L-`zY+b>?lmZH(Q#Bx4 zou)A`cyQjd=3!vq09CP^w?JwbI6ze_=WP(11zg2)-T|>WI2x)M7&z~O%ol)!);*9` z2L?8dPuvU)OqHNhI`4y+>?#cm3_P5Vs(B!x^%&xxHFFpkIG-djfLhAnqT(6YTn46{ zplEpR1L{-wf|#!-@-Q&)ZK-BpV82!cN+0izgWL#CVedhD7&t&xG3N&m8=5{of|W3^ zaZCdlQwZ|qClHhUL=6Lj3g?%+C(jZF22N2%P}bt$e7u^0fm003G2jF>EI7p(LHZ*E zK&=E$3C6j+pl-o3P==CZ1T`ZwIG2C|QwH37DBzqAl92_=RB(cdT~0a1#k>p*4Ge4? zXL&%yAISM^92~q145Ghu85lU_89{8Z*VQ0{6&YFh7#KLg=|zbVlwcS*!DX~E*i90g zNubh1g%OlY95}(5Qx)uw0ElI3VEqvcY#dub7MOt2kvb!YE#_Ozz+l0t$>err#P}E(L_gF0>m~`uwfYtY#gy5+ZaIMX~qa*iz!rrqR@g-il2c2 z5>u973!t`HfjJtSGeKpqH8@lPIJH12z=jc&!=M(}g7t%<;55jB37`_+juFHbXXEr@ zj0G8fLz;ns)0+{TqByU|GB9xZFoN2kOq{u(^y3TWurP>oaQZVgf;2RMl1c#B9}JwJ zwi#z2n8U&#uD}`0xD>7?1k8r22?cWmAPv>6{Lsb&GlRGQXC&iIke+-{VG_j%YOyhJ zUIh6i8qDDU*&WOH7o^4oq$Un*9Ruf0P?C!Wb66N86gWK?(*+n9B*B612@Z70B9IsS z7R05Ph@)<#Fv41le7$$H|V{8H$lQj)g-GSK*oclnLu!M1vAgG`32GX(&?C%Z+ zZcz3FiGy9o&cMb25)jP-xo$b5uOI^he_8_r!wW{vb&Lr@3=Ew5vltjS*MoI4a0)MG zVBp*U=CE)o%w%BT+yqv|!Fj8Tfq`=~W10}CB-IAhidz_Sgh1Xb1ZCl^j735W3<{ig zK(V)tu|fz`(d`4}-R+Ds1Q{3%I6s4eVF#FVKuQdxY&T<_5CcO1qtsJS)b3+!5n^D7 zV3d*o={f`suN#a~;BxE;;}jtVh6jucY#eRif@~!(s5s-|1CfDXQh^`D-p|Ltzzu3Q zfz-o8Uu~aJ~X_1UM@}arzoOaw5Um4~nmMV2+GHZcZ@+=SMI{ffF29U%-K-!MPX| z_5Z=vtl;DWd4!AUk_ZFC3(hy7Fy&^tC&Iw+f%6&2G#;i`A`A>aq{2XYc$vP5fcDZz zfs>Ue6DaIC7^MV3UXfs85@lfMV3fKIGEj<%LzIDG0;AM9P=iyR=@lyj!wg0#B~Yv@ zG4Y8qFl=CyY69_8z;ZhnrN9YCjR};zE-*?tf+Aj%NlKJ~;Rd4=sG-HF!=xYziccv} z!;Mp)Nlg@#BBc(1R2nmZwxfJtlmcZUP75X{Q3eJLCaI&KK(uD^5M^L6V3OJfN{zNm zpmc7*B=r&GYI`uxfl2Bp$mfnspk(jCB((sf(iO~$V3Ok9!oa}k&IIZOXD~^Xu`)1l zdV+a5Obpy-L>L%A7@lrG%{w-Z8zP{XUJhC)&+!Dz2DO#hI6i>cpw<=}#~&~o)UIRW z_ySHDprpmdAtDOW3u?izab$x=f;fb^K_Y9p7#O&1L>U-B7;GOK0~^OL0g#-dAeiJ~ zU=ZEc%)r3u1s-l<<2c8}z`zvh$G{+#Lr@25LB%eWnf_U&7&rve@)(5d_lq$w2ympNF)#=@fCdI6IP?}XFbIL? z^cBEOaUt-6NEME2H4F?w&Y*^thG1$%34@S}wgdx%0Y~ye&{{b|2?hoWj6kvQeNRblL5oO|!C#0vQ;XWUrPmFfhxW2Mq?xp0#CQV3jp6WME*E zHB4h*V3$3i3hHU=7BVn!%H9_P4SpGjF)(n;HfS<1@W^%uFfj1SN-{Ap@X1!#GBEHn zFrBMlU=VC)5NBXuzq^xxfk)hSr8olvM_U~OgSb7YAY$M+SIxj6?f_!52qu>nF^D^Y zS{WP+Oy74hFo-)fY<^Y!OaW| z;+`Ni5*(l!UEB*aAf&({U&_EB?hDFW8Vqb4pmKugRV@R9xZf6WPLM zNI*>jM4E%i1Th8%InYv6Mio#P!=?*fL2ssDoTDJcz#tz0y46Dk#B}IqW{?-GVqjoY z0Xd{*0yBerE@*SBinbI3gZM;d28EO)21Ye=DF%k46PXzlLEQ#Mbz3P0hTjvJ8I&Zv zLF0D}3>^v#3`Zpx7?c@9naUWH8ABM&8I)|Hl8Y4>7#=|+84Z;gLzp-W7?hSnCHE;X zFtAG^>0~NqP-ZIU0G-yaavG{AO_6~?=`EB4vda)z2cso}lBf^FA=Qcu3^5Q%=Fg0w zAeR^_GX`=p-?|K1p~}F(pdtlTIvr%F5tOq?k%6HBqL0yyK`8(#3^HytL>QNGp-`n@ z<9eZ-9f}ML7a;nWiWwM`=0L?k)_`^+DKmyL+At{Xf(nDI5dzIFGJ>v>!RqS$P^BPi zKoj6n3=F&l;9}8G3KahdV74Ea5rm%NW6fqa~A>8I**4A%PF_ z-BE})K!KPy!7C=>keI*B4j>8CEaJG@IzcX%o5swb^a*MK$mO9jaF=uY zL81xd@_L9c4wnl+Rf1j4#xYq26bUnB7#Mg#)uJ3|=@27$+lB&Ep*XyNStbkf%3P3F zGN-}3G8g0(@#)MA@}N#R<6KYyGk-cP*yn>B?>mE;L7||SfpG!IlvgvD8I)3>7J$kS zRauBvKxGIh_w0nqfr=0>h#XTbsPF;#3RZ-ig(^A)%I*K598kdIL3DtkRKgz-Ff$;+ z5Q8uhsw`9`G+;pWgdAv|obfQIxOITarouB?gd8k14ue7?Zze1>4ue8t7l`{BbQsKG z24Mz<&og16aTF99IjtQJEZnUu@-VmF1i7_*7R;?TL2f+^;_7T+U|_rnQut>U%&oUUZZ)0F%%JoL zY9z?5XCRIOh3;>tFvzXnAi_A@$`AmF2e4b&IJo3N$xc8XG<^Wd`=C8gjPF2>7l0~6 z1oT0;t&A3%<`o5ReY|>1d*vFS5(vi@DKIdofh%EVMv!Ye=P)yN%VUUAUAi_8td;_Wy z>|hPhCVYt%pqXC{&?bC|l^~`j(k4n!4ipBtRv6SOW6*q8$G{+Y1w3~R%7EY<+hBr0 z>rD*{*ZDU{%2QM#zn9m>qTDHJo44NN-tnEl^f~@WM0F4hu7D$%# zO=MtTf~?PAhAi;_Pl`j9ctF>0FhN#&FhN#&FhN#&FfoExdN478S9&l*)^>oxf*G>5 z1H@!PUE9IL2wvO4#sQveht9-vu!HC2K@;*U2B7vRGiXAd#SpX-f*CX+&te=6S{DME zkY_OmIgT0BuVPhy&cMLH4(eBNfY*w!ea&G44cF{uU|{v{r-- zvQ~r>v{r--vQ~r>v{r--vQ~r>v{r--vQ~r(v{r9=T%ffgY>+jFOj}DEvWdMc5fYF~-0N zS}Ou!vv7jeim-#XhI4R&)`~#b0u0=swIXb@IKXQ~*cO07h!eC{gl!>+$-oI(E5f!2 z6jmIZptT}w%Rq4}z`(|_0o+A-!@$761DZ4g`I#5I&zymc1LPF2C}>eXSQNBYM3R|- zfe)mX9cirymk$$Y!Gk~n1D7w;TF6=v(25TZ^<$u92U_vLVG3q~R(x{@_%XX911# zffj#oYS=*+e}K#u0%?$BVPFsj6JTesacl!Sm6ZiF7{I{50MdYv7ltZQWno}o1}*;J z)C8$#1}*;J)Q$o#{%`>;tN<_m;FhUmVPN0{E&kw^1-XcUff=;;gIg{Ey!eA#VJS!+ zwD^Nt(H~UeaDo7b44Bvv(gJ!09jCO+xC{EDK6pt~8 z&BO^>{J~=aVzYqfX?Q$AY&HgF(BcmsuMHq~ffs-9_{f4ri$HTfJiZ&i=75@mJboZy z76u-W^}4JK4B(W(&cF;>{K4b@1Ee3k_=6`f7Nj3EX=Vm3 z{@@9*U}a!n1}*;J39AGz{-_77(*Q62;E6Z^Tl~Qj3Gx{v5%5GMgOq?10Z()uXz>Rp zXz>RRWbp?lXz>S694PKI7&yR-KX{TrOYy)df+rcoge?BxNddWygA=s)gC`Zl<^fMI z@T7s*pgaOHoEfzEgC`y26b|s>4_-NUHUf(kw@h`pc%6@1#wASDdUpk*I?I(;Axc-aS^9%wNJCuk`JpFW7mz`z4C z;W-;L<`@~6LCZe)4EBNafR}ynH7m0-FbIN{eekt_?cfA0``~K@v01==<7)#c0lD%M z$d#aFAAIc~CI@)g2VZ9-I|BnJ*p*!%U65rTeBB^nkSSC``}jwd0c>j8MN$!Uu7Q19PqLa0eLG9&@faO1A~AnNG}5?XxWE= z5r_?5_90*iVsdbTmVF3VgMwRta~f!sj7>IZ*#|gS1ndgI%RV^3%Rab*Kr-MdMXq3w z1_n--8U_Zg5D;4cJlM_^4l)!pa}KhejpG?8TtLe{1nfaf4)C%M0mscCw}Dn#2snYt zSq4tfvJU}gkT7IYMZjezblFEac-co22Ll81eUR&1!A#Jy53UH19US0gA6!wtKt_V5 zKDnYn&VkI+a>aq1Bf!86TK2&ee-+g90WbUDO4ooaK* z53Vd$P6h@J@UjoC+-*?Rc@Wj0WglFSWgncNWglDxAO!*tQwl+*NN|FdeQ*`+0WJIB z1TFjEDh9C)I6=!kxFE|uI6=!kxFE|uI6=!kxFE|uI6=!kxZuk^Ku%@j*bfQ<(6SG% zCS#DxszJ*>xLS1}%Rac;AT9$f``~H^g#ZU6qB>52mVI!776@>4g2Wv-LCZe4z{xm( z6SVAus}~eXA>fH)u0D{O2nJ@*vJbBQvml$m%Raa!HgJK~v8-ia;F<(d&j3!0T$4d; z7VwlU*Ax(&13a_LH5J6>0jDCaX&|kjg2SAPfq@yc?1O7Mh{*w(JrLlUc?fRuERZe+ zhzYYnY)JOxngdb-%4icoCV-ZGaLolVIY6@!8e9uBxEUBYLCZe47J_s!aDtY7a4iC{ zp>A6YVsn6JZ@3`KKERX7TuVXXkeO?)WguffiT5(dmVGJ=3|z}WOb+m}53W@`kcAms zt07_ic^3l%*BX$17Dza50I{Lrz7Z6T&`hxzB(4An_bni{3Ij7}*$3BFOVDx^@UjoC zgG`WR5?qHMri0V+VNkN>0B3csBcO6affKasgX<`$9C3ie>M@W_0i2*^A6&;l#X$lm zXxRtX2~fE>0b=V(kgY2im_f@vxK2$6*$Q6v!F4l?2Xy$C2?GPyEsz=paOKK%8^mS- zSFT)lKx__9(6SG%yCCx+Q)yiHKw3e|K6rT;7??rJKDh3Km>l3`A6$<>%Ps_q8Mq!p z`~w=l<$3~YsIfqbif3SR8JI!KKDeHzgA4&J``~)L2DC;4wCp26(AO3;`5e>4z#!-c z(h4al1pVv97#KJ>O+ey-t)K}J&RdNP41z%*wgjgUNIZmB7&J$C10)^_Vry`I0z5rL0|OUm2@01GBgh&CF3=JbE)g(?g$uNdf=iSUl+8J~ zKub`##K0T_E>QD^OPmp;AF>36OM=mbkAWe93$z4`is)M=n8UoPkp6fV#b6fP66OajC6B`92Gj372- z2@015<3YG>mS78@wpoEW8eE_yC|uUyPzm4yEkWV3VFcxNs0FrQ{h%c%MIZ}6OHjD% z7(r}tHZCtl76AqZF3=JbE^kI~isAw-LE-XY1hs^jxIjx#xO~AJ&=M3be?|?E2G9}| zt^lw<7`Q-7P`CoY9MBRJu3$zVxS9|!8>%K0%n^Vzmt#OPTA=O~XbB2eBx3_e4`>Mr zR}|w#P}1fCEkWUm26I45P`F|lmw?oOmY{IOfvscU0xdz|iU)H*OHjBx82JRDOHjCw zmY{I?FS`fShg)0&4de9OSu2iri6}Ui4P`J{- zDj-WxxY8NB1sNDtK$1oV<7`3D5)>X#s}3X%?p-i4@PHQigQTE*HV%+Wz)MiLGQqBb zEJ5MQW_%>bz`zAsg2Dw|g2Dw_g2I&xHkkurO&(YksD@n&S}6fqg2I*02x3E)pm0rN z)D&W1-~z1`;F=C*GjM^Hpl~f=v=m}sXy5`ZLE%~k_IC#Z4=8(r#KEouEkOYZfR~_f zEoTIsi3VMQ!nKZ(U6_G^3$z4nxNx-!UtcrsRv;>7~Gb5-l z7T^LcLE+lMC@jptAi)J%g2J_xQ4&;SbAgtiaBX9h7iM74-~uf{;o8n0f&YFB(lyjP6hdyfsMmam;rQyVo?SL5Qe84(9jSYM?H9U^bmLnN;jAdYPYj-Ob4?;Ek8Dn#b7q5jmgF_8=MY6 zNsEnRH&`6hnquQ<11~|*11~{2!NtJ9^H`LD0ffQ!ftH{w1TQ{$0VZQ$OHjDHz@u1f z9CDzMCeIZN3<83B7NBEaLHhy(%|Qu{f%9rB1B0Lihz)7U2wH-afLb!_Vhjw-TS2WP zD-e_8DQGW*pkp4$1W<=x&8sotpmlui(!OqJz1Y)ajf)>LFJA>Ge#W2DyQJ@tgoS?-p!mbIR#W0+p z#W2EdprK6%PS9c)VaQ?_PS9c)VGoda04HcMjIbxjoCpTy95n_85%n0*8jOrQ1_loB z;uc{8kgLEw4PnUQ7EaLO7GcQZ7VwfWVaVbZhyzVQhJhBh2%CXCsQ_NXCu|N9)!+mz zZV|Td2RW628ML@X$oVvQF~&u41_tR5;tY@zZ-v1ukQQOk=%(;*anLOg9N)wl82CWJ z$;JU12AX&R9?&h zjr_=hXQ5O<%(}VE46>jyjZqck_`4u3Xke64wM~+NA$1-zgPd;@0|TQvXq@!=JZ1)Y z(8>^vNsIKj(0?trt0odg4e4Cq`?##B(?3j{*~AOW6G4@xsI zKmtD%6!^OrGBe16Q)VhihJ6t;gDhyAn=y5{Fav|bB3MFA2Nl_sizx19Nsc z$k}s1?gTr#9OUd5ATFpk&R7oGRI9ic=Ilz4v!fO>Gbk*D8VPc?gACl+`=G)gXJ#b}=l7J3-mx6Nn3TD_DET5|~@N zL2li)gqcC%8PrHF1qOzW_(1`x66^;y4p82e z`Ps<8z&H=&69cF$Xm%3h8OAy9;69lL^2z%pFrUl=`NVoDGlMMHCt&R*OJP1)0P@M& zrOXTpK~N(>F5U)l8YpjNLxn*uegF~1;o<_QO0bJ%Ku3Bot^>KX0V-Yyawxb2ULgld zA?rXX}8_3~B+$Pz5pFmZDUCzba*>jK19fM)qrK z7*sO}gIElFpjj$4(B!x{WO5v2t1!s>4CT#i;ld#Af9k7zv_i11_sd-ka?g|R2XcY7HDmN#A8NK4uL4s7Hwi+5IPA`CJaie3=F4~ zK*u1|gZAG+)&?jvL)Hc`ghH~B5$NP?1_lNj(4yrbzBT|nH_gVN$q3rDQwF*&3(^b% z%}uj`=cyrc(`-m{(-5`*1GtUKu4=-_z`y}+qjFf^WMp9ADq!HS1uswGD$ZcwuuBK6 zmh=cZE zf!e4XNNrROq&6xCQX7>6+D3)6NjVHGKojwxMivKBo0J2oP0E4PCgp&%NkN`619=M4 zCgnhClX6%TgPg{|T$aYbz^M+p%boph7y|?IHa!LgPCrnY%)YFQfq{81Xp3fPG7|#> zdyyFf12?F90SXcDnt5hOo0i?V89Z;_2|Bl71!#dZC~NV7S)eq^2ReU(ZyzWjvT>|q zVqoA01wR|dP7o6`chANFn(`L_uia)(109Yh2wuA_2wuA_2wuA_2wuA_2wuA_2wuA_ z2wuA_2wuA_2wuA_2wuA_2wuA_2wuA_2wuA_2wuA_2wuA_2wA($3|hO*-hKph76y2& z3D?&>pm}Q0xg=ajYfZS2)|zl3tu^67T5H0EwAO?RverZhwAO?Ry4FMpwAO?Ry4FMp zwAO?Ry4FMpwAO?Ry4FMpwAO?Ry4FMpwAO?Ry4FMpwAO@+gYgRsXomr4tqB(w<1ZEl zh72LlS`#klS`#7AS`#klS`#7AS`#klS`#7AS`#klS`#7AS`#klS`#7AS`#h_Mg~>} zhGrqqS`#iAMgdj^h7KXnS`#klS`#7AS`#klS`#7AS`#klS`#7AS`#klS`#7AS`#kl zS`#7AS`#klS`#7AS`#klS`#7AS`#iaMln{A0}5; z1_sdr25w&_(CLp149uW4B0PvSB0PvSBD{z-BD{z-BD~Ty;N<~Lpt*nW8WCRE&8!Rz zT%c1yc;%v589-agYZ(}L<tC#@FTAg;rH4BG6%dygx^OMG|vy3oZ&}WBf{?o5@vxE zUZ$`^8=z}M`286`v+m$EBK(1=ApM{zA%4gj5iZcyU;bc_Fla~wq#3+Mgg?XqG&K%d zBf=ln$PP+y?I2^oYee`XF0exr0e>XOXOJ}_{88B;CE!HBA6>={+Peo@Bf^igMuZ<_ zjR=1dF9&qh5Pvd=$pB6T{77p=_>tC#@Wa=LfD8w(5#dh&>9h8dk_<{Mnu?gH^^IgxRKU~a3ie|;fAjf0Xdb8<2Wb?Kx;&} zo2)@|{MDc}BHXPeJPZt6#SGkS5SM}0h;X-qLIAQ>fxF`p=*V#JY69*~kT_(G2sbzx zL)M6JBdrnP?gOcTt`XtxzY4Mmyhen3Vh3n70cgPzH_{prZlpCL+(>IgxRKU~a8Cni z1r;3jpoIXSH6q;8K}^UR5$>6%;3gxj5#dH!Bf>ogqy&`FW`axrtr6j#3t~dnh;T13 z0L{CD)`)N;tr6iyS|h@Zv_^y*X^jXs(i#!&WguffYea5?Yyqti;a(16Le_|IubRRO z3G3C6u>QP@fq{DsD04y9h;SpV5#inl3P)(BKw2Zhjl4#Ld#fX8EdY3p2=_tIhG|et z9)g$-PRoZu$r`dog!>4n+<>eR;XVo~M<8oNxQ~Hsf~*nYJ`O4lAZtXpPk_or$QluD zg!?YYe8?IR?t37upfw_*e9$!_ z-1k9D$QlvuNAp0F`Na&}k0Jg6jgN36tr6iyUL(T&JRf8TXpIQ>>n(f?4D6saB9L_% z-0xn2+z6Vr=SEtW!Hu*ogZm>$2`GJlPM-j;%i#V5VnWtsaDM@9KnIV=bAR0c8YlrD z;lceKQuTw^WpMxa%?DeT!ToC&KLdjhXk7+3bX|rJXk7+3bX|rJXk7-kC?hCqaR`Cd zWpImuIR-+Ybs5~^j3E6HqM-Hxw*=z>eo)ICv@U}ix-LTqv@U}ix-LTqv@U}ix-LTq zRP1ugF@mmq1}zHs0G`4GIiHO~o1cL}0<cbmINOS!mY>%Iu23@oL-a|K?#OI z2(&JPTN&&o2_ewB3~m)hP%?240Gnj1E{8XkCUS=s;P}x(sezMi5&Pd^8BRJ|pO)SxDF! zfStww2|GhDM?(l4!_ajZLZI#}H*{Tw5NKTnH*{SF#5Pl~VW4#xpoIw%pmiDCX5h(x zN$}Ai+!loT~l!J!f$1X`EDZNmu4VF?fmY{B|LQSc7r z9niWAZaYR0TZ)a_i?IP@IA~o4H*{Tw5NKTnH*{Tw5NKTnH*8&o6bH9I_-F$m(7Ft6 z=(-Fc(7Ft6*t!fU1@2(R6CgvuYC;$@Ks{oJnouxDfI&)tJCgAYNDXKm1~+sah7f2S z1~+USh7<>PETe!B1A`D~9R@da9flBS9R@dS9fq_5w+CaD5Cek@IFKRhFl0bSfpGgV z`U^5J$bcGg+(BSZ%7Bgn;SK}4Uj}p(2sdOMh79N^5N^mi3>nZ-Alxxv_sf8e0^v>o zyOTkhg*%CH4#+0ZIt*^eIt&@mIt*^;It)qhQ6SvzjL$)H@TCmgiD1_=h?X&Mr-B`+ zAOu>6!JP(Hp&<%73WPhI@w*TM!wN`DWq?;8Y+&F9wVXiW(6$wK0O(XcVR*ZcjRWKo z3D8j>+?ilkNrFx>kl@Z{^b=-a5CW~k;D)Zl5CW~k;LZh`%mJ|`53CAQi-A@$NPyO1 zaOZQ7 zg4SVx1SCL5fp9NpOb}*Z5P=*8!o7~MSOj!e&MXE7Zs1cX5AFu1oc){8JONC<(}VQ_C{Y!zW(P!IyG!{FY=*e$}qpdkcW zhrzv_akVh$&H>Om4DKCZ&H-7_Q6SvA87GQ>)?vtkmKJdDW1KDmT8AMEItqjvwhluU zTy7mQfJtZY?DZo71_o|WI|-y79s=7zYbQYKFu0F0 zg4mMaqd>TiGyVnz0B9Ws_X#kY0TKWw!5j`q*$iEW0g0@$jEtg?KJQxa;SW1OApklG zg!>#Lh%Ev+1cdtu<4G|F1|iT23~uNO3`ofXU4bD4T7kh0U4bD4T7kh0U4bD4T7kh0 zU4bDAIs}9px&lK8986!p!K5JsT7kj+A8gGEA2_3=AKH zKr1k~d6>S7F);j)1swvy&CA3Ln&@Se1t%y`CQztzFv@}s0pXTl;ui-U0wN1K1cY0P zNmLxP0z(#b2ne@4lQ0Kp1%@o>5D;!9CTVfdAt17#LqNDyz;ZhnWx+{CjR};nE-=c1 z4gulTWKtIgt-z24ExYB`VbT=`#iT4~885d!ld(AH5D;0=At2nwOqSvd3?CR}LD`4f zg2^9rTN{%s=nxQYYo<`p*ddcF=nxQYTP9Enw_uV59Rk8_59T>A$$}06;dW#KC3p`e zSf|Z*uX~e7l9f#;8~#c60m)@${;4_zGQjuERgap2?mCi<;)E7pwgdF8FawH&*jVv z3gCHoRZuu(uV7|S1kJ;%ot9u=*tUY1K}i=nRnV`T1kc@Wm0)1t2aVvfaU1|KLF3zO9H4<> zHjZeJ$3drrLnqatI#@_6(r zsK=c^9$&VKnL+6;)B=#lBjIN+fKGt0Vo=f#1J%+D3?PpeKqNtXlRcoqAdk<22;=a$ zFH|Mi<8q)I))=Ef@tFV>N30bPh95%^4e}Q2YS@_?(IA;StDz2$1vxxr4KsrRcy&M= z$go*!m>Cp7O9$fRr5PBWu3=_SngTWdxgrCD9^?=O(5`gQS^=fEP&pDH{uyHWS zFfj0YFflO5fllUUTm$l90aOVh*W81j6|n~7!<_XnAFcuU@HB`E_Td_k4?$OrLW+xZ zARlhp0QKQ|kPrWEU}jL73$*~`!+w-QA*>jb-a{opQMU{t3GyKa=)i9V1_qE1FF=HG z_>dQ>66`}chGqr^#uFfKDL};$xyBQIQo;$4w_-NJa?J@)t})&Ob@(Ze!*_3j<(ktV z!#;u}LAmA(DA(9*W@bH7Sr29pJfUEmRJaYdRrvn7QUTR1wJM3m}R>K4*=B zL^;Ukry;_)at#+$Dah+8ick(X*O)>%pj`78VhA|b*g(ZWI+T2%9FPMPA*UvQa!nRg z7~}vvxke6jodn~1Q0O#3bs+KxXxS0u450U*puW5rmV(}cWQ?}JDxCMA6tsK`ERTEy zRnn4Mp%L*36cH&~nHiMULM;HLpo6-1#b zL4m@+#-S$9z`zemm1>}CFc`TRL0&X~szG>>LxF)o9lV}}n-S#4!&{jd)WMZ1HzUZC zO52zj)WO+=n-S#8>D!nYG{Ds=FC)mCoZFchG(q($A0x=0A={Z5lwzT_fc&`=#m!a> zO3R^=Ab(zfNP_%%04fZMu3r#gXxN|^vPYpR!TwZx09vb~4))vws01RbRD!Pb0eemz z?77X`nHkifVG1vRR%VW zQkWpvIH;yBkRZrOVB?{J^OYDFctO#@2eN}fAGE|p1iZur6fJyU^9(>sT*MzULXI4&03AxfY>>vlzyLi01AI0Gvtb2D z1acY%_=pN-<1Ua0oC}f*g7QVry`N79y}QfcMfEa7(K*Ft9O#ITqZYg$Qg+V2%SfXdwa{ z3z*}<4O)o6#tP;HaDx^iu(5$T5!|4K2y7gTZ$O7$aDx^iuyHYd0v&q64O)l*JM@AZ zv=9My=mj@uAp)BaBPjh=aDx^iu!%8(Lc4(*v=D(!oDsAJuZbJ95P?mC@dxOF6mHN$ z1U4B)4rT_14sOsw1U6O1<)8~vxIqgM*z_28GB7aA;07&3VABV47I1?WBCr{NIV-q9 z3lZ21!JG};poIu*(1i%xpoIvqLoc{N3lZ4N82P}5UT}gJ7O^vI0fj&0&v01nvhhBiJ=iq`IdI4e!Fz|p@F|f_zgdTbU3L!4Yp%)+~0~h4b3s6{baG@T0 zu^YU2<~sue11~65fc(t237o#!I6zJTi-K1EgGGfII6x;|Fz;w)VBiqYV}@Ul!tKKZ z+La^#UeK@uap(o7dOhsW3r0=r9WC_9cZ9#Gw~lieSCq1r1!vTr3O>T%ZLFTq+JEok5k0x5_H1LKv!w$XR4Ql}pMRb9T0WWCajkpXu^ny1MPCHx3kc8VsD^Loaxf1lXV{f;Sn&gdBRo3)@3QPVk`@d@3Cv6F_6(e5xQO0~hGf3qEkF1ceXiqzh)y zf(AbIWY7{7@PY<@P0-;KT%ZLF{GeF{2FP&|{Mvn>l_-z}4g5MYLF&N^8u;}dfouUS z65!VdF&P+mK_-9>r~tZWao47!8$O4KW zHVYRh^$I|83YTsz1A~Av$m7rj4FW1FLFRxLGziMOa)3@<4Fesi3)0KL1v>OX&PCI|RfQ0B`T(4hIr?ICmV#IiQuQpnHzuAAr`*Ko>NC4vr86EocChRt#Jq$8cwYN+$S%1_98@67ZoH z+_?uKhhA{!K~zH)G=NHd4ldB47u*FP1p*LL3PGkwaDfiJ;4V52I`o1IvY-LPHsAss zdcj=|s+}yjK!;v%SAf`-T%bcQxGOK)c$RAqyJpKrVwW zXaF4^0rE#1#AT2L4WJObz&-OU+~ipxT?`NtW`o#}?8!X`qy&`F=75%UKo&HB zn4F-|cMa|Z#-If!kOd7OT?|~HLoc`&f!I*DEe5eU!23nHmw;kU0K9#jdnrg9vR9sa z8ORt=;=Kp51+t(4#N-4YdcnO4bXWu^tXD(A`ZIVz14us$Bpf$@*wAp_2nt7Nrq~P; zSAc~377!bH=mqyy7tk^s=z<2&fe|3n4?#=^r{%+-WX%E2>fA>_<%R+m=+F!9qo8uc z0TQdnKsE(%feyXkJ`O4l61YHzUT~iPm5UP~ww?sp3O)3K`_v+ktPxN_yb4Pvu^D_3q%HN?Qd1v>PC`!2|Q$mU({dmyc#1q~9QLMRgC91n1U6^dF0I`jhUOd-%DE<*$ZC-~3{?st_S z2Z5*F--9$TaDf^G+#f(}Xj=UUQUXe=Ah$yoSAdwD;6pFCzktq&faIO8h-L`)cSth? zy11eXv_J=Z=mqyLPtZL8(8U!XYZ$npiz`4J7H;U`3Q#uZ;D#=)0C5bsp^Ga(`XPs2 za7!@ygATpmMqXUO4P9IT$`b|L$crnu!IPA7jG@p&FF+@8K#sd$p%>hmjDO)4XhAK2F0KICCIK-<2doOTxZ(uJ80g{(5F2vn1-Cw9Ha`OcB~Z(BcYy(3unr)eH>WW{e;< z47e`0JR$#xS@+JKpfDa7u>Oo z>p^Oui!DIbF>pf{TYxyALoc{J7$reRQGf&86CCK^dsn#q81KOjz2FXF1bG^K=mmEe z*gxPyFSx_O{sA9)!5zT}@-q0)3+@>3H7VdjFSrxH?qrYvEw%t%Y9a|;YyonBBy_O_ zhz&jTg4><34RYuOcOuyJphGXXQ^Af@;D#=?0I7f+dcmE}I1O~@1te)?FfIchdcg~7 z)q%vp!NthH3)-Otl7jNtI6y7|A9}%^3BE)Ha_9wjHsf1C1_o~EVhd0nW#EP`wg748 zfLN0URt2hIcfbz4;Lc|Ru_1?Ea8F}25n^EA2G5O82eTQtp^GhCK!;v%Ll;|s{N2I8 z3rdI}aj@$^i!DF`;6pFCmoxrH+`GcPj!{UMfq@%z=mqzBaM&|⋘|sjAG#i&zx@p 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

9*8>7)B^*>sKlGA^Z2BlWPZ{E;Is7r!g=vfa(Ay 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� z4Wt!Re8_MyFff7kvvW=dG1);|g9JEdE{B^u3#5wyV!~_?8cy!1?|uV?Pup)2-3yC0ou>bxd_CDx@|Ft&A|bhDCS%Oia7xeLr^p? z1&K>Q+_?;73@GvL0NDcC&(66V#AFBG7sa`%5VD`0b2TKaLHpS`*MRi1K*DhYhz$+* zji7LZW{S-qaRo@YZvnAY7?_e885lUX%5gI=u!Hxra~^ySHT@98bZ~4O1}AH9R_8nd zDmN53K=(y)9tD*n4v<(q2C^xD<7xu~1Ltv2age|Px-W|J1gKn`0I~HX$kr7MOrXO~ zI8W7pYz6Nx=e%hR+G`G~VmWVt)G%;>s#wn3AT|rQisifmVsmgbR5LJe-UXR2012&o zAg!SN<)64gvsJ1L44n5tOm^^nQJjyeA^Y4pA4B{D+F#E3B#D86fd!H`pMlL~VA=_a zhUY$@J_TrhIp^z%JPZtcp#A0S;QOLD-yH|J5uC!_gY+Xs0lzC?hCqad3k6baRS1dh6SfMBS=4FPdBFo<6K@) zw*a)Kn^Tez)QrsF1nueOlmYi13OGS~x;bURG8N!0TAXr>i+LFs8W`9(&VnvN;{Z9I zje~<1x~H2{o)N@`?CIuIWMl#DSO=#UB}Pz!VE`Xx!Kn;(lLRMdPdBFuBPf|TfRBpc zR0aDZ0AiUMSU+e__g0VvpgrB3>Wm;ZWKTDzCZjjp0xhTopgrB3+F*_Z#26j0D$t&8 zG0@g<(4KBiT}BWavZtF~Z(4OvCkZqtn-JE8OAU0%AH>U-o6h8w4#5PN?1yI|pz#I)u(4KBiYjCIp zaDw)9bJ{S1av0PCTd;mm6oB@jih}lZbJ{V2*y3!QUW~CI!$EtxIlURdDT))cr<>D< z5!41{;sovK=JW+~SQx}PIQD{$K!~GQ=4O=79Eea|Samg{uhxv!QB2 z!5jfdLv<@?_baIJ0NT^d8OeAPqzAO8n=^_L)M8`c1nueOj0ST+d%8Je8UKRRfcA8A z#(}M4-~{dI=8Ok(KzCJfdN8I7K=*WWdV&KTyr-Mfk5Pvox~H2n2<&O_o^H-Cuz$dN zx;ewa#(?*9b4Gx@4Bpet8ND|k;gX9C!r3=%AyNsOH!n?QTIIg=qeL3_G6Q^0KK zo^DQe#w(z^#Y!1C6Tz+r?dj%B1v^rK^TkRA2F^6F3do*r&UD5n0t^f*AW0*G@rwWh z!v+R!P)iCV4sO>oGH@F)GcbUppnNtCkW0XOx;Zn!t`Y-nN0s2rX0!$!KnU8?&6xuZ z3*gH)lR0hz;4(%{h&+31kdt^DXCeFq?rBw5OYM z3F9O|28ISs(4KD2Wnh1IFmQviCrBLZI?$eOkN|j3H|KIjUqSeuZq9X#2|^4EoS;43 zoa@2588|_Ex;Zz1IV_x@t+||=z^XVnL3_G6H#4RQfsQu=?dj&+!k8ljI^qVjr<-#t zW04T(7!J^$Zq99t6+#RQ8l0d#-JIJQX9zMd7;u92baU`u_1f9Igf*H>EZE=8EW(Z}jAR3HBFccPHm7Fh_tB zw40mrHF(TKf)lixoAVu*1KG{Z`4P-f-~{dF=KKN;N=zK0@ZH>;DquP2Zf;IBCQ!n<0NKsW zsmUZF$^hHV&8fpAB?^j3@NRBSeI^Ca26RU7Zf;IvCN)t8h7XY4+?*CnHlhp+(B0ge z)=W;K3=GiS+?=*dpcD?>&CO{K=0SIJb2>7C5WF@2i?uh>COb||3Y_jb9#b# zp!=hai7+sLFg(S88g^_Pm%zKTmxFh6-vhHjtzEfYSu% z>NhqHKJZR(P}_x#BO5dZ#32mc&ApZjbla~e1B00;w5JK$&HW2}B7vhIXi&-#wws&N z3p~)o#sON3&jdPiQ$Rgcl!1Y72WYhnXb-iZYN;p#13P#RwUD)x7z2Y~0fUe&=+qnr z&>m_byAV+Z2JjwgA$!oSYyl3?9%>;6(2#%x2WStqkYf@91A_v%5iaBeVykd~_D~Bs zgV-A2J=8)j+~N!j1{}!?85o3I1;rT{EI88YKxY<$#{3*OKzpc#+(BXH!2#MsE#v_b z58wdpp%(H4nG?albkU!IL0CgojDdkqK!SmR3ABP%MBNm`2c6u^1lmI_s_qKngZ5A} zm8gRDT!(`AUiJ(O?BG4qLIxmTfLq@}Mj#6rz{iUU8H3my9H2eZLM9-#0K`M4Aj>5{ zfno*<6a@}Y0ViY*64l@U?U@#`039V{z`z9BGcD*0*}r~DjDbNKw7Xpx6i&im7Dx;D zXmsH(pu-T@I6%vJc|pO?#sL~pX5#=YT4mz^iSU8$_hRD!9XiCuAr4-m6D7vLz+N?x zfkA*#)d{rk9aIr8s)G_WWIHsY28hkUz{Eb0fq_v|7IgWNF^Cxk3QNXLaRvri@B-{G z5L2$Gn1L~3iZ}yUCk9P9!G(5kBsP!Yt9O?sd$>YybDhQC-Clt9N9Wc^}cP!@Oq8tY_W zfNceqkYZp^0V#v{7+qQoq)}ZIstIf#gT_CoWgzFm177>B5d#BbIw(ylK$V-rmo%J_ zW?;|(@2^P*F&F+~VbB3*fb{Fq3=FBiSr~LdyJs@*g4SRDVPVh(Wr-|Mnw$HFg+ULz z8zCEHw8>u<2CZ3|0b(>L`qh=8 z;$TIFP!7m`(7I8OMKufz8eULwkOM$sV9zq5Sqifj0|Wgn&u_HVzL_1_o_W zN6@MkW11xl}sb7siEBCQ7$Y0LhyFz85w4s`DU$$SEF_kr%N>RBwqz@YsP z8i#$LI7|WYib@z5`#}ZRhJT=I*`VfwJ+l_d0ePlS1{_O_ZVVd7pu#Y}6oVoTi(lr+ zFfj0Xfe$8GDFdoz*MkH=$JJ4>4gaAYS_<;eP7n{|p=BTsu`;kSXbC-nGR}X#dLqU6lrMxC2xi;czuMn8SC093H^H3XO?fAcxNZalsDX1yXpGffbUQ z_JADD$H>Z{3v&2gki(-GSsApNpyq=d{tn_oP)eQ+6$Uw6LJsU791dRqRS9-D8;6b@ zC<=_^7#Mg#trqPg`V0(=7ePMR09BX@N>Jbe@Dbc67ePK*$_VqxMUYQEg1Dd|Y{rWq zg+WYEw_gUiy_<=ZLF*ONM3CE8K^zHkJM&{mT!7qu1tN^Y?Ho{*P`AGTyZs%=?N(w8 z4Emsj72+#EQ;+(fg%#o}K}-YC8VYHnTs8&2B77Bo-R#_km1y~>p1y~>p1y~>p1y~>p1z4CM3k4u6 z1eg&k1eg&k1Xy4z1ehm;GB7Z&0j(Nf<5&P1RD>)RU_QH>fq}&UbeuV4-2m%XBhb7x zXx#uSXa@rW19;s4>+e~hgPOtX23Y@q*pPJttbajl$hra6e;_tw-2m%<5F4^?fR%v} z#DT0EU}XezAnOKLnZO*#x&c-eFbA@3fRz=@fvg)~Wdm~{>jqdk7?;D>4X|>7C-*bJ z>jqePz;pAEbpxydU=C#604wsk0ah{af%cGf1FYhV(F_a>kaYvB65uKPX7IWJRvAXn zYjqen*A1{DuNz>6o^B6bH^6EFcH05)x&c-*M$pj|M;IXM z2G|(#LE#TtH^9aKTG9YnHvnNn)(x;RfYLr>-2j9QT{plAT{pnG02D&tbpxymK}^WH z0oFyJMFEg?1FXwH82~gIQwCl)07?MdpmYK9Gq`&QD!4#S0gHmxFM~xv>jpq=0bY<= z=1HLp3@mA&eh3=}$UGLv5&;(If&tJ<0d@n>@&IPgN&$95P_L93v{HcG800l(&`JSz z$W;y3CNeN^C|iN%{lPsTPJ@}C`Bd<*rV%LlKzcx&#vnGN2gGRtVncdBoTecACHM*$ zIL+3vfI51h9uTKFNEFfo;u-kgATa|cO^NIx{{oZAX6b-Nlqsa8`72JbOy0C_&~>Tak?A@*#_=Pa=M;o zfpsN0A;)onyONylAooMMlAMs^xWHXWPEU|I5e(d*YXm?}0Eav?G5X=H;69h$-U>~S(VdLmzVPN2$2R>^Gl&ggxw-0dpFoC=)T)@EX%k)eX zQ~|MZOaYq;Q40!ren{W$09g1Um;^-*^MuI^3_QG#KnoQ1Ok!X#;9kZAniUW(NMqn$ z&ZGxAXN!S(M>7Khj{wL>=KT{H78ebUBVTR55Ox@1Y}v+7#R3K*DMIgtz`uji=Y)L0`faR9s|$y2q+w81syLA zS&<^3><5~I2dzI4PysO^D^dhhL3x0K4|KPJ0NDGG6)6Jhd29>}0^k)X0vf$+kh>K? z28x0-WUw(X2!aW)-E15n*RXMfurV-*f)W5o143R9s;C5XwlZi%ihw3aJ@{?~0qvD+ zpjH_0_0NAiWEUvP{1%i?p6?#ThGS8ASNKF@C76fT9G2CIFAkFbiWEWR%OLZ>D^i3EZ9ud0py@IpBR=rVHE6m_$QZU{DeA$pM)InvfFm1>FV00A7(IEM_bir&l4z#su$ks^e&B1I?;6nC%{DMCr0J1aRr zDMBb2#AE=c0-+R;+aN1agi=9l$chx9G!Pq9Sc41)->o2&4sr@)MT)T8G|*%|Xhn*! zJV+%2ALwocVFeXX2?}~j}9pJSn zBCQ}cWHM8v4WtC*N=Hu60gd$x3?l6yCS)y&NaqTWtzcJnfpjtOfnC`R7G_}Mr~v5# ztwj;(X$DQ-gV&;n^j!k!0=u;z#AIOLUdPG6@CeDRptUF>6Ly01fY+jk%4>lp=0R&w zL>0g`ae&sMh$@2EkhLhHkemXZpcYjIc^rDTf~d+{kU8MBC}Q%RAag)#QN&b1dLe63 z#Ed{}7QXxf1~E$z6S5XX%o-HjkhLgcHoLe$bsuO|oS5AKF3{ZykhLh>K_D6M5(n;J zkOs(F6z&iZ8#3+A9S$-SG@TE!o{hs4bVxmDEsB^uhzVJXBId{hS~>t){vqZBD#;l* zKx*f@@X$Ad3|%(>sdz#!%dW`fqDa7TdbfUHH~j`9Q<37U`P zjs`ggvKECq4&)r@-3r|CDxf)d@LCk^^m1+n2EJkj?hH_A1zC&2oe3_P7??q8QMj{0 zK&ruOQMhwiLDS>K4BUAT)u6Q~-1(rQ9l^`}`Eedxdhz(kc0&*%F2Ol>B12bqX3U^Zz z$Ys@_wJ6-J)liqUL0kq}i^AOw3IWKP2<{F^ZcteQT8qNn2@;2_Md1b~W5`+*?p{zR zLDr&h_kq+v?^fXMmjTVsgV&;PPdpEr&j%$&?nxl^khLh>lR<3AdJpa?AU0$z3inhH z8?qLKdm2b9sNeuiNP+KG;GPa*Le`>i&jg+03Qp$Svp~8aYf-pogV>Pl$vp?Ggn^CY z0q974&{`DkxgaKFEeiJn(1~UoptUI63qiUdYf-otf!I*DEe5e6Yf-qDfMO1^7KM8$ zNF1^jg?kyu7*OI>;$>i92CYTmUJhbH)}nB)y3GqoGpiwC{dpGy1NRz`e#lxB?hPO| zG~73W!V#J&HiN_=Yf-qjfY{Kx6}Y!{@WR%ja32KC&4E(nA&BYVw0szxtif5G`v|Dq zfUHH~J_;&FAZt;$kAZB0tVQ8I4k`{HYf-pQfXYS4S`_Y+AX}k#D{!BB2C@~r7KQuf zQa%O-4p8OFeG8-pvKEE=Hi!*bi^6>e#D=Uz;l2wpAF>vO`yNOuXe~+%9|Hq3=&l3q z`yeJ{EeiLer+kpmdJORoXe|o&6Hw~{vKEE=8Q5F~X3$y`?&sS;hJep&cIzySlKq!9OKmjqo=2Dz7g9!)dt^@H%kaIl1rH^=&vLpjT z0C>racq~Xff&sEFhWp)Kkb^+$Vz}RfBp~ZzxIci{(6ssytb~D$1LSt_T?gEsKupNG z8165hK_-BjA>3aP%@FSIkY)(@t^@8L=lB^I*um>!xPSEuFff3xjp7z!1X%-F7sD+A z=0MiPaEmg6vN>d347V7V16dctEzStiA0Z5Ct8hy&P8NWzi{X|8-)I0?7sD+B?j1wc z#c<1lWh%f^``mJjGX)^)Vh)3+jX}<5wsIH5yTdG4Z7=qTaghoJI4u5 zyGo3p#0yy$!>tT<6J%Wsw+bUD$wJn}aI1p-0a+Krtp?T)S{JigfPq0Av@V8Qoe{(q z0pE4Nt;q0~j+k#O5w0Zz+n3CaAnRhdZ5Tnh9cqCsSU+f8 z%t4R^pmj0ac8nml1RJ*(V=%~Y(7G6IZ}25|4B&M!+&+w;mM~;p47V?s!@?lJ!R^mj z4$=Tx7sDL@_6KCm3wI!x16rHH9n3fzt|o*L)We0U2?cWm7$gL^BN_LD)PUB(a7QtM z+KG^LFx=5#4hP7tSjNX7HK27c+;L#zA?skc+E1MV2G`=vm4 z8*nFp-N_)y!kxsJ53=c0Jp%)GGDIh69SnC0n9U#pzT1G?opF~i1A}lW19u|W^$fyg z4BV+;M?%)YaHoM)Xb6MuHsDTYJS7ZU2g9AgcuN?(4hGbU1BrvX-HZ&}i&z;LKvGaX z8wbcG;-I??xHG}75&^G+;m&4M1TBUDt%Kpt0S5+T9SnCa*ks5$816idX$=evFBrMkF}jID*THbF2kV9`1L58P=0MiLaBl*uf~tMLIFb0Xj*1>RZWsCq7(VU=lFx=Z1<3LLo!0TYRw=>p>z}CTV?*MZS zNQ3S+;NH!cA_`guBMn;M!M%?$OBA#YMjCXt0rw$rc->%>1~px|k1$q&S5GjoaeM%; zdYLZ(>WMK3f=G8T3A${8jbp1I0|Pgx%?45r4}mTb1_p7^IvDPwj3BlM_-+I4@hWiXSG$89>xX&_P0r{DMjiUiPJ=7!0z#tA<2g7}i5yTdP z+-ku6gmI2IbR7)$3$Ou@buip7!Ty4*gW-M!=0MiLaK8qR0YcWnaK8g{WQ0Mt8gPFE zb0F(rxW9md39=4``#;zk$T}EqE~ZW5uyrup+)Vq#Ve4SHd6-U#Gcf#+2Hk4F&C7HR zG&9X84Ng#^OrTKbV3ZbYU|`^uV0r|aaAlMR-DfPv<^lZbgKckJW~`0XdR3+ z=vD)6C8l@cpmi|Ppj!>NRlssP7^Oi=r?}OaKnd#tqqHL^b80gE5(lk=k$yLgfq`3x zi9rIi4n~>{#M5VDlK`bfY0#|(+{R3x4Vxbrr9s(;+k#0+0<;cB8g#1xw>6W71ZW+M zH0V|XZd)c$3b$aA2Hk4FZ4c%-FiC@MHQ;t+0ws74CTY;E2HdV-UIddg=vD)6cP3DS zA%jU8bgKckCzuCX2eU?;fdPc!DF)Q2WaHQYUcw*i?GX;pB?;md zAU324ByI^-0$Nxh!N35%BthJ&UxI;w9ehcGxZ^jF37~>q+zB)q!2oG>IfI26*f=hN zbS-OQU=Vi!G1)kdfw%epkzjxf(|}rM;9y{3VB-L_7TGvJWG?Z%2%*r4OwqFBe|379{28H6>0tQAc zkV58(8|oE`3(^=EwQtKXFfdQtQLh*UGF%4~k?UDl85A#rjttfX+2zj4%Af#VyP^k* zO{R$sN}v%bM$pPc2BwJ~ilBXJj0R7b7#Pm7vN9-x&#UTGISqB3y+IvBlBtG)LFFe@TuYvTVLC({Ms>L_DikXr{8pAPc%0VoITq4UTb)@P9L z04q~P;;0xyIUuL(f*K5RiY-(e>=ZXB2jmn6HV$_1U^r;6pL_-=$1VmX>;$M9BY3XW zlVyPBn#CZdCL1d>Pc62TWnk!LV`Y$Qg=zx1i-C>9M3#Yp7nC{KI6&D>K4mfk1LJy- z=?kC=5vKRROgDhx$9pi40VZt1&405cXBLo;27$AljgZzCQWS9a}9AOw}kpZN%I1Vzbl!KK) z7F3!u9)Hioz;GR8m@8Bh#4uiv%PxZqOMr?a3@es{1>$9pVV0b%46>kEEyl|r!)9}W z0uibSVwft(u=^mx7C^-<;W2UmZrJ^4k_-%g;f8_Ln{lx+$eo000vo0Vn(k*=0h%#X z15Nj{tOPODL6h7Pj~PMbD|q6GLF3~j1_mA*P~jv9p3cz(&G<_|X8b{=k|3zE0uv0H zOdSjilK)shr5~tZ0?$=3Xw^<(VBi7IUD(SrFbIMO1~v{*d8)Z&G6MsP03)a{1(ii$ z4Pb&nOMWr~gOoWa=Yz^2L68zbP@T@8wR{o-17{&97(nHRAjmcb4OWmP3ZT+R5M(k~ z6yhqa#h@uF@S24EAp1cC0~-gZc-LZ`%)r3w0y63)ND54V0v~J>m|$Sz08NHzJpmcw z08$Amg#46GWU?KsS!OEo+|YY0FD z0|P5$kpUZc-#ZIK4+8@OJ81qI!en3v?R$r?S=d4Q-dRA2h=GG0wC^3l7GPk7EN$T6 zV7d>oCkf;`@VNz`zb#2*L3UG(FD1 z4mv=A!#frAOmao_+F*_XisIzWMghY{r90v^x-3LFApP6-d_00j;qM$jEQ6+EB= z6gb2f4=^w=H1L29P~Z?}JPuk2!2>!#fkT23bgob{59j~|4jINbpaT?mKnEyrs4_kW z9iYGiIzWL#k8v^s1H%j+&;be@`e4oi9?$^_90p*{3Lek_3LJ)D&IX=!j0_AMMvS1b z@f|#%0~9z+z-~Lh13Ex~!;JA0_y7f7&>09Ev)I6AAaE=Id6FG;1_H-I5R-u&bOr*) zB9M1E*g8_85qE#!jQ8J zI1NCjr-08g;4}mU2>2`mPGe9y2A^fXX%0$);Ij<4)FohN8E~0`nV^|tZV6Ddg3mJG zmIQ?e_$&i%=~QNrbFxA6@8FqZZrM4^(D_4dITz4$C_8B8m|NbTnSp_W9o+R*hyoo) zz`zDR%Ya+?DQH0iXr7Q;1;k`v2elEoRY7ip%p7y8ftCrJJTmd1{r{kuV;)(Mi=by2@W{D=)+h+@D0GA5K{LlZil(5|3GASm zV;&`tTIg8@Jjz)h^T0F5yoQ%S>Oq6syhgm>paRVt^BRNLOzfbu40ugIY!-IVwnJV| z5Sxtwe3k*P*EEnh;F)7yA7;?RJ*W=m^##Q|13PHTA+H}un1z8EWW6veG(kYmGT`;U z2htCoIpz&?2I&X2(|LnHiXk({yul!04hAj`kY?~%2D~A1pw0fEnPc9tcvevGq=Fht z;F)9Ih;^*cnCFcI`HTS^^Sn`>tgx8pjSc~wWxx(P%YZiql!_GCL1!88#)0BagMkfv zmH}_lYmj?DDS|f{#DvTo^QM5@#=#Cc%YZi(#O7fKon^qA24aJ<2FP&mSq8l6>p;!| zpJl)&rwf{62hAMw$%9lvW{&w3l;H`IPZ6X9WWpbi_dqkpd`ciD8~7{(K9yvU384NU zpDKvSzz#agfDfE1LE&QzIv)TubIhl1$Hu_G2A(-))&yN%#SWU2W!8e&3!0N<*3JYi zFMyn4z^nsWv%m(PIcC<|3$g_?8_TQ@Vlpr=gG@Nf28}uBDF)02OF(vjXO5YhIoY8z z$ILBYJJ>;|7%;bj*eu}8#@q%{0&?XIkSjr_7%;bkm~7xv446CZK~w)=S9XDPL1vDb zyFtPrUHqWk`k_xgNEg_x{U9a-1G6hT14AV{!mXg0W9A6~AU)uj zV}ALAAU&X&V}1p&P3)k_I(|hEn}r>edifzag&nj6lwTR-ap)-q{3+I>UO*M3mw_F0ih+O;hz*`O7O(^{IoLs`7zkK{f?I$cbc%t1O(5tL18}ei*oA{n zF<=9qVjvgkUR1jH6#2c2Rd7!ER2fq{zyWIY$h8Bn-@W{w5y zK}A%&lS%Y5)D23#Dm9H9J@0ouY3nmHD5 z1v5c2$AS?cJJ`Ud7zjo^02v9Iu@H;~Ifnt_oH&qkpr;rJ#_tB5h|oWifq_LZ2{f(7 zSIi)o3@WV{7?@n9GB60HyauTN&n*k48-SJ@6f+2BfK{-AoGX|KD!Vu!=Nt%TffgsQ zf#;S5bN4}2=Rs71=9UHXL6rdqJLsGP!2*y10f;GuAX6mRLFXI@799bdbHENd=RmL+ z#5Q0DopT^q4yv{+*gx#1X9lcPOgHJL2MRw z&}^IF6cC$(U89kKL2xRF%>zzng3~}+K}Cx_===cCIR}E%K}M3w$pmQ&2wxgWyF`(8%Y)381}rSE``yz6xs(Zm(AU83vgQ|PM+aNXzxVjg-17dTqgU(eDybJP=03>$qfwVd>aB;MP z&N2YaWeeU1G1|g7SP4D6sLir@zj8=5yhf|P*r2FUG9pfer>KY^HR z;4>ZszsP{5>OobD;8#S$NbozPVFW(oLGZ_SP!NFUvIT!_<6~gp0X4P+g&0BBFz|rO zei1N-g$Fc4Dk#bbDpojn90Vc zl8m6{bp{XUj0Zs(a0jP=2Xw}Rpe$IXf(O(v7L;Q=#0Q$o=Hht63#uGI&gbG#=VM?H z2F+y)$}@u4khyF@MMh=NzJG9DQDOvT76u-04WyKdI;m~`uwfYtTpTqZ+dy;Kf@X{$He@ba(1OuYfPn!LQ%!@?lSA?VMz0Hgslmn|3o_6GwGsLd)E2_XG#JIB0HL(2vm@w7vn<YT)Zd)({>}7G#+_qp0_!I_l(A>6Q0@$4lVl0A5j4MDkf#$Xa zlOZ}mbK8O`U^aAaThN{HvmgV5P$`38BG~l|LS+nssbEJc@PN*E5KIHB&=3O6Z40I| zG7B*Lt;B+vXfd_QPgWwXzjY6QI2+$c1g3G}E?qFaBWlxYe*tP5oTpS<);jAhK2EpZ! zQyCz0+k)#D>xCH@ctA5Lg6qM$8F)ZLrGgv492OqXM5W*+uqqB7&>0Vcn;Aicu>cQf zZd-5*W4AB^g9H!gj0eH3j1xgcHVD zT#%jM0~KeQ{2;ObOxg>8*!TDu7??qAMv!`V2pk3lENE_9@F*jQ4Vl{(JkH22!oa|D zzK(%G@C29*4SW#koMV31(o;@Bw+vhOHp!fLXexuPK<%!0}tq^ z2tghu&>qPjlAtMaL0+aXG0`c@9jHpeb@eM7snMI1_t4M%?u2JUQB7ANptXQwvgUy&}jpp-mZ{2DA_TvgJ!dZEI@2X zBS^>+qy*Fm+9k%oz_hiAfkDU$#AE}{W(zqgiZd{VViP?4UCy7yKGDy>z--N&16sBK z>OL`BgP06l9H7xGh0h>upIN|uSpl-wZyEyw<7XC7idCGEhhT$BBE~N)ATg4n1D`4L5zsjdsSJFkp#09D`Un)VWncr<;y~;)CNM|g5y*?mY+#Nc=)eQ1ISdSp z5CL_KVg^Q4HjuDJVgUoA8XM^BLc<0@j<) z#mb=l4?0;?3Oe*c=Qn6_^fuTD0xuvF&+u!Y=3E5bat_)z#dw%w}4p7T|K+`IqcF&wrB?bmdCr}_< z0S(Y>5P&A2YoG-5Qh=4g26VtYBbXZ@$jV>?E^M!Z3={@!k~M}RP-&1hs1*#_3D7_S7f5AL4#@9ZiU_~=Ld8LTSAvQcGcf3HfQo|(++c_} zqY)x9i^2JF;+}c~#t2Z_X3#$h)c{IU*$@q&WnbDd(8$pAVPMeLfO0_Io&Z$@vdsW0 z4s!nrBymS1@dHqCQ1G=t#X-SwA0mzv9QsR;r2oOBYv7S`8Y&GcJ*1T&VNuHgDKzvS zKqWy&TR|i_nc+!P{|}OMBusi1%xFU$XheW?gHv!2Op3v|8&rJSFoHS>6QIgK>jFRt znsLr~6$S=9(D*r{4ft{oC2>{;J#fJU;YW+JGPr=MHal?BYl=84gO&iOO=S-@&|iX; zK?_{^IDp%BPb63wT*1YUBe=)OG||WIFw|1mDNpLk3=B$K-V6+k9^jP;S|_97YzIkJ z1_tfzP#xeD^aRQQr63<=NT_jWFzEk*iW~YcFr+}mL2;`59#SlTQqW|GIHNII3Sye* zW55`}G|`7a-x#VKlmu2ol!MZbzBN?b*@uDQFjO35yfaiBr2aNST)PG;4o+`#pd3(i zu&aRG%*hNsYaA5n`um{Lpz=lyCj9_f<}m2rfl7mPyTYUqdHWBNbP7y*1zb1F2Z;Yc z4y=JlgFPez6$hC*8zPRJc2%I#AZ?o=(jaYy=1_5vHgL+?hDcctKsDR~aQoZ=s*DF- z#ffM#KvK>kaLTEWWM$9-S85Rca}Xcg*k24bV2KnfgVrHXwFcp9NW)Ur5^&0jl!2wJ zrQnnW%KZ)>QW+Qys53BVfqN52z`cpvGOP@apl-{jkC1?HNCVw(#yDGzfx!f{7}{Kx zmBB<@l7XQC#Nd`>V7Md8%HRZ2W(r!FsVc|HU}`PNz|aU{xJxoHFw3(t=re)V$}li6 zfU34zs^BK*d4UtcHjqSz~wwDh<*Gs@g!Iq#X+lByho04dsCR{uE*W z6UguVP;rpo|3bw<4p|Nr2Ng_u8sIQtGyxS%pi~sd1ghJEK&cvDwe5v!0HrB6hz5{P zwMC$T3odFDpd665v!RMWwrN1cLGEux61PASpA8iU#bgas926Y8AmT{Dp+5&n`X)^J zAFL?Q--9Im119|dCT;iz8kiu%!Kv^vOp3u-091%@Gl8n70BB-mhWEP)v=|t4LDdg8 z6R2hEBhSj92d-YYnLy3keIP!#w&G^m3CbD@tPEP*pz4ViY~Umie-)_P#>WI|_F5^j zGU#_hZ2_h9#hT!FfTr{s1_u54P+3s8?S{&N#BV{xL9u-qDh{fKUO~k{)zE9GI4C@M zzd*7uD5djjq1pmU=^!=AP&FXC)S+rXb{Rm$L3NKKL>zM6v%W1<2BaezS;o)@Nd}Z& zL6;OeKLM4emQ0{-LjqL(4=A^T`ygkup_aTMtdZ;YO_2Sx)Fai1Z6;vD)w)#+U zPy_iFR2fx!fHmua0UD}#Hf1g0WBbCcN;3=CIPSs6?wNH8$` z05Mu67#Ki07#IxCLk+F;0Yw$KkHcWN7%B*A#DWU1+ImO>mO-Hq+GEM~N^nINO5)_667+gKvi018zG~{3Mn97u&^;QFl4E*GH8MAeFe66u^KCbBPcSGp(Y&wH3mMg zoYrMvF!`m!z;IKImBHkl4g-UcIxB+{NY<21mw_Qeot43}8LGz^YP+eRE(1f4I_Ll* z2E#z8Bq*gDForOH23?2dYzi7BDFY{p3s41%;Dy-&eVE(Jz_EE)os~fsJWf&$9-TAQ zfQ`@L{;EKbWID^WU2-Sg$Xo9(x=VJV0isIB($Mk0<};W>^?vRGoXRs ztIxooH4RjxP6AtUM4Oet&hQ6BCv4==2&AYNR8~v}mmS7BtPEP0KuKUGxLghc@$Eo` z(|oXer4B2Dp*PeJWa|wXpn|HppfniD1j^vxVF3nxU#QDKqc@HEAn6dWG^kxDX!ynuG<*ZeAHhSQ%=)YhTItIf7#Ke>fy{cY&&uFZ zlv={T_!(@Yt|2RfcL>P5G-ii{bOwe?h71f^W}x1iDKltnr_7L*!3~s=gMUGS8+0BN zC|Vf(nX3&L7{IytpCL3in;Ee(n1FI~o)IepgMQ#oh#pYrPBwsq7O0#|g^Ghpz13jx zP;euo5GoF;CG=IH7J@Wi04oADK^gSjq2i!|`#D%Vl+lDiKLaWZs{2?CVRnIPy&47v z?Ix%!*yLwW4yb)<4pth<$$SB+jS6z(JE$U%8~q`QKyK7$0bgvvzyNAii>c&;v;vRtsp)f+iDii*O&50~%ZY1JMZ` zUv^;7zX6q;;={loWdsV8P^K~oP!YL49tQ^gACMP2Neg!@FuW0B!+iF#X*5_3M>vu z9%rE9AZzrmKsg{q?-7dh??A;tma!W{y$l-lMv6~)sCY30gZ2g_aqXQ@4#-#9SD_qG zlh;rQ8WAA7!L7*-XnFxvA)p1PI-f!LI18L<0-%Z;;l=PgQ&>KRsMu}<8}i6v1~nq> zjiEyxIn1CoXN55YNW_dv~= z*-&v%b4JVr5*(o9y$31|ihEaxIL4S4xW^X(N{9N_pvpmISQ11zQ!#89=>t>}6ctra zNl@CB1m%DN{2D|Na+>l%lKu*lMhtu9 zAW4gug1ybjY<>uq`_$iX`n0;RCX@~N9qHpGN>aN80Wl(ry7Wg{idu8M$e(jG9aU6C&ho$Gu zU<+z3SQ)e>ptgY1vpJLlO3&xaz!As<>iT#?#X()4XHaoankt5hgVM98IaodB2pp(f zgQVvvQ01VQ)P*Pq$K)I&aaX7~$oL~raZn7WK*TwjB|z)mLE)=^2T8gaCXEta5KrTBDQ44mNF;pDnf@ctMPUc{EigtxcgS7pFNQ1N)213O_ z+Q4ZKZY_gz6sY0&1RMbcP-Td;mky6%h>BwttPDowP-URB1f3g&r9E>{OZ+9+um-3q zMB3W|Hw>cUKgh5ys4_Ie3>g@l-+|hC@4$vlfT{|Dr@b~SSlWZAu&{)twGZI5w$_rB zK?^*{_mLSiqhD=~r8NP8+&8l2V^Ksg|n8AH^9UA7r24st;d zM4XcuK768o6e0;(OC+pscdodXq|DlDMoMy0l}7*+*a5Mjs4pv}Pm85IV{unv?1 zY9uLGg98QJ(qXh=&<}>nfnw7iDrX96B4LlsMyT>rJ`4;+5ar-D5~%3{>XjNW=&yk) z0foavh!Rfb40vI221$AYOxgi1{RT<;JWLv%YqeP!A^rkKpa7Huiis}}wV;^LSB8p% zTp(-%_Ae)M4Lk{1LZv}|Foj4%{J>}c@q(c%R0YTYw5W9E1uZcN0f%$~R2ib!mC@FtPDo!P-UQ;i&kt%fr^btuweyIRfuBaHQX?Wid!JVs-Viy3^Qb4P=46Qz`*Rt z%xufRAPZiW;|N}s<9r=dmBoQAX@Khf08f084zR=rQ6X#(O>7C^#MWWY%Af@@j4_b~ zq*T@cme`WO2EKJ*WpD(oF1iOb`9V4Z17j7-R$B%JlLScy1~W%i1|Lul3RwZO9k}BZ z=?LvOEd?<^9Vd`#1_n2f&$YqJs~H$HK~)T>3a@8zw_{*1`EJ9&@X?W#!53t!B~+0# zR1vd>5l9WlVi70k7z~CQh~?%^P|Kr143Om@9SjVHF!e9NV=$piWgH9)+Ebz41LvnL zP!6cX%(erkFGdFj{S#2(Up@>B%@AR5>+up)9He{}M4ZusL7yAyc2LgS3l%m)&Up;_ z8c-#m1b!8w1f0MVki_32iDy8?d3+fdSna_M1G}IADh@J77D;>#R2-DfZ6M;D%p2f& zaSxJoG)%e!E`0|{x(X(Z$f}SX7Yq#GtjYjBN)R$pKOdqN>=R+AILHP2AmW_NJ@Blm z3Y7*q{2D|Wtj!QA4$}4&B92td>pMfGLE3m7AmIRNl6ynNLE)nc6$jNh!BBCKdRv6J zVLVhEWDQz5z{LT&oz(dbXz*YzI4cxD6(g!@4<}gOf~cr>g66IH;Joz(#0BjMWn91l zQhLZ4mbVsy4V>h{%Anl}H5HujmOwe6l=KARPDTp`{T)zYIbQ~bKM-MXw4Z~DgHo8b zBRCis&5=`>0V8rv{RXNW6bjA|<>0{n0u?v#WnhSgii1M!4^$kaz8Fbdg#}VZgFJ11o4~h;fn=1A`)Xz?p#+G&2HO z+X!WY2VWUkL4&W*Ay;-*$dIe^E>M{-4mRTg)WYx3;)`)kunPl&9(Y7coYjSyf#J3b zD}x?*#7mqNG}>zA3cA4sRG>?;f)@D|gZQB!6J=OIBV2o2Ss5JtoER9CSs{@i0J>L3 zl~vV+fx)EQiGkt1D=ULZwi5#blN&38NxTySgM}L_gHu8V1A}Rc69dCcH&zD6B1wb+ z8$bqdvaWCjjdn>gFzf;8n+;?1OENHg2Z=OGGB8NFvobh+$Y5ab1IdF46OaOXcUA@y zkb)$4RtCc&sLPyuK@kHPS!D1BSy={^QOIOqVB}&o0h!Vc(hFyROj`>Q0hxBeot43} z1FDA`8iZh5!35aquQ0_D9;^(8lc9=HtY%=){{oE%&`^w>3)orU(Pcv(s0NS(DC2;p zB%LRMf?Janw9NPdR9+TpJL8OGcH4m=!IjEIe!TJHJRSq5^KR{YRn^G7nSV5aoU|Or-TCG7^TUZ6yA%2B55E$ne zyD=~Xg0{Udwy=V>y}-1#!L&N3gB;us&Y%WR?Tg_yGq}Ubbcl*1PgVx4BcSH}L{`um z^dlfXIPfNc4Gi#NWe5TfF|TIb0X0oO7|H;xLEYpAN^j6rv7jNjR;Xl~F9U=23@B%! zF9U=A5hw>#KD~x$L#p)*&p@S@fm0QD(2&8I5!8>^2=>easJsB&GY)XiKvc~1Vr9^N z2~`GKEz97%5)>Fa!1_NxC2HaNSHtx~R9y0cCH7rl&zpF&GH5$AwwT2a9?zFetqM3EX7`Eh1O)0I~0Z*$lzzAX)IF-ZO}Z2Sn|6Fb|^k2bj&kz~Hejm&Knj4oNS;1pVNnx!4VWr7ol1MK!qD0+Y3(y29p!+ z3=HW$tPCc5+!+|Ud{`NrK(eM++!+|If_M#?3=F3C-5D5+eOVbyU%N9f)cLY9IBdvd zV9@tuU@-mT&cJXQB*o#uz@X{J%An872?^}&z6=b)p5TVQjo2N zbD#onPbt6}91QmQ2N)O_tw41#C$m7P7DzpV{Z3GcW(~@+oXi|BK`l_xZUd^#Ihhl@ zAu2;ar-X^D0G-7X0y-s3WF?q+29z-jQoKMT;OoQ~7(${!$;9xeI0Hi{= z3=9IGW&R9d{9OzT+~6$?%Rqvll@S797Fe2#V~rQ|fEv&ueDJX}0-*Uwi0*Lx=?o0q znV{~w07xf81nB%R(JSC1hd>QO0njEAFu@R(0@{(I3EFf63JL*`dlTR|P;pgXBCg z7fgU=OBurIyBHYwqCob5)@%uYa-INa@09>Zh#{hN8UuqEJ9u##XoU^f9Z)IIv1#J} z!0lqt#1vSHAv_Qi$R?oXE?7XFAwr~>fkE^V*yo@@eTb(+<)(g2vw=o&$Lc9IOnXGeDkeU;?!wAsQmyGBbr*%mkex!30_eA^;k! zWe8n9i-AF~g9$Va1{KKyZT;(H0u5aWfCkwZLJL8m(7zDrsD3lnuI6!+LBTj=Xj0A@~Xp90J@(f{u(-;_p>Os0d zJyWDe04JgFpgsl$zUW^d13|&g5PA&c#cn20Uq=Acb!P~jF^z#iu!jlMO@NBXg7!7` zGJ#t50-!DkL+D$ONFNiZg#-}^Q|e@3;EM#=8{^BsAOI$yu1)dt4XtT91OSYv{mu*L*AoQ(-`&>9mH_})3_g>#^T+MpNCftXB?v*1`c z*g%Ub8JHkPt+8={E&*eL9Jt2B!9G2Wfq@xxI~WTnbQ!?6gRvNfFfcHHZwF&B2HnZV z47weR1#&wW=u&7_b3ffJ>0lrb1!}=y80|Qq91BdM`(25nV;tU23yDMT03>;i( zc?=x(8B7ce0-T^5r8ywifN_Ful;&^*c}js3G?q0|)qiG!6p~CI$xZW$PS}8=^Tuht_gHZiwatZKmab+z<_Mpee{O z36LkvK%P|K1Z|JzFb9chaDw)9aacS6Z6{@5UhmDoz@;7xI{eOpm4Si#7y|gBT zllHiI7(os$5CEOD$1MQnln8iuGca&NuN)KrowUa-23n58&>#RhX^&eRe9wK80O+JW zZVAQ;76yi90nkZ%+%k+EEDQ`C0-%%jxS>}L3V=@92C;&QXj~jaBpaAHkJ#Ofg zg96Jzrx+T7FWTH709sMc4ZU(u0CdtGw+Yy72LwPT?Qxqif-Zz&aSrxosh<{g^7WI8x)-&XM@uR z=vZq|Y=TASu|O|&5@uir-MR<8e~!n8X)g-{gKz-@k1tb$2&l;czLt$w9hB6-*Rt`N zf|;Ph_4p(}(F(qnjSq1x8=v$SunT^%Ffed{57*<9)dn5923o+xCkH-ajT5xmg-;%I zm>cBmeLjVkp!4w+z|AiV_Yk(6wxQY9MFu zaDook<5Qmky3>slbhsX$#%9o2Z47K2AOl4}8u~!zyMYO?-E15n*RXL^u`)1-fC3Gq zfe$Xv2URqSm4Sg7bS)d7CP+Q_S~foI^PmITICio!FmQkm*W;IY2XZNBr76EG$VCj0 zYuWhauCg*Phzjs4NUTff;ly8^7{Tka^(4^#lx) zKeQ z(D5(=!60GKg@Zov_5w`3M3|yd?7l;J; zi~$_;0#Tr&+#oS85dD}9baw&ha6JLYwQQWA!}SE>KyjzRzyUs7Pavs^9hxEpl0i%c za4HZ;0lAHX6Lh$qKq`pM!wEX5L?8{s2IU5j;oxi81kyoH;Q$}5Cn&cEWE$vjJwbVp zN(L^_wQPb47eOT-_<|NeMUWDZ3EiMu=Rk++2`Yh@9N@$C1XVtPOaN_97gPl?88|_Q z>j{EWB`ACjfpmcm*Ar9+wdgs(hwBMxYH%la6Mu9 z08R!5PSCusumad7PSE-pVMP#|1zbA{LvjkIZY=|YurkQw0u0P+8yFabRfIv;z=03f z6OmsJG6!_rfQTwcF9Rp&a6J(t5SxW7zkor+62#=-1Rbs?Vhsvz0Z!22dLlLtI2jle zz`-J7_Y8FO8v_UUa6O(NkPIj2Xd<3qkOl@$(BXPKAt1H@C+L7Bo^X(%pqs5g*0XU$ zgRZLs9j+&04`OnF57!fM)Zk)Z-~^pkC*lMuTp2h)hwF(rgM=YjK*U9l3vw;n8%|Iu z{T*Zu=x{v|S1=QFxE@ag$PNzh;d(q#`5+@fM=0?`gPa4oyND+ag#SAxc$!v#Tn0WmkEeAZ z)MafDmw}GX<7o$l00$(ZIzVUCNpOOW&g1C>i92wDjt1ZXC*uH4(9wB3y`WGE0pE4R z(+5%my_Suq-wAZt9r)-xo{3+%85qEck!KP}Jp(u~@=OM?SvWyQKk-Zfu{k(FCsgrF z1+jU+sfcG9NGqt|SOdCR4s>)L&vX!z19XUp0MATw9+=6qK)M(pCd>x0A=#5>4oC?o zqp`uRW#gF(Vsd~ECDGtnFc)M3=;%D2g&ugcou=!P`51xu{pp^P@W~Am=oXx z9ZJQs6eJG0&536j$QV%K^#IudIy#SMIf%&tK01$Q6(i^foMHx^)sV3Myo-T>XAMX{ z3nUyjfY{J*-v|mvXr|Z<5?6qP`xX!zdMz8z)^(t(?!ZUq@f-x*3CC5;z;g&k0M%$3=ACLqwIKYp5|p>-~?5!Jhwn<7{HY)&utK!1zfrE+ySvUI6+6* z@!SQO54moJ=N?Ea=vuZW&?R=DqwIL@gP0uPqwIJdaf6NuDrVq$4Dk=>C_A1fpw)eHoHFBZw^yz6y;;k#P#hwczxk#0W|-3vP2VI5c%Fn@)^Q*cpO38Uo-l z#0cs%&}mCN#^4Z&5CG>?6R=DI#5Pl~VW6wf-hymn0EL*KKrjb%6&g=4qbXcX2xA1OI|@+~3g&>WLgR^Kj0CC4Z)9NLiDJA6O3DJD^4eJ?JVlo>Z_S6$C)X#qp$pRcL^( zLgPtiOaUDi2T2MUj3waX;Q7u`{r7 zfCR)q$Hnn1XFMwezY2|K9V6%hCIQfKaXjn6x)}sO;~_j7z#J9<(4k#Co4~3#1VG2d z@oZ*%26Bo3XgZH)3;4b^2?5Xq9nV(APoU%C1VHm{vz<{wn1R7S z;4`R-*a7B1u0rG4&G-*|T%0uM05hI_j4UFc9raLMx$>@N-h(9jgmD=tkE2EGc7 zN1tf{$jOZ0tI&9inU;av47m!8$AW1O_?$QJRcJicOou=Nb4=i?(0FW_Kq(x06&jB{ zm@iyG&eB@1`vj)7*IowjUz}5 z)JHG}9SF=32WEp>#cUi|U^b{-#l}$vW`kOAY#b%vG?4)&`@rI$wh9}^Z?FlAK?nVE z*zzziaBmZ1U;tsTeQXSD992Rfn+}4>i@XdBVxV*0c)Y*^Lu?$NAtW}A#o)0X&=4Z~ zoJNrHXJ9M7gUOemTP4`L85jga&6UI%Aa^c`T7oQL;7TqnVi2_gu{ju+g}NCSM6Lgd zF)+xSiUf_2gPKmT;qiB%k{>*64?4w6Hedlr0f_m>pOrxtl!6&mL8F>$16UbkUxO|r zQ_YuPVDJuPWsn1}Fi;2C@(09CNn&8sXpmrFXbxg!kZ*yGzr$8x+z@AAPyin|VG6n$ zM*)1^1emGV4^^rv!@!`#2;QU4pnMQ2=q$s)z###Vs9{iI3}G}>VhrSD&NvTQW2wX# z1X`)1atx|IUWS1|`4yCtCBwj=1JTUr#-Pm14Ver8nHdHVWol**HK87uh(fB^Vg^K||4O949~lyP<%AfzbsNunJI}TJZVcN1#K|zya$5 z3fS{OtPHZbMj-VdnXF(|202jBxq*UCI)s%$6?}J^JIJug5LO1c{9*=1574<;M?zQ` zl*6IsgASJBkpzbeC`3XS9T=2XK;>r3FfbTG!dNJJpQ8xb}ypejLOqs+kr4N_SKhUpN~Knu-OM4;keMJiAZ$jaRi zMT|BKindT;kd-*CV_@TW0lvcS4g)9)fReh5T?zvOV+JUI0-#33!jtg=DFy~eGR^>n zc62B#-DZGdZaIhxPSqJ8g%3hm8B`yEQgs$68B2z-GAK7ejRZM1LkbeWZVbw^p~4`? zPJ#%7LKU7K5#h4{suJXN1~v{*?vZJ8VPIga2Dx|xR5lgv;vi|5i>pB{P6>m#xEkc* z4InP4;lfxAQYaJ-b8#)m#opnp49c&dMuJ={B@K5mGw1+f&}s!41_l?1Fb)@UKvhCr z93c$~={RWy27b^Kk<2B~(J*}=$16Y;A{@^Tx;GG_d1KR{ftFtAcBF7{V9*7vO^|rZ2%gMh zV3@1|no+%J51&!BgU+Z(F|i7&`#3-otDMk@RRhq3 zDAL3#7t+Kk7t+Kk7nX@tYtW=DbYk@ZXi^F^vC0lQ=#(2gvC0TK=ad^fvDy!sSmi>R zSmi>RSmgo@E3>;=4PbzSMIug){RW8WH zDlf>X49vlx`6;a^(6sFGe$Z?xDCP2lS)hEte+nda71YyZ<2VWmUQl$fae(%X zgARseVB`4Az`(!>op_A~UE_o_@ydlX@ydlX@ydlX@ydlX@ydlX@hXTs@hXTs@hXTs z@hXTs@hZ4ffq?-w@hXTs@hS+Nc%2NIcol?Byv_toyb2;uyb4a%Wnh3!yb2;uyb2;u zyb2;uyb3}mUgv`*UIn2Ouj@e*uY$-EuY$-EuY$-EuY$-EuY$-EuY$-EuY$-EuY%Bt z*R3oJ3`ZD*AQP{g(1}-&C%GUKuOKD^7i8iU`n0S1cp0F@5h=3!;DSuNf?UJ^n|KA?fF<uiyxh zhfKVJZ{y;EOuT~B!Y5u8*ccc%p%bs2AoZZJVF9B_GVuyxGjTyCUO{XYF3?D+ zfG3CzpLq2FnFF18-3c-WR6q*&ZU>tK8YLC*0|~Q0N{*9k&@{l#!0Zp2ar2)7(hr?@ z{Rz?!nRo@cm4O>F@d^?KjroE!LnmIZASPZ#K^IMNNQ34ip%bqu>sMM)GKk3lP6YxfAh&UFK_*^7 zY#uJq5T8IAhz*+k02vOQcm+9y6FTww7-Sk`;uWNlfg3XMdIMChf}JI(2vPzvVH0Qy z6*BP(Vsb(!UL`@(n~;fD5R-umGVuyB4HP~vLAoFluU|mH0iAgD2k8P&X=;H~GH`>Z zG_~bHbCIx#S1k?(22SY2YcWVYcrsHT#AIOL0h!Ru0gX9E*u*R7CU#Ef#Orot%$kcn52dIm1Y#4Cu+!VR8y1u;3eAQP{k z;1=M5OuVvlfjZluGDyUZmy3ZxgMkw|@d}dR0uSm3gETO3K_*^7YymFt)I>PQP|(~J z$a*%8CeVZ_Wa1UX=Arr44J2;^euhT$Af+ruMLC#@-I42I|9Qed*A!u?II`Mjhi-Ca~H1P^5tssu! z$pn>5@QK%jAl1-`*C1|)>O6>Qq={E9$iyp1fdJS`JcS@rB)A|GuMyk~3>sXJiB}NY zfD38jl?!R&l?!R&l?!#^735Slj%ZL2fbJ~gX*v&b8Fb?H1k`115SKwFUO^$i0g0%N zG%iqC0-1OPi92vXCSE}y6Tk(Tcm=UTxWJ*;2T}u{c+CV&wn8UfWq24Ez=@G(5=cD* zI5F}}2C-SVz>_9ZKx_^!@Z8Q+5Ss@anLN`#T0sTJWyHiQh{*|^c#VUbJPV|Y0b;^z z5F3&`dFFtWfHImcXi5}xXBp325R(%+@p=Sg0%YP9q>F(IGVuyxL*2F*#OB}vHBq4x zuUz2ioTVUf35YwFfs6qq-h7ZPkcn3ilM_1es=*6M1*;)p{TV#*3ewL43C9f}HZqcsOsnWim3W|zC)^h=)`M3XmS=h@#@FVz#s@}PVfjZ zf~;W>1eb0iU=E8QbmA41wKxQ!6R#kSfgq?!!6VKH(jOrLY9H`OFou98UImdSUImdS zUImdSUIjtLE{_~z6m;UX1w4NNay}c!em({Uap=S=h%Et|c-;+hEjYa>F@h2dgCMw! zR%QgbNkR}h@d`>N4uas!sS5T-0K_siMv#8c#A^;e1A{np;uXY}fK9wI2{14Sf-TU3 zS^%AR1=%J6F-C_GqzW|gdKP31bmA4nmViyX7J!Tahn)e~X$+9CGX!%q1i>+E1a%rH z_FxmQg5aEL0+vaD*k;NIG7L2FDk8wZAP${)1+gVy6R#KHwpoHLfZApS=4c2)Ctg9J z5+Dejcm?G!s0Fr+ApM|;*Cvn!(1}+NTM{(!Dk8|hAPDMt@^~|XQY;uX|3V-SQ+yn;9!AiH82mw?njCte|TK_^~892N#C1s)GZJ|PAMX>cGzCSIjM z_jK|2F+LVxV2}ni;&_4>L7tQb-P6Sr26n$R=$T|5b3cQQzUCSDCdHbEy|K@O0HPP~HH3=*)3*J>dK1`+VYE6DW>B4rFbsbEJc z2!e(HdD0j`Dl|ku_jK{3Gj@X}ULi>#gK;)^;*|%~asr8idvS~mJnvZ;7(h}`J{t$f zCF0PDR}fnQHu3sMh=D;6dE!+NI`In9&H=F|j}fE_REw<@Vqg%5PP~HH60nI^O<@KG zLFmLQh|M4fop`kbO}q+1CtgAR?qJ{nB|MNg*mabmH|3Xc`)FPZ!TR@NHCr z(1}-&ZU#ZncnQx2a73^OLML89TOc?Dp%bs5YpMhUp%bs53xy;Ep%br?A`A=)g3yUq z&}CB^g8M*K$96`0(8Q}CbmA4nIUobt63MfhQB?$VPnQgMMsXjbHhAJy26RsseBxCG zTy7m^s5Z$nk^n)rn#U&E=Cz}vJzzig*^wO z4CtCI9tozE;OSQx&^22;QcUZ?)2}k1YqoggnU;g6Uu8hoZ1E^DZ3R!i%7Cuf;!y$1 z?O>DvCmc2K!IKvlWkA<#@n|v~1W&)pfErdjI!wpG)2}k1h8mAP(^-&{8D&7%Z1ET~ zT?V<2IH<+K#-RkBcs&IsQ+OB{ctFF9 zAPlw-H2n&?kB^Px1$g@P2QLGIICT0IG(5z{0UAhR<2VH#a{>({azLhEg+aGjaj1Yv z&~Yi8(CJry&|O!c;zraGWC;T|Wcn4vhEKn0fu?9T%>j*nM%9DVuL2z!69Zy0aNPv4 zV?k_C0|;as^IVWnT$DHigKQXxIT197&v-_hfk6&DcQ_Hml+P<>V4QqioPj~%nJfbX z;}no01@Oq~RFLfqY#f)w85jf}gQw0~L5B5#49fwH5HT`KzzoZQ8zq-rHt zl_p%(N)QulWHxxB+tOc0UBvm+zZl{ z0Gm^Q?5kv8V4QOSG%pMCR|3e$PoOEHOb|B-WDba{vJg5C5wFa^pt1^yqq-T1qXxY) zi9vmWI0M50s7woJ)CfG?-6_eyps_%lfuTH-l|gg0I0M6f5M!%214C>SD}&|%aR!DJ zQLGGF27HhiPF|?i7??(#XjTSobXgscNje}?bU-Gkm_l_IDucA^o`brhMwx-3M3R9) z4YCnm?>3S+RE$CWF6bIusQxIBcCf=$q!<`9-ik9Yq(-wcX#NmqV0atN%Ahp^s!#~3 z@Vg`fgFZ+ZW3V>5oDN8r%3P=psMF%0T#(cFr4UZbMiPfR4U~2^KqU}idI{t-VbJn` z7*+;NISB>^p;%T1EhTVZ0LAJi_8CaU|?7o%gUhX1xmYdtPGkV5)2IeajXnl zE1)`H5g!0kuNKeBpp7o812RAdq*-MxR3p^weo!vR?HSVWfYptLii4uL0ZBX^Dh^UV z10k+g2^F8H3^s$&P>m5Z>(5}|S^>J94wPFTK$RSZFFxp%WneG_&(CUunDOzf42GaK zETi^v5e5dc1Xcz^Pzx2p{RQHJn#7FSpvjnhiL4BU;L=2AhAaa^QxYqKGH9iO-aJ_b zhR9@A24(OH5PeWlvm}|7!3ex?!2skCjTBY}-CO}kysuGaV91dHJDRD6L5&e~GME}; zAfp3=ZWEG{eyEaKh!O#)lBrN7AaAdRD1muh_a{{Dv@!$3A*h@igRYPu#Nvm_3=D4| z!c6rbQ$ZS0CXdCTDnU`DVur-gb%Jt0M$5@U9AC!3pc@Sp2bt{w6~}6}ZXc4$dORxC z7=t*OXF!*;FsMw1Y6CfmfsJFbECYi8XkwC$15|vnaeM{U%N9Wl42%asA-w^roC7rZ z1uh_J6kt()5ESLbDXa{J;5y?VNQN&J8jFWPvA91K7HvmBhRLR}G8mKa4$iUE^$;zP1C4z{AZxHu@;$0ak4DyhIA}laKMXaG3 zXuTO?!HEV`B`A0p*f>lS85jgWO&T^1P?5^U0V+C;vqKmd7=^*>I31wsK@*oCZ!pgJ zrpUlx0xpz=89|;nkjctm0xoEU89}}<%3@_O0T;`{j394hgZN-?2s48Gu_TL?L3b6@ zSWv|5gE$!EkHb*mhsq2LFCfB9#aPR}6Ht|4e;8XPgDMfQgC9U8LP4$t7b(+~U=G#> zJNPchL0|`KgB`4y&B|Z`cAz%cfl1k*fEGmrbi5KcL_rQzh6;lm*a;EF;XnPwyXFyeg9mvLErwp3ta8YJp5CE+JF$J9xBDMlFooxzPiv(ht zfmYjyKV}5!;RiKw8O#xDj4Zx3GcbsTfrLS&CO@bZ&0q;yVkCYAyvG?-mGXmH`Cx*< z;?f)j1_@VCP62s>AGCo0LV(mTSb~-;NkEn?fqcUcQpjKhS?>fYUim?(4=f5^L1E2c zfmpL-s=~kkkq0@4AI$XviN%A65)jb|B0!Z20~?363Il^ENF`Ve*x_s(0U$w;rC>dv zyv@MIkqQ$8n*mi>1rh`~8f+$1uwR9N!3GjY4ELo#%aRzJ;meX_=0Xm7*Z_?LUT6hq zv2{=^OJW|32R)nujYL2WdN?8mKj;CpLxkLe9&UqLBhZ5$9*QwAkbcku>|h5A&F+t@z^1%*VUsYfSJ8&T#?7)R| zumcy;!46zV2Rm>f9qhpMA2cWeIoN><`Ctbwr0(lp5umczTU*nw9T zwBiW7y`NX^Fbin)8fbkHulzZX67Y6dUWJ=T2Rra8r+`-QfL4?6s(_e~gB^I04tC%L zZ=r`A?7*wu0$N`LKG=a*V=;7n5y(LB!47qxgB`#G*lsoskZagDvS0^0fHd&I<@unB zx?t;zcr`)l!Rw27wfBS97p(-Xi^{BJU|`{sxd(D7XnheM^1%*#az{bSmjw6}xIk-m zK?7$xk5(bUzfi#2H7x9P8g{?2*54#6G*x?Dt81VWc{s>*rLLSiiBK}B_ z&mado@JF2hDFMekfAnQG*uf6`NC!Lc$AQ*{!47udPs#$V(gCFi{$vmnap#J!s92pgxGnz`zYM0d&R#IOd@1iv$gnK?{_? z>x%@NJ3y<0K;u zb`b0a34?TjmLf5O))xu(%mmpBUSA~G_X4B~?ACq|lYxQz2nPcL=!^%LTS4oK1Sgya z=>e}V5|VcStq}sPFA_pJ*g**CU`VgdHV73uHj+i-es(g)8J>2VrNBFyvqd zVHeOcB2Zep13uW{ImjH)`XXUhFcY-Ch&uvg2jpM}?x=W>k)SDX?r4y6AO|~eBOmO* z9d89X5COcth&#O(v>vILfja|KT0stW;LZevcHk}mDS#a8z+DJ31#++hcab7D>|h7(Vh|g0umd;J!4BL=2Rm>h9qhmjKiC1} zR5lJ3*!m*wrWqiYf!7ytw@!q*tPSEa(E1|oc2EdF4jAC>&x;ON4tC&1I@p05>0k$Lq=Ox}mx3|}x(R4>x;OTgP4$m z9k^G01g&B!X5d~832V^$B5tIE9k@4u%z}pdMo>6HGX>JY4&2BGJ8*BE$HTzD0bXCk zeJ~xg(x;e#`w+x*a9TbLO4g8r9k`Ex$_)ih(E1|oqo8sGax;PWgP4$m9k?I;hWh6*#6O_*MchaSJ8&Z(?7;o}3}}%OXnhg) zYeCe59k}1QgI4~4R-bSq9qhpU0TkMhgB`d(f|P*L2WUSrczqG~ClC{Iumks(T965# zs-GL_Us@Mn2eq8~I=dZaGF}==vg1mx+x7Lv$J8&z5-2^$R#c12^))4&0_-!$9kc z?t*LstuNv>V+64w2Rm?EFwTP8W(l?cYMT|913B1%+Zr4y0pNojxNR6gIV=IZHJsZP ztRJ+#$QyRB1GgO`h%Ld!?ZtQ(WH@L{j2ro22X5qp9k>w(J8=6mGJ+0U0L{&CBOmO* zjX2nWJD5=wt|o*LwEYxv_ycz+m;*Z4fjg4X6Ql;TzK9$7UumiUr;~Ln(4%|UtPl6A2;0^=3AAGO_H`2il+(-vI zaL0h%4?fs|I|1xY21yp~B*vd0n?UP}xRDNa;6^#vf!m$Y1+=23lz}@D?0V3_4&13= zM=F31cHm9}tAHHrz?}|0l)ggB`fnF7Df%_zw13B1%8~I=d?z4<>(Dg-J;8i4Iu!9}A&oP47(1RVgpD?Py4tC&1 zKG=a9y0Qp-umd;p!4BNW2Rm>hAMC)5e6R!e7jQ5^4tC)F54Hw!umd+2lMQH@4)|aP zZf+(I&?+4P(8?li9;Oh`$|7md$|7!FrUcM<9^_yLZc!#ss6!8S;Fe&@0k13qAMC&_ z#Z&}dSp+`Vfm@!b0KBqD8nm*ATZyRxys`*UReY_*nwN0X#&W}kb@n#jhUu_+zdI`f!l&<4R~b{_+STaYo<-0fjP** z4&1g(pcD>0*n!&~%!3~6!0pHcO7PHw9k^Y=Jm|p=+$aY-a3cq;u43az1G7ObI5v(HaGC%mDmIQfusEo#!p89$Y{F#l z$|BI}b#BlB79b3^545rAG1)V?e z88k_&3>vD04Tl$k#-YHIH=xN{$c}hr5Hm7|l|dFXl*Fj~NSuLTDToVdLoh0XR*T%r zVP%l>Z33MF0SXWCTvi5o(44>8XK@CGq+C`8g>TUD_F0k)4D-bq7!(;p8QmBZ_{AWz zJsTt$7*0ZjnTi<{LHpwsQRdH-M4&3qNP=hlK+7MXXDhrAXJFt5jRUiBfCgOIICg;i zcCUq5$PW$-3g@75pClO=mP&yA%~S*O zAjq4bN#!yI2Bp_fMYBLB1aOK&f@HrG1A~GVlmiO3YY^?AVDp3ugMy7w5^O5y?hlZi zhG^mD2UQ9RH-$PV=L%@i3q&92+y$i$s5n^BbSMX8rx!#KqYZ=dHmESjPDRKvDJ=Fe zuyIr%9lQWaiYlNPEzuRA0aO*xjF#w15K|R2%OnPwWda!q*%+?|nPmdGgAWunU{UZm zx;leKwj2Y41bA1yD$?W@C}hD~=fMPnCTK271iXJje^v2N5M8 zq7y`bBAtPa!$1nUf8HJ>2qGBRI9#RR`{x5d@}Lv})(-L|Xj%#;2sRF?sR|?r%5Gre zp@RKV3=F)W7>4Ya*90AhE)WSaZwtsiFaa8eV_@Uh1+o-WifNweWnd6V1t|hW86VhE zE$Cbz#2g0D4ZSQ3=C1H5E_3J<*8ymR{e`CcecMN;xNx0i4w?;O7-3Ufnx60}u4VC~ zQ(VEI3phrnxWH$Zk4|w3j81WlPH{0n&MqIF;u@XeVqh4Z;)0xAK03uUI>iM(yL@zt zYjlchbc&0gfnjutYjlc>VRVXXbczeKg?@C3Yjlchbc%~%uuO4|E%A}T}2vp@h$fVYJ&`cKO!0=ljCSp=c zCRdw*ff0OsssU6FVmd1hepI+A$RFoH4hL`DGzH0=FM>^HfiJLJS_{GlUg9x@PQ%-EQ)_p3lzTK`7AJjJgFrEnk)jF0-n?Yb8SFk zfgmCcL{x(aP}DQ9aVUYNqChGQL4qKHfsMl)G`|En6xs^CI~hT zstI%|tO#gQ3v4`8un{z=1&U|LWE9e*)-sTNU;;X+1=@hmk2a|Via0*7rD&5{9U-87 zTY2vANv#=+Ap5osK%-3od@=$9gXJy;2BzRH1_l;R3((2Gpc7n~ZcJlfVAb$oU|?X0 zX8^U&85me2z}cP+a^)^N^yp^L3Edo!qnklY1`f#4%^)@l2juAH#h`OAIUq+jgV+KL zY>*p%IXRf_gX~GN1x-jpuKQ(!T=&ZbxfqxYa&a&dXipcr!F8}QjvMw23{1jl3=Hgs zFF_*ETZ2JMh}n%n7Bhja@?|#%U9HUqxo4OuG>d_OLsbHFsV(@dXin?Bkh7vWZ9Ul- zK&P!`FmT$1f)@IN&Wh%=FJoq45a0lv70u}YaC#6 zI6!Abb9#Wp12{luMRR(B%!y!N0^O9%bp_-Drcgfy25$Asj0_CSpfj_XF8VVt@Mt^+ z@dYFp7?@saGBEI}e+KdWH5nL~VoDeo_|%!17#Nr}j2Rf1=BhC;@T&`h_%Q|y3{2I0 z3=9Gq${@Z|5d#BLi7EqwpsF#5?*$6kYZDn5gp|Siw!kM;a~dpSVqo9^Emhz&0y&$3 z19U<)r!k1l!2vqphtmYa7JvkcDaZ>F+yxArW}u)|-~gSt#Ayx^)!+b~P|a!K4+FPhMKYz#v=;j=a}%85o2?2ca-9*(_jS5UvL+5T4J#AlwL6(6N|-LAVJl zn(V^BAl!VEfq{WZJ&}PyxCP97x{!fExEsuz46S)gFx+Xxcd11i;dIMy&TFz|yC zCJ)DU5EJAc9u82Z6WA8Vz`(+hrq9B_AQ%NY#6xhq0Rsc0;G#eV1}4F)B@7JAg6Bb} z#tWXcWnf?xG%#dfU=uV1#$-U|>4Cn}I>pz?Fr8fgOBsx7b(E{vZy}!QEot zKptS=03F;d_8S!I92}s7yT$&1*a94&gS*B4g4hxqpo6=`{(;yE9H4`{#r}iX8bY9h zyTuq7K^y}i(81kej9`w15a{4;F(xp_K?rnkw-^hU;~@k(xLb@B%n1+z9o#L(2IfQv zfe!8#<6!*D%D|8y1lm|9#>L3Y#=wvv1Uk4|jE52A-~u7g!QEm4U`~k;=-_TKA;u?6 z3=9=Qpo6=`#27(ENrMomh!ztEUkKJD1Uk4|OoEY-2w&%31^V6R5%WS)VP95BMpwbAT?f~7&PF>o6Eo;;m67h zDrqZ0>;Ta5Bn}+gK=wu(gG*X2klq*&JAxw~q$U=`&fxF^v9mx3U&b_>oq<8LAdNwMITNciC`6fdG&3+r3xI-^33T+ij07kzFbUN#Fvv)Pk`Yr; zH3Nf;^l^4jY=N%YWd|R9E+hL7b^x}F+%nLWIUJz%WHRy_K_Sio+7=|E0J-9qeQgy3 zgMy5*J81h7Xd{}83W&+T0Xq6zMit}%4)CGZGHM`a^KgKUK9^BX2c5ji0Xq6zMx%{` zfkA?ShXZ7w1V}?72Ll5im;l?&!vS&)4~H*E9Vm=I8W8e)P(`^M3=B+nY8e=0G(qZ_ z7S%B@$Y?JD-S*4T!@D!c{B zgSIBhDozJoguwyYnkcITQp>@>1iEutR{0FbJn+^;IYU#>hAYtS2|1&Cpg`vU9eplm z3}Q2JfQ~+wGXb$#I6!*|vP8xm++)B(dY7V{UFmoTNCBwK`J3f zpUW%!1~>UY2~u7Wqy%I_92cl+sbyf0R{}BF!AGCVs~iQH0NR=;uL@!^aDa|Jmj|ax z4h9~MsUTgTt%>sL3%D2<*uh&96*PZ?bb&VgDQH3L1s#2^pnV!7%)q2y#K54Sa}A^( zyfsllPad>e3bfr%K_A3qU|<26pwA7BIYtI1(4Er?25j664D8^oi3-ixp!1d zpnXH2t%-^XV4FBVd$tr6L2MRqvrG|^Q#e3JpDQYZJTAb%w6=kPK~d!y$Q;=v#d3>=`N&&5MPYypnE-3$!k;UGg57K0S;AAd73#5wyV!~_?8 z_!3ae32+#KqIoGuTms_GWguffiC3DRfq@Bh^tt$Q5R)Bz^tt$|EBv7DC4=~CNLYim zCW@~C>1Tn2;|35L8txlG;RwwXn?d3VkZ|7uVyiGPB{ecIh;MD=XJB9lZ%q_G=mpxY zQOqEI2x2-oHV%W6H8`t_9|4sc3LK!ViQ-2=<%k0ZI988=Yzp829epl-98?@6aDa|J z7e4_i7bie$JqfaP1p^c4&S~*e_d&K!>1SXN5I@@{z`(!(Do(}EftU;&hdCJ-#Lt5| z=o}nV`WP6*FM!yb3_Khl^O%DA85qPbh6*q+urHjzz`!AXWf#=lS0U~O>%Rul!@|JS z0aAT^I!HBmE2Q{MK0yWs4p3z-ehcI#25@CAejCJQ0axbYcR*|o4$x8S;&(y*5rD+b zJ&;xh1|E*10t^gHpskSN_d!f{@KNjHkNgB7vGW+>4$!)E@h70x3=5e@naI2AlK{*J-mf$b~iHCqrtW@B*0TK@du{Ah8f!N`oO51>=AEYMYl{5o`3C9c& zI}+p^53tviqkc&Ouk6>U2ANwx;PECk`0g}7kgCrO@Kn)P_4fkFI-xe#a!W+nqe zhxo5=plwm0rj)o4Bgh&CA#lkq0_Ly?fwmTki!y?W6%L`ts~H%?#lRc`Ay9KlT$~Z4 zKSC6A?7O%G;~ybVXAZP2Q(Te})X~on0v-D#%^UrGve?7O%;BZv*zmMN~tm@Le|AOy}UN{pb)!XN~$pOnFFk`MwN z`!24+2+AT3Lg0c)73_}yh-GSE{Sgd29H6aGQlM>_;_8rXQQ%|W#WfjM!7b2&S^(OX zDXtCXNI;Cy0jn|qH3Eeh7^FbkGR1WnOF<_&gO7a|*JqRvVPJrSodMWs43Mxh1amZm zz~!eA)M=mr8gXNA2t^2iODPkuOajCEzw z1X}>L%?ixX5CR?hE^ZADl>i~ovG3wGjG)p5YJn|Se**&#hn5HfgB0l4cX2yL5L=o} z+>3D!$Z*iMOmS~UaEcNF9s4fs!w715GYNr?eHZryb66OpImG=LAAmG~wq=S3fc?QB z1Zt6r2ZA{)4AKhX!Hg`T3=BeGH6e^?pz#!lnouxDfI(V7Jd#lZqz1GtQ#^_h)GlQZ z0v-D<9u4Mjfb5E8^Z}^>ZOaso0~^mE1UmLzJRZzpVUSS}_h8&6$^f~TRooLC$g-dv z)#84P-69MOvY-a6co5i=vY=z%#lyhvmjxaBE*=gxMizAJyLbfHi?X0&-^F9V?w17} z`!1dUb|-@ji+B>_4UkQsZJFZ95S^f7-^EkFZ0LR4;_i&fVhjwTr3~VUVAnH~bHIVYAOt#xSv(hPG6%$(Jg_QI4IV1S zz#s)W_FX)m5yXaU%M_o+cu|ajK?t-hQ+zs@%^(Ci_Fa4lBj`x01|iU~@8ZkA{_bF4 z0cA&!IM{XU3_Khl0V&Y2@8ZiD=ZG;dh(L~g7hlJ?9yI#|+LkH49;}-|2s8*Oz5&c( z5dxjwExrk?ibLpD7XyR%W=2rqDK@2fZigB0l4cky$KAhx*YEKqyyGGhzKWN?(-0PANEYMRZ!Abt}p!68&Om4QM0 z7FY%p^Ps9g>I_K1Lq>lI1_lwxx$)vp7=1u9PM~w+#b1C`GYEkTu$SQA;Sd51D~rDZ za|DDy=f;b_1`m))2!YOx7k>xl$cTc@jTiq2<|qh(L;DLjv^9i4=f;cw2V1j32y||| zI2Tir6a&KxA<((;;@nJmQVa|qgh0DH#d(-O`;C6cfp&L_^D?zbf%fmofjue81d2Qk zMmf;A@!}FppetxQ80A3c#*0fa&5&YXn7}9pIyYWio++3MbZxgBXm_W$64N{>28Ioc za-ehL#Z|y^I~e7_DNl_Fls+#o%7M;}7uRH3CB?vSgHaCDDizmZ+8_l=5OSbauDCwa zHjtAUq3xCImV*k>j=(7EyA)=W2~7#Iwg9T`k=pmXEJJ;A&j zCI*%mDFy}*h9^8wTbPFfbWJZ02NxeG!GLye@o<1T7CamcQXmmf)0Bq;G_}RU0cr&E za5zhV#6fA3hhqs?4XF9W!*LF5LK#0uR)C*@f#r-80|N+y?PFu$;qVj#$$_@I@^FCe z){p|78!zq!9-`#o_yC$i1C6bL2c7sp!(redcLwo8;4!#aObOBq3_{@M%sjBC7=*wr zqWMf9k8p_Qq~Fz|3# zfdRLDC{^wmfCWvmg^d)rhhaXsm?+(#CTJ3p4O=tN`g+2GZpMV)Agzl?H{#WoZV;AQY%A z2o5b41|AMj+mz{C3j>349Ei!o0UCm2Ql7@Zpdtnu8Dc7&&cL7|p)Lb5LRW@?fjwp> z1A~ByRFDh<1BcKo1_l*rK^f2(4yce)k&yujGq5k54N@du4&s4|dldzc1Ovy*DGUrM ziXb)z1C!4T1_l)+kU>mbAcItT!M1=j@o<202)in1Se7yFqznTCcT#B@17iXx5*WCP zs~8v)K~ccLomy1Hz?cMTOA9bC8G!CaPo62mz#y)^2y~FY7}GBq(2yzvqXd}6Ahc@+ z0|TQZ6G#JSIEI&zfkE66G~&yc#A+(bz##N!8Uq7k3Yg6x3c4nMF_jf;m1t^S3Ik&r zm;)-XtYtxA0$Mg8B{YwLfiazxL6(6*{@_9e2BsR2`OKgrr4^bMf%u?QrI0Gez`#@s zn$l4S0u5Hxf+u$54}o;VgLN#IWnh5lh)3v%2kB5`P|9A&z`&Ts_!6WBG~LLU28to& z0tUu(uo$S9#h4B#FGgn=;~q)%Z}Jm@BFuuVK1?`0Vn_(6VFN}12Vz&MN1Opbv; z0c_YTm|?RJhRsG8HXC8sY_MS}L5mp}n54iH6U-HI3=FEf;=uwSb5!{kfZ3pcQ;Uz! zgDF%6hlVsG$a4(pzd^w^862RD3qdA;7g9`S1+g`9iy0WFvaXS10EO~$IR*y)6O16H zqbvi1x(vwmnXDk^G2Vvj%tYwShUiq6Sj51<*a%U`BG15}3bvw=6~qQx(F{?@!vQi~ zJr3leQdW=`7>(h|OA+RlLzJt(1I5LDh`BK!h2XP-_p^e+P6M>!;~*=Bp?l;DQ% zQDk7S02{6XHr%s{D}&xXSq6qo5aYNk1H)+$1 zi1{;P1OtODsN{sH63Aj;V9c?bCCk8|*DuS!5K+U*U@rkRLc$Le&!LP~3=9koFQG0m zU<_gY%ot+82r@MgVrq~9V+6!-$ETpeqeKB}!ULGI=P0W%Fld9z!V*v%_t&s8XccEL zFqVKMSZYDp3RJ9?fJ(x&T2=-{r^EsV#-htAUk4 zPhO6JAqT|Jl4D@F*T4$#iZs+5&}ux8QH-mPs4y_-fucE}krfivHI1wc`X*3?-jJx~ zWY)U?8q){)0g`kW%^4W19kLi0+CUBgS+W3R0La>#AO^@GLQPPoff*pjf*9cR3o{>{ zem4Yw%mKv`*c?zSfz1KMl0_3MgT6b|MUYs6I09T0LP|k!>V>H(0HxlIh!~1#Vr8(8 zfEs`lLkupU_|1n(fG2-Jd1ij$9pB8#;C5b) zfx)twmBF1s4wA-TRn%3GGeF89&On#;1ZnhQg=&I2;~vy9RA+!13kpz$NpNROS7%_* zdn3od5Cd|?4><;g-5_TaKo!C+?1@xoU~mN~V+{5{m-7Va@+yVufVjcX7|I2?p->%^ zVnab|CJmjT;*~S0TV*sD81$s&85mevSQ*^Zp%h-Dw$|-5~>3^<1v3`1Q+oWSHKEM zm?~HzX6)zCL@p%R+F2R&w?U0U%7XgsP%%hZ2&zLuA$3Rys1yd<6Tu9&N7Dt=YS{wv z>;b4I#1i3CS_}-@a-f#v7EpDd)y~SGeFxND+yV-XP7oKgLXZ)x{Y5(~gYP9!jkFD9 znqmhlgV$rIkt_Td7z}k4AfW_`_bZx^*s1|Fz8H-SkQX=`fbOU2Q2kKLz~D6nDhX2Q zwF=6)4bt`=$^p5!9-@s2;$lOvix)s032KtnGcb64hpGo_)>njh_l+L|!(xbLMjHlw z52!H6OiX<ixiH5!HiDIMu<*;1BL!{$Z+6f;h|iLKXue&AD|W`pgN>0)K@GKXqn@@HW1 z@`G{|L7Z?X2NbFLFvr6J57cUh)LNX(H=vQhz+ku&8e(q#3=Hx*py&<(wdxJ8LB-Sj z85nHA;-TQ$E)bM_^uI!7r}=|)z+~zf7#OS>vKbgA=`t{wfR-CDUS|Rg_JG35GLwPv z1`}w+BczL!0TNPoQA6qxIHbC}SQ-34!-gt?r7jlr-NDzV$2fuRK~0Szjai8Jci7`#?Nh$^nI(ksipeAxyOr42D~g#JynR91LFjpyJ@by$|Jp z0t%F8k&~SPJa7%aLjxBSWYZyLL4u4Is{g4!1H*Q(1Zt3}L*+p(WiTvh2X&0xFI$JQgYo zHarNLbU}ve7eF~6^FTpe2ku{YNiZ-NEd&iDRDs968=$Hzpodg3&gnLU6^T`hpt?%B zhm`?bQq?emN~)?JR)&DGS_THjT1JpkSaHb+DK0nEo791-xq8NFh71g#QZC@+0tN=g z2F5vt3=9k=pvfr4M(~(D^Tajv0h2&ZX#$TJF$A0f>4b5(K;?EbBdD-s2m}pnGM;7x zt?|;xWMDkQ2pVZN{0?<|a{vRwUIS1(fn(N?7d#}$zyL}{Q1Q6|3=E&a z;-I#d0b?Lj83TitDO45|dS3QW4k%m=1ECyHP^ub&?17|*0wi%;B=HGQaZrU70ug6) zV=!C+6$T~#TBtCnZw0D%k?$z;S_@SON*4?~91{&082E31N4g}KK;vB+ObiTGps}a} zP=%mYJt+4w&XF==V9;}sXJEL|!^+?pAkV;Hcn+!vIC*uJo}ocvV9c91mb%&~HZK zfO1|${+$WbOa>l~P<94};9H=&;Sh)dV+JNNgycBy%<5pGEIyL1rHfQjl2K~Qy<0(6X@Xdu|R(O~B?1b>(e9wG-NSO=(PL@-P-g$6?!I2ddv zK>eM8k zv!}2!fEsBKp4U`X21vVD2JD`xAifqT<3sp*(^wh8Q!7ds81&8cEFkz zhOJO-3V{p^Kf&4pLE~FNAR>a%fWd1XRDow81B2IEWX>KaCkiBU8i`|g8Oo_uU|?`H zM|k8hR2JzD14A#8xC~UhGmwE{DU!GmRD50_1H(Ru zI4GbQ46UFN+X5LFu0bR~4RgZ+sQB?f28Pd2@j7tF(SpJ7GF0+WAOi!p1;`g6O!W*5 zhToy$e*!_1k|6O=us9Di9zj9r238NMM?pO}kddIkG?alV0Tq#PU?rgD8iSWBR2)>J z7zRT*AVtkcidv!KAVpqlkvYeaIk%8FhA*HTP+fN(Y8J>3jG%?B3=9mQ)bbN5jZ_ARLH)_@9w46uX>W39V&p$b8+X5irfWwl^^P*Y|(IHNc~rDwv+FHQ6AP$-dH|K+fcx_p$e*CS ze~h14K#e-T>8uQpd7MuyAi4JGtPB=uc?^u7SU_9i4uGV=Bk!MBKtu08r?WEHzkr&` z8w7R}C`fP(>@ymGT1udjF!Glx0|O%?>j$Xj!*D0HTf>5$krm_wqZzCW?v}ccOlSsG zW?{_$F8DaWlado>urhdsLY07WgjY6{14_OOUP4eFDCc>}Lpf$a3=Cd6P!1@wdNo2h zAWdE~kvVIj9QPmw28LjBP@|~>9HIxH5=TIr3c-!0Ks%W8JHR3MVFoLMaU&?Lb$~;- zVkRqtF=#ykV+RYU2(Oq04dD)O2v^KzWiSSZa0fVqgXX|$j7}C%jbS_&R>pUO9q@22 zZ0zG6%VT{=An7K_GcfGbgK+qvA;!3qrOJkZL9auefgyb!D}!FGJOjfq5Tgh*Ejpi- zLH`6)OCmTTp_7K7kp%`rW~daXjGAr(j%QH&AcTn{fx$}^Dp?oAz+mVK<#YxyFdPP} z4B=#k&EYV3WkRJv)r;XuC%i=!`D#G87l^c=U{cApiyTpQv*oUffRYUB5@3Zp&U@jAZZ5*7)XHS zK*d2JVha|>=rHKFK-C-o$8IR2Ap=A3sksacjDNrhX#!LlQ8xU9r{q82lbgt4`6K@uWYDFP-GgmLOCGgKzrUnT^lzB!?{plkZFt#VB0}WAB;HkS_D-HGK_(T zgF%LYA$TIFY;gyN%?7CS5m=s|Bj*SU8+UNn$Sq=JFjntkU|@7-1vMyZ7qK!}g0G(U z1h1r#UJNTp zZhi^_;|XZPenp*SS_%W>Nw9G%RLtPYjfIFMT>k`y&pzt$% z1?7OsWql_|$}t3u{DaD6ZqR;M1_lOjx$Ft$fXd})uu7D2IS?uhE|-&`oHs!X42@v5 zp&$n{7*;_g*n=4umO&(#YC%m^PUh(+p`)-~)1iu#f*BYLmqR&+jTsnDLv?@#(2haH zErJ;s9)rcfqeNcUpyGbP3=CdBp&U>X+mO=)lJGKu85l&J!CnQAFuFPMQL0xAn~OczvqNiYLL6-1nM;*7c}ETFP!;)Xf~!wpasM}ip` zRzg&Oawud;4QQ?<0;T~}U>PczLfi_fz)pabhj22tz`IHAP-#$t{t1!BUxCF!HGnEG zDHo96(JHWVr~+^W)``rS4ds99`(Sa5lF={_ zss^O@J6H|4V`i8M6$j}B?P&$o8rWQ7SP4}KG5~bT2dJ2K(DkQ$e*GfmQ^g z5d%Z;e9#EXd~k*K0IJmm(R}cPRgv?-s{-xTvN9NhXB+3U@_RBc%w5aMUh}G!JxQOg%}J%Mdj?b`DB*goMdBFlhH^kgH$u$-C66mmagf=oq2gFOw1#(}DnWgz!%&r= z@%;Btagd#cf1w64bsDu27rbLFSCM2=RuO*AOSbf5dE_CtPC-pP!kt{=1CYBg1-nd zFfd*NTNVJ7iiIa_B`;V?x&}^3pVqT77=v~_FkWK?$=PjyHcW4T8>ZzOSQ!kPpr(L= z;FTvhb$}vqHdGiC0wP{uVH}aT0ICucpbR`5pedN(_W}$IjIY41+yIqExbg(tm9N0A zoV|gS!5FkTfbkXBmCs?Wd;@l+!bVmG!$(k4K(3qsaSq6pzoEh)SMGuc<8UQ|6(mN% zt_+^h!N9=y7wkR(s6;3{6eGQ1q4*cVX#d3|@zz+CXM7#Ck1aU=UpeI<_Y6GpHSI2b!MZWL^=k1&-Lb zKcF!a@cbPo^9GpUO3=`h14QKlnBXapN=Jy`4ya&k^mGOWMz|{R|AM55S_J^~(sY3`zZS85m?5 zw)rwJ@PUpo;RCZ?flhh@^&CLEGkqBtBtVV;tq4Zn&yvi$n1Mk}6(kK>EyV}&I3LLI zd>|o)$c4*(fx4Uz`!nBoJm72AT8%Jo5`$M3mg%&cMJs6Qq7Cs(KKYfrkUs z-AukCz`(!<-feOVt_aM6DtZ7i^ampYLvmU^0|S2{$T*e&1_nN`^^MXvsKvsN?BmA3!0if>0nLu{f$B+yB)b9z25x=OatJ<%sx(&y z27Wt`D$p_pWc@`>3=Di=8PF^~vP?`O1A~AANDpYf9$CgSn1Mk69Bg~w?gq0!+Y1?Z zI1Yi_4Vo%X2VK>r`j`Ss{VhyM~c?(|N1?rN5MfpI- zyFn9z;z9-nX={)>Kz$-U&}11ya>POg25ldZAgCV%PU(E0v3{^LI3q)JCq*n~V35lP zb&2>UgEmDavn*g>kjn(=1oe5Kg3?_K3_7|XK~M*W4>Yy}(VzTw9s>hs97qNEk2@25B2m(-7YV}_)X1q=)Q<0BJfPwuDRu#9Z_kn-1_rQ^d|(zxBLfe|svrgiUeF*T!~l?bu;UaN()+s@7*yq% zK!a@1fD{AmsRJh(&{zYc+ykAlz>r)!g@Hj@6y!ZdP{SESFz|4I#!eEKfKrP9cy1Up z#sD@QOfV$5&u3teHU$kT@PU+qUB{5L3Uq>(9%x7bA_^)*8Il7RF)&DHgKPlxI>B)R zmS#v!oW#H&`W7Su>L7yC9z#+FC>q5;LkAF5iF-f;5mDgr8qmN2L|L-@0tN=XYaqS* zK<)-H_&_WM9uCk@PO{c~1_oVAP*{OG1Rw?^0YFqTFff3Fh7WWO8bdPZ-aZX*fuKd90L!BD@YJz4_FVlRN&!=f(e4n zfU3*~34+oD*i5KkO9%r)COB3Z7?PfX&LR!)g`Y*bXes0@(g)CHK`M0L!SN#u3`~rW z6F#BGdP0s0WrCjL2{|N`335m%Gvts^CdeV7ETF?6*_-1)XO@BvgJf?3F_|F8m9lcM zae(Y#f}Hfo0y1iMe)RJOg0UdnGsj-!Tfq@-VfPgSZHMmv62|n?Z zi-C!efq@Hj;wcw{HAsRBbmA$5&B6sb@sx|93AA;R3v}Wsge}0p0y^lFGiepbaiGIY zxeY*DFIYgQm~tC}j)Y~g0v+&W3^I`gbbKkdImmG=AuAaexT`=+PViBTJYPYRM_jqP z85nrJg@dgJ9mUA=y9I0|=qN^>KOnXM7w9NPp1&Zr1Q+NiMxK8lwgMOEC`O+DAhrfK z=qN@W21XFafE#oaBM&2(W5EqNijjv2%yHlb9mUAQ0_J#dgN|b4VFhynxIsrT^00w9 z5!|4o7Q^E~8ijhZ% zF_nRVp@JK96eEuqBPcu?xIsrT@`y8fGB7YSaf{h8Fz`q)axgP6G;@QFV&suw1f7xG z!3{c!kw=xWfPsNw0ypR=MjkyzQ25W_1|7x7qYvgR;07JV$YTKJtl$P6#mHj_=4{{w z9mU9F#5j?Gfnf(X=qN@W6R_J3aD$Ev;W1+bt&%^&zymspk!Kbs_$Wr61t3pyfsSJ2 zSqNe>aDk3u7`RrfWnkb{ zbOZMv_*r;0g;*FE1YR#=U|v^FffP-w611gkQJy}#lWB_uwXd@gQmcQl?)7e0#lYVFz5@sUcta% zBp|Y$fx%cneF+1@9R`8WwG0gR7zDzXFfcq~5GY&D!0?Qe!y5*HZ>t#?S{Mbs zuV7&4U=&bY$-wZRQ9yDf149E7gP;KKWg8aIp6pc&47^vt%$p#8T=xNqE?&pLz726oUnlpq-}pPhjn6dpM&3=CjCGXrPMY6b=Y-ixg)3=CYKLFU{7 z1qcHJ3+RkvKF$d&3=G1cGmcq62RQPpgS4}N4shf*1u;3n#|H`+z5r8#05G&P{0JlX5j*zc_ZKnVzV)@fQ}Co@LJ8nz`zMUK2X3%f|Y@R3)CSM z@CA8+_1~C~#@=|jc1X4h5ue1F=DQ0Ax4|==eZ^bdXb6 zK*wGRN`T587SOSmf|8&t%TiR$z#u4X#0GM`1sekcC-~S)LD@_;sOtsg1VN`YgIzBu zF9SNR8SHvN1$8zC1_1_6@UfSI%9~)W6jT8*8Mr_*kAkWouW*1}ET{%@84nle*h@k6 zOCZf$pkpruH9oR|j=^B#02wF>(r|{2fk6yRfbC}E0J(;ZV+R`pgD5C>f;1rH#h{9A zurV;OfR4Qs)C8$#0UdiOsLjg`^4fQh*TBbK3d!iOGca(0j=dC;1-S^6MH?9ygye)l zCx{9NDMWzeLC0PSDKdfPOt?VDUJ5CJ)N(MefR4QsQZ{F2VBiEF$jBl$1*9HyAR~)B zNDYHXZejrgi-H~_0|N&*s8|$1N|u}o(f1kXmb&hK8VS{zz#CO1a!zJsJdljU;zzYGZ}DmFfed}4`gI& zF5qBb5CI*?$kYP1g9~&ZBU3Ag%>wotQyWML$d&dS3=Axw0~wjxK}=5Yfs9O@%Rsh* zUD*ZF#UKK9Wj9C|q^k_13v?hOQ%@tvUhsj8OnnzXy1;Jj2Qe8K*w=6{Fg!qVE9gK* zrU~0YdcX%VvdL?3GB9v~c1W-(fNkOet@&qD1hHAbNq2VRK;gETO3fevI80WZuG-~t`nB@zxY zRDpqw17tlLhcPDu0}JRtMqzsplM{R(qp%|b7Xt$q=nO<*r>UT#nhSL7nXofR7?Re6 zUD&ul*^-Up2q!4vp9h(9zkz{4*cHqK9mpsW0kVS=d?2Gplsm{s(7{|H(GaJBof8Lg zjsOD-=s-r1cqJ|d2G0JO3=AwHNts*>3?ju0BFUh9#K6GfGL?ZrBqa!>qPd=dK|&;b zI#fjlSOwU*BAK9E#lgS=Ic(@oCSU^WMiZm?(xeR<{qe$y)Zir9YAT9$P*(lNu z3L_3kRCb7Sfy!^t;eH~WAaMsS(2Wa4{n1_p3)6`2H552?OHCWF{4T%aSzM5ch)99*E2(L|<#*gW8LCNd4A z6;!k=<7QxB0Ug;WG9AR^1RZ80ATraC2WIjtkS+#@39~_LNCp;}15yIYiVQpq3@o4{ z8%5@Vn4F-)aWq60Ob3|&I3XpK$0%EH$uz-$i6xj;uZ*YQ-Y!o?I$P0o6w+gUESMH-v)=bY!E*1rVE)fsF%X z9t-HmMv;p(AQQkxHi}$%2zB>Wh`Yi1uYvTiFtC7*Y!tb^52PA=WTVJUQ$7X;E>HtX z$qfwVd>uyMTMWnf?d9oZ;yAH?KT zX<%UB5qVU~2MNE&5O;tMju3eQs)<=3h1WB%xeP3zBO67YyYVqFuz-$i6nWjv$H2e| zKC)5NSB9T~feX~m5%mLUWdN65qW&Hdpn4K?WTR-Hp9BMg02k=UM$sS;TY}4|iGe{h zjc4YjA;%Y!nR#H5LrGKu0!;MubQ(Fqm+Gj%*Z-1Ubh8>^0G-6bS}~ z0I)MfV?p8(44mL28%5svf*b_SUGG5>3|yc_pU4Lg8=5yhf|P*r2FUF!pd%YaK7p8= z;3FGFzBGeO05vs5z9O2MBHtlRO%~9RjUqqd_!$^D4M9gXiu{rlU|`?|HD5)97(v!B zaDxk55io~^8+2r&h$thdSmEFX9oZ-%2Id%WgPO1+;*22u5fY&GsE7okvH+;l2RgD* zM3NEIU(MhK9oZ-%1MV9ZaD$F)6p;nXRB(eDoFa0JngR?A4Ge4?!Tg}g0pxr(jy`?{ z21(G7jUw`lAhy)&Y6gY`5kkZZwtMTrrVSs1v%HJCEkO%mLoBO66j7(rRYfg4;f zse=6x0I^IBtRHmEptS%4gCyw4MiF&J5L*g-WTS{C<3+dyT2Ko>M>dLRgEerr=75X=9oZNId6Weg6X z2ySpGWdfE-fY@dVHY|gIjpH%MHqeocB4&&rwv<8@14D<11>*v^ZI)mQptf0oIU3xc zBO67m!J!hs4LY(>#D)=6x0Y!r!P^arT{9oZ-n#RzJ-GH`>AY!rzGb3jKnio`M&gVeY* zGBAk5fsJS21|8Wb5)bBpj%*b1V7w~?J+e{66CB9kBO68h7&i$*k8BhP0(%mCWTQwJ z*!|!m8%4sw#>j$>Y!rzAdr=m2WTQw7*!|!m8$}Yp?qrZ*5lLeF53&h#WTQwjL?>vM zoJb0o%^=m;$iT2e#GTPgn1Ml}ltCmB?0N=?G6s=Uupz5OLG4PAIJmdV$iTjUg@FMi1?97GfLtO8IZ$24rViO?*m1`5=IUY28IT1(2bWdtUHZ%ZEf;k+JviS@+Gz1`#b(S$j6w+4}5Cz#M zBg()a2|BV-~zpyYLdQ66;SqKGEb1aZ)bi}IjGrHBsGH1H9e@}Nelh(6OC zkdqnZK_@PX7&9#bxtUQOl!-(vn0AOWFlaEzgHBu&v1ZyQ4mxpB9(3ZOh%FN+om(); zgHBu&u?O=UnB+kxE{ZrZfs(xklRW6eMG;ppFM>%PbmF3jI}@nckijGmT2vw83FhT6 zF|a#{GcbTKJl%krvTPiF;-Hu|kHmD8D#*qb18K9)a z#?b{92eqEqIKF~Sn9Bn)$DD_Ofqj!W0|N+y?PFtL<0udY$!!Od)_e>MlKYw&7(~37 zYD5_rIKihLis~^+Ffed|I;W!MprppYb+wg&LDT}ohO}8kEkQ~^ZI%n-3=AxwQx8S0 zKuk{XsfVJDphFP2K!XjUPM{$o21ql>86*tSB_sjr#)Dd2E+8fw2fGBwRiL9v!Q+0Q z<`f5Lynuy)jRVxYV>#Etz#tk2W`fQ;6cYmvEP>8D6qD!%Yn~{xF{LQ zWKiW;2U@tp3{uDFFU7#11{!~4{L2g)AyWs9KQjJh1`Xk;VK3OD4T(eV>2%UgGx8Z8Rg8`pgR(a7?{dI3u7;BwWA}3`(C?F)*-tfqcXaI&M@kKE9ZN)e97VilFllS-n7Eteh$bQUw~(R|btl zuzGz5r7eZGOBfg!=YupeDo8UhC}yTGFwO_5Vo==+l4@rG%VkP4FsOr?@r>;(pscJ3 zQrF1>vP7eGB?ALv7eu%ywS~kwWF`Xx<3Sb%)qSAgYvctR^g^0}K@Ft2kr$*{ z9UQccyr7^}->k&Iz}UnKQlcRQGPH&Foiqc3#wrlIl@~N1rqQ*Gfq}7&7ZfBK!XO7i z*xDKzAWJ&IhH6NG;-w4B2IWkUMm7#mPf_*O3I+y7SBO)ZWuQ)R1*?QO#TCgZuDqaF zfH=h+;uNs;9$@_t>mh8-Oa?{>OQ%GJfdS$eZ?Lft$M}HR4B8r3*%%lY3wS}L5d(vY z9VkV(GJ}p0RZM1Ja|NdemB;HC7}$cC*UK<4fJB49qDlsW3=GU^pg?23CIib=V7@Z= zV8=9YtSO}gGcd5qgTk8mAIJc3YLEwMgCqfYkTP8cCD24UvjHf1GaJalj05u_#u?Z% zGcZ6hsR1YfG3Yg~Wnf@@2cGlvogE7$P3@CCxae(R##t;TJj=vJ1xgAh6P38g(+A-LI z=a3k<;LBa7%P}w*gGTijAu2pKvoe@uFfdL9t8W7Fa$r2xEvyVCsW9HUEvyWt_Mn6U zQ5&|EmBB1OKbL`VGncs))FPLA3=HaW3=9^Gp-dbG4CY=?@u+(Y4E9j*Vg?4wR;YOX zJq8Bz^-xaNJqCspsG?d12J^d6@!9tn7@DBs#R3+f^DG4ztT^l-e%pEvVjXCAzcr}z z5`apSg8~=S3Yi1ilW)T&#lUcMD=UMIkQ4)h`ZiVu8#yTkhPG|247OTO5nD5;h#lw( zQ2T})1_lR^Ht=y~4j|=B6C)fz`Wb`4vUVVyb|780ARW)Pu`)QGlVo5BggQYQ<^)cb zG;HlsOm}%srv9pkS?) z2m6!JgTbl+Dh%^FU91ew;KWhSA>jatD3=X63=9EE3=Bq~em-M5xa+_?aYMapuM`8reW;5W zazW?da?DU*V6d4f#lYaVo0Y*WQW8}Gi@AHfBm;whdkLr#waJu(sAFJo1y#x7pxg&K zSQKPb9>-yjQKpg%40Cp~GT7KjGB9ZEVP#-&1XZtwP^IvL8uK}RgOtyfWMBx|!^&U- zXDpFqVCVqpa|Km1F;IO85G&La85nFpM|nR1>DesBz~Hc#73!KisL}}_r6nAJAf+JJ zH0*`C<~&F#*fo7nrEu4ja&&@}gB+p059%5)1LT_6eIP49uGt3FcK~9=7LXp0Yj%S4 zfLtTGpOt~ZoCm7x_&o-Oo1lYE8AF-MBrF(1SSGHhXR}}oVl-qh_k${WeUE|R4^&Y- z11QrKGgvSNf|RUAQX;PePL)ixs7fwDm2ll>V6cQJ0b9agAQ=nBP>3jl`EF?R zOW$W;$be`7?b)%?f+{z?54MMsdBtncd?6@bd4N|HMkPR17=y&X*?qkd1A}KHsD6tA z73GYf3?6}?MWM+Hph|bbl{P9fFnEDaVNM1W?VtCypv*JxV4{^!Tqrm1H*p@(4p;I%nqTDg|G9iT%lUM zU><-;fr6CdKR8G&7=xI(7#J8Gb zWbpv$2W8c12U!_xK)Lk-hylv59EVsL7%Us1dS=~cU@+eZ<$#L2TorJHGjV7znE!{0 zufNa0FdHJyXbcKJkOM%cuY%I9fdyj(X#cD^%uk?D+XYe0$;@~OG}p?&z+kQe^)jgN zyabU3ZP76|fr{U_&%p2sDqhUMU~U5y2dQULg*XZ%o(L5Or8aelI45(!HHbA3$1#|f zKox=9;s#R`54r#n6h`J9P-&2zaS&;+7bZZ(L3WlQi7$hSzq-%B&<7R=ZPRA3JPH-( ze89k9egVn>Y2FN01m47Beg`TJa?2%%I45%j)WHnq9L|uW0MhmfA`J=`D-ozTNSg&? zC?_-A`3!moK&={eR!}ZefGXPzPm3Mu3=GEUpmwY}D=2dX9%5xM76CONApAFnSQ$(~ zGg^$Ate|Fb$zfIoQ*bjG!hd|2mBHK=YOLS`28LEOaOgv#zJ`IpJO(NW3J%bv$e>UM ziPuBLL7H|UYXY?@rb8t`nl6DQ!RITQo4Y{#4~oz4VDV5+W;tlGftehLq)0{`>>N&J zD>Oy& zR?rM)({WY?lM)8Tqu}W2I038Df3to8Eni|_U~n{PDtV&Na9IQaZplCg@{AaH>ikdfJ%arYAskY z1Z>z6s5q#Oo&^>UWvUSXS0xPQ8=#UP7i@w`f(*NYBz_7>`~y@RWY}}4cnz$oXMik1 zV*q7-HZ8Dskg8yF4yZK9E;)!a*nuigaga7!h&U(n1!&@CFt3spOq)QLSfn$8hWK31uriphg_;72>>zD$Xfhgt@;b;v$Dxv-$j$;wLL&PfR2&pP z?O<`xp%2i=eg%~Td1O9R64bx?3>61gtK#m6lIRnGf3+ikzF!FGM zy0Rb6vNHJRK+S+1NO4F9mRb2Y5v2@-%@}~L8j@4_I6fFj&k7ZTw{ zun1oXl>|k2G*}W6;YXn2pa`!3i-YQKXoO#bN`fMM0#p)|dVU~@uRs!K@qolVD8`OM z#5tKAu0vWW4Cb;>X^?UEA=2PnqzM%VY5NBe=VVrZYjc50gEE=29@t-;%u`Vli@6U} z5y)(7h$67riBNHn*-;R2PUbv#nEGDgtRn zWNgsvvNdQFZUR&qJogFC#tsGy3^rBL3=DkdSs835N;5D_JI~7C&{U z68VTEJ`E~f$G~8o>j{Z%P#En7i-&SDA4ChIX-JB|frzXCbRwy_Bvb{++OJ?^AlB+Z z#X;?J9s`hbLKz(x%7&L>*PSEIeA#)2zbrAzoA$VX|PZiWuQRV}cI18Xw zWk3@)%4{`c7#QZ> zW@WH7kzrtvzr)I4>mb9xPz_@E$S^P*xx>l;?)|~chWGxwc=?Q=y+4p?R!^ap!;&y$ zQ~;cW8}b<#W*IRs*hb4RF!0`GWw1?`VPFWm%gSIbrmGN)aJ#;etW0E9F+O(7><&^VZXE7V|LP@&Gi!0-X&u2LBW zhWQ}pEP=|}JYZnB0x_P^093XbFj!rJ%7Lr~o2_sIYAu6#JX8);C<}uQMr8~|AM0MR^pHL};oIwEt1DoSa z69xvGL@5Raj(e;OKA@}#)0o^w?Zif2AB*(Ap--OlTED@1H*ohlR)JPikyZ`l>r08BanAN-hs&(6f!U{6)S>* z6676_Ja`BaMIPcE_WMxpXoDCa?|@V@Fa(0k+X!_bXtD_8IHnRCka_<1Ss4OB111>q z5Hm|aMuEmkW`GzVGeJ5)1~4#Ks(C?D6S!q(59NT`7mGp1rZR>yn!wr@=}<{fy4www zgrvt>s5q#OybKlxAD3d;1r-OIyco&>nfw=`h!Ng$IS7>mnJj7s$9)1h#rp&w2T3noDY_SxY-se4l=nNEDkX_8Y-UjfPulX5Xu2t zvk=MwnY0tE17gxvs5r=^i(qkxNoSzqV9mFnSsA408&pv}1B0b6G(tfoggGp8fD}oa zgF*pf(o?7+uu0<3OkVzgfx!W+2z=0yxdT)jl=|K}56S^40&R(dwi3;)pyD9ce}HJ_WWEY_eFan+ zG*8243HBTlsFw^Le>3la%7QfOLS&JOa`TNy(mpWh1bA!uIFfWWO!@;{`Thbt$-*3`Q7RtR2<|oaGf>*W-ddJ5oqiv7u0+b2!s?WijYEvfpJdo z4^Vfm6eQ^Ym2`$nCRi~rgs?1UU|<9**mIwiAryQeM>$CS*ZZst=8;fMD?lTXR-o_- zVKiW{%!LXcdI0K$LpgA_&4hZ2fg$)YsAt^@GHwD?)jYWMo2(fayg)JVB5M0R>GRSWze^Gdy_AMWE84*q#QF2FJEK zR2-xY9NUN%ckr223=E9RL4jZZRpp21p4&1ogo8qKB`C}kAF(oof+wI?frc(VK4N7E zF9MAst_JBo^q7?)0<>v@5xjt&X<|$yxb1r*3{<6+L(PVtda}>k26QQ!Bm=|K$IzL!|3EccHbNQTu$v0y@IPc=$gl^89VkbeZ-a`1`W^LP zaqtxE5vVXI)TV=l!J%e(6)Fxg)ABx)15#uTvlf&L^u#cN=O6o_=EKV# zH^vhV(87o76}0e)1TjEu>ZPw(85sPpK=p7#qms)JHVWti-axJao_Bz-t?ok=t2_h; z1SsP%Smr_nK|yZb4CR1|FMUUdDvlit=AWSApkNGvh;uUEMM`L(8sisK5hxCFAc{a% zm@|bT;-&>EUcC%)nqQU&_G12vH&S8amuk2hI*`Z(xJr_29wq zb#GW1%>P170hJcYiuFmu2G z_5i9F5n#bCumFRo`2GeOVDrEM_WUg@z~+Mktp6P=gE?;mBuqd7HUZ);M1ZM7B|!nU z0xSs$FdwKmC@cfrbV8)WLovMTRg% z=mGW+Nf9`}I?w}*6O_XCfCKCUR52pJT3lfP22pY29W=o9fdlNsdsu+&2M1We2UZ4i z*+@iy9fY_G5n$#}Nl<{@1WQ5!ECwnL3X6|maY%rrLd8MBVmSfI0V%Sa3FUxf%-2IX zpa8RV#SviVkrXAt6rl&$Ml>^?Za0za`bnCn7K0j01{5O*O0%pEET3NS7=kk23imIDuq zP;pSOST2BaK#D9^LOCEA^Zif`D8TX{+A*`_eI!MbVT#ZLj4>J#1|XBb0rmquz>a|e z><>7=1fYtm;OG1adqL}>f8YR`^$D~(0hA;m{LIg+45pwR!;JqK!L8pftPG~1p!IMJ zOdzd4zJR8%K&@m(CQwsr!dF=B$raQ>u7cW}P{;tf?mo*MT0;qagVs=S zAO@(0TJ;UoJhp(U2ZvTPlmm){es@SDfCm2apyHrX_V@?bpJ z_{`t1Jb0W5+(LAVgTxkW2_fTbrdM9j{I}^hH2=K{=USa}m+nMnf)CmJ!HF=o{G!t6)kCnj`yom+ExBCyv zf>*#6#51rl7=iLG<5ecmz76Jyd+H&bHaq6%98k6B$*h$O2~zMhM@YGr391%HqZ*n0hLdGAUZ&;iVP%S z;Q+8bpk)A!P~q7R85p!7!rZFprJcZ4RjK! z9OR2ih;mM54Y)5ZLZv~jod}Tz`NI4rlK4ufIB1>aE2#L|hYSn{q2i#z^b=GZWDe*k zQrPe;Gc>S4*8hO0=Vbl?%RJ_SNYdhgV80`c1X`*!YdmHTLM^JH)E7Tz3oXin$Yd#=JyTYX5>xV7B zL#4q3pUluw0Ay4lOf90k6G4)m0F#~pj~7@O0@A%6CjA2L5F@B=ko%8Aq{066fr^8) zJ%fmIGM|8(nuH|H8VvR}QXpGpL8U>aLemdJlsu?C5)9rX*#MOfg%6aJhcGahf``^a z!8;<8nb{ajZ9wZ-!k9tD<4k5Y$T(s+GpIrIm6?sf{5;eYP}SNDb_As3^$scyvVAsK z9MbXn4iyKbK}(AiNZ|rfWa$XyfMm?$p&U?6^&Fy|liA}gq_hBKPf(4S4pjuw{vVkC8&qXmQcGN|zWhYSonVPJc}ndk^q z98_>wL&O=)K`992VORk_(Fat(PxN81dAh9FW`7A$mEPBj9fT3Y7-Ay&fVB za=ZCosQAT)3=Gqu;-FHU6&hq9_3M$um7(GwYtBK$Ihk*qfd!EvlJr}cG@_jHfl7k| zrv}Oa1&&}iI21sEGXW|L3LH&{FxaIVpyHsw35SS70tXbED5W~c{THFCLGCPrsODr| z0S}IwP-&3&`XJKaj^9J5I4C$kr<8&UBT%vZ5-JW-zYnS&B>o>N4zlJhM4Xek0&Wd2 zG?{~>|G=c-!C@|qBrP2Q4gsW6(Od^f+6pH91D1!)VR;B-cqB}k;XW)Xyr8;4PA-H< zgT0sn6$fdX01@Y8)_^;?3Q2lBOd4K$ST#YVL8gKN2efx0YAvYwDFohcJpn3z0A84g z#Xt)a5%5OsOB`$rrr;HTqAZ|x?@dlN2A2ti3=AUC3=F2=4cC?|pi(B9i;ck)ywlkV zZ1PA7apMh2w*s%0OLJO1A+-wY1Hc$ny!T{7@f)oZW z4+TN_mzjkb2~`c;;`Q(x5<ik zkq|{-%^FZ~kmfpwII89v1_mnws3MSNP;`PuuK}10}3AVJSYdGY%anq^Vv}G z#}648_JGAhIGJJFq8ZHBL#07saT_d+5*BBWq<_Jr4PY76$_*O!Al=~VIRPfc5cLZ* z_)`T=_W@94ez1ProctID2J?KVu;L>IhL9Lgu!Mq#m>CTjEc>ByV2@6RazK7#h{^^{ zVV?jSyZ|ck0B-QwSZG!~1y1VAc-R;~+m=p)PgU^aWn-}11JwgE+59S$0~+PN0d)d+ zHzBCO^ByV*inb46NpQ5qFlR%u0%*gir2>=#a+{?Flmj-~4$1*Vk4-GtdziDP z;LvM;D&vF~bSw$b(0c_Ay%t_b=)DGqUJxG}gDL2MQbq`W9UmKm_fbazLS} z76})j21`OTxkJURA2BdY0E>fi z4m5wKLM1_&e>qqZG*50>1{DT5)p7=u0}49Jg-{Mi#(Y1N0}A=iVBMjd%pGVUe;ld^ zq@6n+>{C$4o8O0ugESjJ#5tMskw$Ys(e@mw2&CBqq6nnfk}($&5+KdyoKOx(Q7%G} zxe8Pq6t_JPaimVCxeb!^YM3;cJ05s5kCBlNN3T((0AvOlnm7uGSApBQCYz*eEP)$*f7#OA} zA_o(P9)o!(RJQaH1H*c_EQ5IgR2&qzXQASt1}J1avH>a!3ZJKNSq7^PBymtJ2VU=% z1`P{P2`-idb^xRVH()TI3Y7!pYh$P!ylCAFl?3H$Z?GgJYOg}YLHRlZERK|~KSL!! z`MMq~3C`CnP?v!s(b52!GZmS$0?Gk-%yJ`?1J<)2nR5fm0V{h5<$#o#|A2Bp#Xfs7 z*t1ME46&fS_290LegPy3L3v9DB9B>cm_rqTjCX-30_839aHu#)b0$O_v*0L1Qq%`i zv;mq*VUiJ7Ni<_ga-%IbO(@uCTt?93=B4)BRt-K)PfEaQIlYU9w?#)RSLg%-IUD-q#SgF zN16m1I?uN>Mm0f{yK;?orL>Z#K{{)o;l?%yWNl3Y14-FEK zU#r04;N4~+P~o|c7#Jo)gqh13*ci+wLnT1_LDoVfAZ{^V36%k5v`Y{fj6K2Tdtr(m zF)+M>C<1ppk3z*kxtA#&>_xCRdod)Hf%2RpL>##nECH1U+35z8Mz3`hpo&0d$3YZ< zO9o@8ILPc;h&YnjR*p#0;QZVJZEb*NVL=%wY9?ro)fb#i0-%aPmz9Ak8pb*EGGMtH zqGFdMD4V4)F#3aYv5XWOgAq7a27q%V)5Hw(Y^WiiYL71i90<&x8ACzk&%_J{%SNd5 z!AA@XmMfqfkf$s+KslgfV7V8Wa{pd3 z7^2ibWk@R6^#)Lh6Cl5X=9A`3$b@-66&yetrJ&wV2YX*w8s_~BumK?NdqWKYd7n2E z!}|?TX^`cX9Z(L~jZ=|1E1(>(vJFrUSlM1=&IKq3tn3Dq16KAFne!IP0V!jMvI6zI zYQX+sD1`*lclfAtS`Gt)@tWD7<#}KgE2N=5t^@lxQihGev@L{zff2%Il!X=J^=zOc zCe3AG1EmdM?Pp|R1Eq~@ph0V}4^*1>-1_nzVs2)&w znMXi5pb}(H7C71%Z5YfaLWMy?a<8Gnrl6z@vL9o-(tHP0IVhl+v%#i-NA`|D#X*@= z2_nwPJmDdvyT)Mo0V)mBX88lk0Xqv8NFa^TFpVBCjpmxrFue4LfuRl}4LxZG)Eo|E zG+;0fgen0QE{h>bIGGioCO~|`V4eb11hVE3OcA_MVA%kb206)cDU<^qlidjAfHeMw zX+-oVc0#2=9uv+%gvWWPI7pijM4XfP2ehTaVEzm%iyMsrUEpNTm|QmL5cxmKu%^*Xw5HK5aMOra71lJm z4fg&CRgiNS81I0aLUYt$O`*GB?}3^^kf{s^KU|%S!LJWA5p@qN&)}R7TDTw1zM&ct ziqW8hj~Tt$T`D1bmjy)(4Bv8LYoEWcfsR;!oPYpf8)>jHFo0WEdKC~Y7eFl)Mt1i+ zXzMCV1KPSmRsd~Xoz;NmR#r_m$kq>#YG~^!4r(AnF#`i5J9`nxKv3(-7Ni!`x+>QM z*$q1AtOTmm0HSm*NGYh5v>l`r)JkH~f;Gh2p-M$Ti3Jqn9PC#?%0Ue=CoO1049oyE z#JWNHz=y|Oh3YE+Ss}yDkPmI^y#(n2xyC{p=9*Vfr3)ZRwLwZju9=_>ZL_@tD+Qf1 z_YJD_0Z6GVdpt-f$R&C@P?vxiAeYS5VPjzMSAoV0Y$^NUd|1>dvLm7f!Zy)mV+cUk z06CmOksWjZ#uQyP=y5oT?4Wl32VKzmC;t;rJ2;^sq+S5CLmkNu2-{eXjUfPC1H=w> zupRY!Fgw)2cAU@y*&?4%!pF3n~t3f0jbUL3?{Z z6D16mr=haoBI_qIM;aQwpwh!q6UqT8GY^7tKrPh65EEG@P5`w~PeNtD%F>~M0W#nP zL>XwqrFj=r9ONLjLWl=o)dYk28>lR(=_&`7g=}uuseev{}d7$}~`!NGU8$^bac`2;5XKoIa2Gs+rVbUL4QqX6b8mPc2FO) zLm$>Wiw7IPG%*5n>|_F1?LK`r26G*#kxOzI7c3*1(XAJdPT)4mgItUxU!@QmOsy*DKc(1R_y*+BC7yoalBq%y>gC!yTZrOTB_?kXuVE6$RM;ec_hDw6QVKt8pc2<3oe%-2IXpg4#I>&7z9z8$Ivq`erT2;3nox0{1F__zQ&Mv{sfL^X%kpHL-^$;Yz&rJ zP&2@x*$m}?Vs|0LS&+;M3W!BeNl@(W1WQ6lJ4${0DB956Prb88hG#`d20%^9~2o(ouHs1;5fE2wzC^EkS z6$ce6?3G}jBTv1&L6TO3NyDSxoV5uOG$1RyVbbVl9`HgHfvir3C<3|9N*O8+vKpMM zW;wUFgO(%GB83EJTqZqa4Ir_aa~Q>7)0>j3IwGngMiQj-Z&13HeF33RehAj`xZ^=u60f1s*Bq0j+Q z#mT%6)|N15Ylehy@nZ%C&~0qsBZa{l`JghOmdh@PMsPJD1{DVd_a&$}D8pz##X%;$ zf{KHhPsUJjkU31%5HFT-Fqm6I#X;ss!NeID%)_DLAO~AQ#F5%X=J8NzkhVaGG-#!P zRXJ1~qzxR~@KnSQRSasEw18u)0jg{&#M=yvbC?=IvCY5;k#{g>gEj!#!0K5n*ceQ2 zfaZ|eIY7-PIZIHf0$RV_0Y0{SfhDMcz`)oEKDIm03RD86mM}1OfsYZ_v}R*~4DCGO zn9>Td5^}8fOO6gG-$kH=fnjkS>{#z3h-1A$XK^#NKy)oAVPIf9#^GNB?N0evvq8?{ zX0QSE*gy-c*`P{=K$Re<$#s-t1xPvQ6z~N$(3RWhil80LdRu7vWVeHMFhM$?WAesO zgW+TH$2lH=3C*8$oc1T#RcISkSVo+0=N z)dy;DFrMd_3DN`VZOS`BT~qA{%8@}(WuVe{Pc0<5g7OonsRo@ZUj$VGItTm_L~hNIGblu_1%`E2t`vL9BHUgKEGBK7)({74hcc&?ErL8gdXNAOjf8?Vu8%1t`v7 zi4aa^*qQX84J)8UXiRO8qyfrsv0z2e)4HnjmuLQWcn)*b0;W0F4AtV-MuSQ&2^qB)SNqhzWFo0yuHrgGz!d zI0%tMs<|zHL8ZYXo^0)qqzF>`7N!J@-i52_l1CQko>3spwbMg|5o z@I^rXz~!iB83Q8&C+Ic~&7x!mMn>?NpbSw>pnk6&n4<>5%@e?82>_@>1ogqq8TS> zu6DUA8-wXdknGhkcL}yb&iI8g%e_!i)1l z6Lb*L%nMpipeusb6Cb>w^@N5uw4MOzV1VrKsY5r|hf}i|YH)%#)L?W)P=lp>pautl z7@)!gqyuX31gOCZu$s-6vk7K!mk-onbVX2uC48X<2Y?tLgF!l=L#G!zAnCRMRPD5L zt_2wis&+a+YC+YGo*$@&0#!R)oe-r9KuS9}AA*#Es+=}IXq5wIfU2CIU_GE(O#!Os z0!U96Cr=Bs@-Fg+R^IRYVa6vzl`@nuFfev=I)Iddj4um-8V_cGj6WCvstG~HS3>o` zj>lu{RCqAgN)of&7*c-&gVC4wa1FFC*7(+Oj zAJ{+)v0w}YsRT`Nfi`oicSD5Xo5GfGT7%37O;LRgg2tS9Ff^@M2ZQ_q8pw`@>N9}( zrI#}cqz_b~$AOfC2D7Jtl!NR3Zm9ACkQcf*r^1x(fhm;<0r?hW@&>5V1t6vEoCjb^ zV?vDbS=m{V@ZBKpUj= z2QBA?Ws8-M85lI6;-JcDH&i|7xNHZgcpU?S-Dwc@%o8uvF<5FqCBUj2p&U@%d;p>fQXzp_`|(goP~Ch3ED7o9 z&Vq{Xe$2qY)DH6>_>Pnbb>KTvK=(|5=HQM&Rf1Yk>QI#+M~ARX?0~2=WH3*KrXx@_ z76ehm$^76kWK0TFErNQ{L7;w#4CaPV4yZ*}2~or{aRO*W z+!{${GEAleBohOb0fqGrhzx401{CipP(>i$Uw|kAb+*kLpyD9SUm@b0%sb!_1xXxp zkQ7OEg8j|O+<~TOGm;`pm?8{@72%g zf({lP2e${RLqT1&LQF^x3=EbXP;pRj zTTXy-KsqevB6BuCIbdZwpd64g^9xW8NTvZ{u+=lDI6N5Ptu=qzyMKgYdHo_j%8G&xvhVv)X8*zcQUw=b1!XAQVg<<>ybw<-cjjr!d z`3rTXT%fU@AK*sVr3f|#NTUqGZ;gaC!XW&Ik!%bmX(bGdKRH2HpiYSbbBh=le{q5? z!~GD&20iBbHz#O)o?0|$lDUe3f$1!|;0*5dVXSx$xoE_k;195e_ofT|-UuF@W8d#WxLI$Mma0NUhv7z?d9 zCF7uV4@fn1`nhHT#B_#o1_nk)u46C*1LL3uA}fFzxCLYms4{y4Vu0FEAk|Ov_ z02%1YCEg2lp-Mc|Kx73_1KZ=FF5C}dfD8nwhBnH+LJdp+8R*I71TxT6l7ZnnNG+&Q z)|vpStU*m0^@$MAOaLkM;wl3v1+`+1CO}(FUG9!O2+{*G zeqJK9#l(~ZQVKG@6sq(CNU1N^RghAU@kL2cs*sR;56jNe@8E!HqA8DG=u>R4_0w`fv&LLx+JlQlUyS zQb7R@GWjo5X#hy6CzmZu=?a)qjx7j&Tl9O)Sh|{Q4em! zFj(?H(=u3@DwG3iPzg=|I~%f!6Vw27hDw5(44Pm`@J=c7aHu$_0Cxn7gAdNJOoxht zOj?wHdnEB>s5r<5&Pm8_a)ORTgQ`*uhzxRTuM;Z0 z^&SI*6GR&7w8>BzP!lf(CNl%nkeiDnQwEa(H~y9*$xMLBfE$0ip)#O&-wKgI-FO8` zqlcl2K#n;JQ3M)$H-7{b2WkEU5$9xXKqMNlqCZeYAdd=81_uEra|BcogO%7cNS+2s zgWGi*U{VZGE}$BHFBfR-jRI7e1biWy-3-``tb4(CuzIAyM(Fo*v2iglJWB(OoTM-? z9^e8E;P<7&7NQ;G0*&je%YZFJJH!QAh~}Kh#$YrH)WkT<1s&(9H(d* zfiHDE3byP8h?mL0cnoy{y1L*-(isE_D8mlG`q!d(Rt$-@s08*;W{R*TM zR7vdtDTOmYwbduE9#Fad531(@NRJk`=u~JGP?is^0c>fGYrMV7N5>$%jgC!wD#y(JSP>-t*EDjkfhjzK*pwggh zz7!%2>c&li3eT2hVAu;026uMWK*d36^$tWFInQo^N`t)d1tJY<@tePaii5NXPXl|8 zllcj(S7<2#4J42@OGPLLPm@=5V&xE7`kj#1%nIxzTD2HDG%Yp{cGpu#T;st9EN8;Bxs zaFsyCK_wOIbg;icJ6RaaCqgAa0iptt;AGZ#3Ne+zd=*q0WT_`i8u{={&=~e6s3MT1 zi4aBLlyDX*4$|BP5$9w^KAH=p`8re)$Yl#4iXb6oejh3WGIj?<25jtCs5r>U*CFD_ zQU4z*4KnZ}L>g)!`z&T~a|vW1=L~QlfDM#|ih~T)fQTa*Xk`eM1{nyNnwhwvo`E4s z4b-Ix;RY3m4p0S?;G2#*XTu7_5N^;V)_?M01!5RCs6cEffEI}1+@J!Htq@iqMsR}) z#My8CR+q6;-bOUSAzK9A})p-RK#5`f);U`pzeYfaqZk@GoeLX zK{2$5LstYX;w~3MODgUXXa@$Q16ss2&xQmfyog)Ood+@)RKz)h)Pp)I)gT6_h}&EO zDp;7HdO(5SHWMEBmfTQraK2E1as(eRFl>M*LKG}cP)ShMI02S~6f8+laZthX94wAf zu#`ZhK?Tb{h%~5RSpgLW6)a-2AnpO>pgT};P+~BJh;uRrz%qm-#~g^)LEf+wgmS=c zH-vIP8uMTpD_|PUouJa7RMY{Hh9=8!s0=8ZtcA#MVq}vpaB6)C<$(HE3Ufi4p|k7o@oMw0P$i%{ z?JdAcK#S-Y%-76=cm|Xlg#X)7sABZ^QVj)oXG8!rgDoaG> z!Mx94UIrBhl_iE?aZpze+C*uBN`jo|36%sHHWw-mvMCKB4jH8fN$!S9f^4b-OM(KL z!TdZ_9F#p5gT+HRnbFpAnqP-10{Lw>L=o6;FQDR}40ahRKGA`}{0~$d6jYxe;>c0M zvH%jSAZ=XpLB0n0j=`J0#5tMep-~2Ea)3Oj1yuyn91l?h zcB2DS9HhAgBF@SD4|*F7OtS}65lHhKh$67&IH)*C^FfF>>OeSXrE3~g5lAzrFah0k z5ak4_m)G)mNH8!IK;`Ga>t(^ku)<_552!FnuYnaNYk8U&7#Nn-vN0Hg3lj+cejThZ zSNZe8Y$^l!W1Lc4c zSvW)oC-a79uqLAkR2rl)A0iEoQeUVzNLwF7oRj$kynYOYN`tg5gGht5WkAJ2+KxiR zIhhy0wG}|6LE7#@q`}&nq2eHIe<0$V%nWcxc0;8>+C&zCLIYB^Erg1L(w!wloRc{L zstuHAL30i(p^8A710jmQ>p?amiDyE^LD}dCR2<}%4v08%uDp&Uy&NWO0kxCC{616~ zWZQm-G}wnV#&KLZ};H_gXOa^ITaB%_bUM z&}@gFqp4}iibX7U|?AS5)TE7?}LhGJz-#w zK@z_K6|a86z+i$T&awm&ZlI_PhKM7leN`msBAE0AXq+&ZyFjHuZJMbNX>dis4Bm$f z-WHhzRRXeL3q%Pg^AETQrAX43VbU7Up?S};5h~sDgn_|)9+U$z>OV|v1Wc{f79?r# zXpb+nU1r67j<>%)zFgoz+tXv8kBv;@?43b0G+8W>s))ja`yS+5P zm#Zs)ZDwH5h77E$A?bmzp?cJi^r%7gFhtpc8g)Cs+yhXb7QzRt4zGsJf$akO;6fK@ z5($(9ck_Z;fx*3O(38^k@PbZCW9nlA-<7%-Y}~p&HU?M4r3?(;q2>xyGB7aSKmdyuTH=TZg+$q8%>w!upo7}kLpu}c{kxF)hO_+~C; zV6a#QNml;+OCb!{onCjAF)-MbEoES^ZCc8}kT#KxApl*8Z9mjX+u2JQ7>-Q@t;Pax z1B{27)c|syB`^DO=oE;`BsR#p)wz>E(+S;@3=EY}r7&w5t$6K0%0XjEUqQ;@4A7vG z-DJ=}6lhRs2~^((h!t5NJ)kLwwUeQPN}N+bTLC$s-s1!}f*BY#EoWe`TLtpfwxtXV zo>SNu0??J%9$w18kUNEq!S?)828IbBV;Ia$q1J&axbw@wxfq;(z+IMLs3fR@dk&U_ zbPuON#X(Iv&^#olxC4*5BbNW~gDL@)l1eMU=7Nn>fw~(cZiOUn02K%2tVoDBC-Vka z&N6pFlCFSB!zXvmEmlC{7*uf2g-Ii?s0Ve-9H5FoR&RwU0y`-UN&GC5co9?_!2J^T!2;tfT|-qc@K$W{s+nd+5Z`=1G4%-XgMV4K_+pp0tW-weq*RO$bL<@ILv-$ zs4PgABV3lj(i9AY?<$#r)fO5bx7oZ%l%p+vZD<}u7><2Q30UAkQ zJ-o;qIVcCL#|w#Lz8=b1@q~eaZ#CH4tmO=B$_5sUAz+3AgZW{o8c+h#g{VPpq}+i@ zgAz~>OxofFq~ZYu6exP1Kox;pkOff$4&Gl-agf=a5OGfC8}P&_x)KsAAhXxOq#01n zwvvS^0+|g;oZ!a(RnW-6S?~?k4p8|&&@zT`4)a=QA%7lxVfN)Ikd*-!_(1n^^-TpG z5E041zzE@ApUTGIKL=_Cti=(s23BD`;6qec5VqBFsA9;hF1XRmV3`UP1jUZ|E+_}& zb(X6(q1gHdyjPIUq&m zB2W&<*yj)(j0Ox=rchx};DhV=0%*uGFhpGh&FXxIgdbFyAjEhE#yLl~GB6l7gZ6EF z=L6O0zoxP=82;rq^jHtQxrErKuElhepKFTmKY@5l(VDn6dfkA2( z8-vv~sLB&hKw%yN8bJ&M9YY?($y@?$~7aC_4s%Z?uL=!PbjHZ?*27&BhQ2zSSCC9&)qwrrA&rPJzn9Jy^^P3Sf{2L2AIa zQDdlqc15ANJH9R$Jo&!w{Fu4GboMYuiXll3#k_V**n0!Mu zC^fi&$mhRJiGsR2VCk{Wi- zg{B6WJluoDf}j8fc@U%qoEk9HKsT^_uO}HhNg)7AbC)VfXN5cfKo&RNFJ0TK=R-eu?gw`c=qvd0;vH77|VR9 z=P=YjJf}Gynj(Bb43OtQIv5zN)gir6zBnj&EGa1RzMg8~@jL6917ioj3<@!&&{zd*6ax&WFYKsul) z;yKjGvQT$HQ-tOMXrN)pL(KFC83i)47{mYt8b}Ak4D(iK4Fwv_o4f^7ONBCl?$}&` zB)$qF&S=J9ejh3fs@V@ggqg}T7|cH)iQk5ZGnc(!U@(VO7NC{{!&b2QoXjtv^**Sz z20BtS4C*m(;~@@-qYBXhZmYCI#X(lvK*T{S$NP|kgAu}FYatmP)Yd412y-%@fZEGo zu8Sl+5hji37@0z)K@M68kp{WJ$_FYAG8No{fooxiDg%vKnec-FMiYKe3ugKPSc}DsAJhnJSqN#dKzMA6AiaDDuXGWt{Q}`%2FZd3JI!)C#*4pe8?hyP@Ksc>4+! z2PGK=s9Ql{%)1?;9@MO~hl+!nmF`duNRhc8lmjx!4x$5;0QOE09DotkF!ZT85o>$^2-?*v-q7D7#O}RVPi0UIe`Jj|GpF! zhdJOl{Iv`chY;S9<&Zdp@YGho;t;}LvjRFGp9_vd?Uk@NT*_av9ukM(`weeHUG)Jp z@z=obwF4S|ek-Bzw+X}m#h=hBQ2Z&>GBC{E!N6e342r)=;P{)iijBeaHRuv|2>;6} zHU=x0E%5l<01Yruo&>EH1RoN_V16Fz08p%M-T{dvQ2D?DbtNb+PD90OG#JclpyHqi zdkhn2V6dDI6$i)aawrF+$b21?12RcuC)fs1tloqQgJRVfBFxDg0d*vU`B$hk$o+9J z=?17YiXY7XLY0DSD}yKnt-!Go-T(<`kPYYwgAZ$wK;y?H#;0ic9&W*0cxz}bBYR2&qYPoUzUguw%KJt!9b zz{D9CEcKz{;Dlig<$x5K+d(-XlXQ23Z2%>VOsFs@+I%3woXkII!%%&md5+2bvfnw?Gra3J?R77`|))C58>P3=FIHFff>chE^H51wb`Yz*aT}E11ck zzytgI07}3yFql7sS_g{u!+XGy0*-cWsGC5MbsH)UD&-5I;-E183=?NyuU&y=474$bsmG&StMz&%aP)&A5<193V>?n2T*0J z;S;gN2Vt365gZOHw!$(qgkQf67Kh3Lpqe^tJ0uPvyc-~14g({E7qtTxhYe7z0IIp)?}Wu^sesoOq&U^x4B^1z)Iz{zA2d!2cR}OyDu@A!Q`6m` zIDJsdz%XGSY_Pyy05n(tIdc%gUb35w!5*Xta?qT;0BCxHWe@D2IeT#M6z>6z*}DkT zF)-Z0W7gF@pxJ~v1_ora6!)T%Uzi1z< zAcF8e?_*;yhlL0zExPRorvpyrgx83|2(*Ac%@v_31e6WE4uAs(lnpICq2i!yW!?$pfC8ijqKK2Z4nFP#nUt6a zRRqd=Qy_{!g`oK(s5mGau7QbbFqj8HLm8z02uz%T!E!QG9AvZQY$yk$$b1o$12X9= zL;+C{0i-Ac?f-<_t^5zF6l9zQL@C&fBG5nvl?mYxaZctJ@G?OQ zNgA9Wkjey4P-?6K$Jhj@vX}5Oq3(-v^=D*5n{G+ctw3plo~oAZS7=VjCnG!p8e-55dyO5^yqvY~O*ft&*XNF~7G5RpHKVJ%x2leM+@&fgs z{=5cA9@L-Ta0uGnodLBR-re;O0;vIYcds6Tc1{yDsqRR-Cd9l1_rAs zP%|<6^DvnJkZU0Qd8fl{3|3pwbs+lld!SNi<9!*2p(8vP@{s;~?_sD1Ve)Vf7Jr6$ z5Tpj&pT|%G@!%$qzd(KOYaj+F+Ce&?{rM|Ucfrjqu>l3zFOa)HTe2|ZA!aHZfd-lj zhygMaqyy5Q2ao>3f}#P`5?LV-c?_BwGLAq~155#E`7j$~bf@nKG&R8F9@K+IcQTJc zQ^OXJJSa85p+b6!KBc}`|^%LQv-%PBsEMq z3QY|#dAJ9Q89@OI@*qeJI5l9Xfp~Bi$X}q;a1X=)c@U%nni~9|PKMPO(9s>HW6(gu zkcXJ5aSR%0J|G6jOpp#pY57E7VMQihz&z!DJK~85o!#DZ=MC8-tbD4oJF$>p+wbFe$Y0zM|vM z6oDZRNfFbJLsJAy9`3wI=YLLcK5+rH#ra>8~yRZe^ zG-!Y-GlVw{RL(%Ba<_q-@I@y;2L~iGFm4B{mpaMDVAKF=TI>)AWCpD$+fZ*L4Vrd= z@R=q$m|B61-wAG4Fr9+5D5~F_?i59%mB-cl>O3LJ~B* zwee0M^AxnTQFa=d1df6jpd=u8hK+&2@&MF7pwKnH1Lb^t!oV=|6gUbXYgix+FVJGO zXHX@e2-pZw0vZRk{0CqPprjK82Bk}UZJL0w$cbC6^S;VlR8au^sPJhAhzWC`K7pNA#O6iBk1 zyc-g(@MP&C$Z!^#Ecc#=CQHQ&(B@v_1<(Sne=-aV3!wS}8W|WEy#!4_dQ@Z?7(RfM zO3E@Y1YBfeu;G(sU|4ezG!YNdvj?h208+RyFnSB-!1QrkVq>rY>B|5yK>8kB0%ZYV zs2)&4G1rH3KuLGbS#U%!S|LUcL0P~Osst1nTOdk6D@iQ9pyFV2vY{N1IZq&pkj(*| zd|3)r0y5_hLC<}<313Lkn1ujCxLCIDJDh?V)livdgUxB9#4E|8@TJTAN z#R8z+0S*k7{!k?#r&)$WIUsv2NYec0!G7jsR)(7@xfkL^kZwJg zG$Qx7BT4(hq~W>8G7u`Q|CE7&A!;pXl}H9S8zev_Zoo(Qtu8Y#7#I36Ffc+?*j$E< z^<;rF!rRM`i~!-qT!AHh2!GNQXuoeA)L?j$uMzxw9-8D&T!AKermN5-Z*>*a?^D3K$q6Jcc`L45pl*;h6=3 zpenlM4rqEXHIIRDA=vfBci9+B<3KG(2!GFAHiqbVprZnLg<20n!q9&;)Qdcj2xrK? z0xL8&BNZADw$*m1VrZd(bdKv?s0_TkIwQFF3befXbQhYyvhP95tNZsrRWxWkqWS>D z9RW=Y42*jPufy~t-iPX$31WZ}<;DA;GVUK#J!~zv{8gB9e<3*+!nWdpdJ^hf=p{{{ zwUyv8jp6a$0YLpBCC z&}tPFIY{Z|{195Y!Q|i`D?SVIJgE2vsR5U67-}FM%XkR&SPzH+@)$@51A`lA3E)?# zdp3Z)04dovfZPL0MJRF*v#x=R0F{)#Kn##sAk~mWZhjG(T0y0n>2+}F04~*jLdC(w zj2JYjfjH*5P!32(8bk-92ZQ-8s4%GXse=eJ)oL)9>p&g*^8o|HG^jXebCo%g_&TUK zXl*epJAz8eTM%(h<_XZ^%7PJk>@#R)g25b?vOvY(7l>NuQS9P}Aejjy!+QhlEpQgK zR7H|7*G1wOK~#Yk-@k&2gWM1a5$9yKg|?u;C-Z=hZT|^X2C}3Gq6}gQ$6<)=pzyL3 zfO0^pEM=e^u#5_n1ClYeu-2|v|*pLY0oDxtW4=&UpD&!uqF<35wDg${Uel3&( zYVCod=QCp{=)Pspogs2yLpMMbK+mLMV4PEV7j}FwL`Cf*=)|3Z5UB9p^BA@(M^Om0 zD@XDf?9>D$A<(G_pFn)@dA-U)??f0FG@gSBdj>`ouyw1R!}exF_@KSnkSoP{g#wO2 z;>G+k)J>o?tbGfdJeWXtJ@FiYC@^`-z~BrO2klJIfQo~5QK!MhL9_Ls&H?z8DhsF* zP@tG+Ksg}w)1c}>Dg&A77(k~#88BE@K$U>i_d_|LQ?2$u)HByy04?l*N`O_JfpS2* z9Ug*JLAG{2f{KIE(NC~Ac$K<2^HGRzK)W5pZ$o?u8hwCFO$kF~LAvzevJ95eP;roz zmfA=h^FS!a?I{C8B2)*c-O>RS2bp9!0m=bupNq`d0Of#{?SOK?%FZEku0c6qWiOC9 zAD|qtp1;T(K4`>(^=Ke-44@p49&S=fpVJ>+bl1M;%M(iNc z%rzfCU0&vz2Mi46`cQe0>9G)bPG$>em@!y6K&3%J3NB$jz$|89?_ywJ=)B9oz){V> z!0?|TLKf7V3<2eSPG*NtEpQE#5Ua$%Aijc$nSmieD1?DQd?kpPcxEaC1Ebr?y9^AR z%qQX@O4&G$h%+!GMo(v8V03>b0g^I+nv%F>3IhY92WX`QC-VWQ`owRb#-}H!Na1AG zfT>(PA2b32D(yI#6`+C%cR`0v`GA(8CAv|(NBvK9jam|#dTUc$g2 z>@^#%HW_rv97{MzDJUkzI7Ap2#5^P!7#LC+CNnTdJZ1!)GA9PIl_7cSY6b>L2asA& zk^!3t(Eti1G0=f949WFf3=Aw$AT^+d4@4T|eld^`LrPE|0|QI6^nC^fuoOeGQYQlg zOC(4V6un@p#X#yAk~hs@U|wywU8fd!&7en|u1_qD>=qgk(Q0R(*LJMpK zgauM8_8rvfN!z`Ofq{$R=qd&VkU~%<6a&kG^fM%U;v%OF9wQJhUC3d z85pEAKniYvYzA%L7ZYS-U=Wi55oMwb3}PUHA$9pI1_qH1CQ$wo104~{kZjk)z#y0c zQpfUufkBLkoq<6N#A4tD8IWE-je$XenJL+RIs=20Kgb4ckTS3hAXhP@6!kMOFio-n zi7w*+MP}9t1_qH>aAd}SM8O0&z`?=9#?i{cz>v(ol7T@?3*?V-kRq@@l76paU=T?M zYn=oV1ru{0Fo3q*&UnDUzza&vY#fV0Obt#3hU9CmzWF|xMgw+fT zV(K6V-$9khxU0>;07@R9))hFNh}knUFo=ON97EEnG6n|eenwDQ<$uV)01kPE0<&NcrFGON#^fjU=S<=*}(J&7V%&fC~O$mI5-|L zFo=Sr6+zN)r-MU`A-Q`71A~+LX;=*8a)@+N>of)i zX?8}Ca4AS0l!(CjfFX&ggMmTz9}7qdba5mkAX8;PX`-A7ba|o}XgfVaD*qY=29W}$ z^&st_d*m5X(?Mz~m_T>yLDZyZ&17JZiUr-V3lT^boW{T)S`YFM=+;K0+yVA(@~&A7 z3{oK=Y0$kO5NVLp7?O7_Wnf?o0Lg%EUxPR**$?DN9nijR(4AQjuO&a3&A=dK2oeO{ ziv-E$2%jelt!7|g0cRo5l_=oS1X6lHO0UdS3=ASgOrYyOzy%dU>I_g!H!y+DViyCM z%8bB5GZP{~=y1Uk7#QTNK*FG5 zY;b}EM>RCLUz)?fAngj01`Xmu3Jh?m0V*gMvL=->Fr=!0l4T1MsHGpiVPIfu&IB>l6B!uTT0l%@4tBdp1_o9RHjb$v!x`Bg+A}b)ca}0R z++hDI!^FVAk-M9Lf&E(&sPVzEUWb8!{dYeD0|N&~qA~*m`yUWnfTPZefr0%mh%Lbp z5y8N~{tv`f;AqlkU||0bVry{TP-S3XXJ7`Y*e180y5 z0|Pq?nB&2@RDpqkofXUp;FR)ZU|?qhb0Ro51v49R_V_;xt;LMR?U|<(# z3}IkkXyO#JV_;yHV4Tauz|hPoXwJaEF2lH%iGiVm(+6aLDkG?}n7}!Sk%56-kFl45 zfnf&cOb|yO%vrz*y6lnN0L)pzxf~>82D5eD{$oD2*S91M}5@b@lbVBlcz1&9AdcNm+6V@)B54YHntV;`I?z`!*T z6iKt#_h>RONU$#eg%F2zH3I|tLJ*UILjmNyMWC?a;5d4ifq{J)D5wM&*fYZ(|Mcw|8*0SRWORx}G ze9Czs^CmSiFmUi2UI(dXHeq1kHv;c{=6GMsz`$<|Vl#0FXEHGGn}FCX9FfZy82CLw zY&Hfa<5~s=ey>>|bJo-`FsShRu(L8SaDc{|`F+_y?&1KAYx4VngjpE4LDq}2LemsG z1CxIv0|USRBanW_Y6b=Y{y=w-{>vcYAdq4P!Tf?^2L52MFasM0NHY^?!iYaaiIstY zsjGp3fj=yn6%;&~tPBk7z10j12K*75SQ!|=G0z_f@)-j-=J}(1L8fznVxB)bf)$kQ z?$k3d@W+5skpjohDh3ArI8fYaFtB&kFfd5)C%p&h0Hp~2WDt`9oC^3;KyKsUFb8Q! z1+jTJKr1cy(?D!c?gAOk)DDtP2RViPKFBmCIYTxE2967r3=B;2Ae9V)xrqe~ObY6t zWCwN@lOjk7$OHy91_q|Hpp2#jVzRT;GcX7+sicEU039pCqzYm(aQp|E22Pb63~U_c zAYCz_?S|^0E6~_DKq-yAT9Sc5LO^pdNN>;*1_l8wi0#RVB@6=EptE;57?|{n7#IX} z>ev_<*t4=37z_mTj)T;1c4lA@&<8OY7`Qkf*S*afWbPDp8pLD3<3hp z!t4wTf}o8{0xe)WI6#}01X@9C7O)Qm+CWM`ZhZuD>oSnH+CfZq!$yz^ou2Fr3>;ur zc7b#;2!dVN4Hjl#K!-C5se<$}aO_^oz#wDpJQ2w8)ITYzI)0|SGQO*97sg912MgzOSI7#K7d*f(usV9?+V0?BaP+Q`7b8w}FG zzyUgbkv9ay7U0O+&A`AL4l-1MfsF%XJsSsT{FRBPo`FHg9>ip41Qmfoj*CHVTL`ku z$q1YuL5D&LIfI2E7Q3wE02P639O)dOTvWutz`%4Lq`?);{I-pOfj0tV2YXQ+1A_!_ z)GLsYpbN`*qe0GLfH)@(8nj8)fj4& ziUfyTJp%)8(Hc$$1`Q6~6$}i##UQo;2k878-f~clWx-*-l!1Y_0>rlDDDh@s;H?C) ztvFVL)Hj0I)(mVMAg8i%tOo^wTLS|FZ<8`70|R??9RouKZ>tO!#2;-Cmu;wFVBl>B zg#ZU6qB?eQfJ&5|ISdTEogi@sj`SG}47}iE9Kc}-3YcC{D1~re~NLa6ig!SiL3=F($K>ArA;kW_B zhKBn_P&h&}#b%JW0wmnGfY>SwOi7?X+N#OTz`zc=Sat*N!B0@r4?#=^$HrlBvIb{$ z-XoxLLxE!-D5Q^q$`J<+aI78!*%ZKW6_hlNgNlO$j$I%%CqU)m1c<}xtt z1VzL1a2^H*CSMTq^&B1s2KFu03=9dvzPh{&3>+~{3=G14Agv4>pprt^ze^0%STX^L z2Tl+JwRdhcGB5}Sf!GopMj-K!ze1p5>;_0Y6vWox_yl5ygQ`*kj((7uh-qRB3?>{i zKoS;SyuQ($} ze}n+2#lkDWXu`+9kifYNl({7tK~35W&L!0h47@VnPE7&le2|PRSf+v#)Fk1RW3=L9 zU}#`q<4EBJ6~!RuvvJJlWnd8frOUv;E6)gGi@gTL7q24YY>;cgX;+C6lz169!S#hQ z*i90gNuZKVg%Olw9XP=Sg(}z|0T9d7!1^N?*f@On7#KuNKxtZ?5yTer1>I)MtI7BX zZh;omf*??8*9LPWAjar`RT(g_ajXRy69O_uml4Dkn*-_<@ai)L@G~$#!p;EfGzLi6 z8G<<)oZuKXf;tUcXc&V-D1sAQYM6j!5+Jsjf(^@HVB`1#vW)>0o@R_7wwOW{C<-kY zH^ObR1X}>L%?ixX;G7An2du%N62PeiN&z;Epxh3%z!t0@6a`8A3=EAapv+eF!1_?EDRC~ydI4I1Q-}3!GZ1x z4s^*PkQe+I5A!oHNP?P~yg^`3OGbjqi!iW%Btg4OdBefRNP^B4;Ee!#S@Igl2{DWy zw@TgvS(N~GCxZkFZxW+|AOnNsDUga}h)&SjH{KL5n?bCzk%3_cuRCM5AOnLyDFbgJ z*!2toWemKjU`Hx&zF5h?z?%kEp&?M5nZm%E&R8wTz_0?6G%^^w1sNDNFmRt{XJ7z{ zgZr(F4BWQN3=AMCD4&f3|8+x22lx60?B6tvBf~gs!!mZ#waPoz`&U`4OI1l*$kZfK#{P7 zQBw#s{NM)CvJC9+4hC*e_5_K8UB}MA#sLx#%>ub@IpY&S1_u7L1_p)~jJ)d@zk|%x zpT)qyyB@5Yfm3)f0|V~{Fo%UxVI~6u?c7Qntq(CF)yt^4i zg&7zE7^R+qqIMsnv@ioh1f!G$NY^27c->%>0+(Y)7`25N7#=V(uyIV}0TpBpd?0cm zn0x^yxATKU!uS~&xIt|=ka~CsSPL;Qh%$qWJ<14Ti|w7xz@Wf;oUvY*fr0Zp$TKIv zY-j+S1amkbW%U_wXb3nvj{$j=OH9EQRm`)q|77(|OeCZA&jvDr94r98hisGwrv zeZm+l%D})m0~97Nz{W6eg0sd;uu~@V)|b1UM@}arzoOpd-QA4~nmMV2+GH zZcZ@+??*64ffF29U%-K-!MPX|_5Z=vtl;DWd4!88Q1?L-3m~u0fiZU>K;Cu!$ zjfbgGl!4)gR2WDPFH^55=qwK@aIz9*0);&Xqm&@XD-ujIMHv`67^QB543uJ;FUr6$ zfl=z*a!`%I^p%x?VFsg=5-8S{n3jq%Fl=CyY69_8z;ZhnrN9YCjR};zE-*?tf+Aj% zX`?6u!wp6$P@|7mhiSVgC_be?jYwX7roA91GfEu*sWfIf401E06ett%S}@%dWnj=? zk~#_sL~Ew|q6`cMOj6rGsnM1Rl+G=fq&|XNZ4c%-Fi9N+`P`8Sl^$#X+qpHjXkbkO^|!AaVy60|WPEQ3eJO z2HVF5O51`Uxf@{8hlhbdbYC+A1Fsi&=!=cxyC?&LG>;eq19)Ud7&PW23>s!&VBTi)Vu=q9fi$7X_0~BYAXYSumy+>Y2*l7f|W3^aR`YqFfeTeHK43O zO!lXs{<5%RCCCI&Q7!BQ8g*cRw5y!K!VGL2As}7LK)PH&Og0WrF_5$C#TXzp3#k1C z_7V#N8waR8$aJoSfk8M9%nZ$9U=UF~B*wtN?#s=8 z>H!iD;P_w5z#!@gGADw8Dbx=%6$qM<;@gqJz`%6TpMgPKqe+~Bflok!fq}{0iGe{v zeG-VT=EuOm^iq?7K~jAYi0`k-z`*oXmw`b_9kdUDZ!>5txF2+Dyr_Yg1Oo%OKO<@c zasUIz@7)XxqQ)RL2M4HB5H$g@1t1M0lG#!8x)YB8m$;?@dhxtT^uwKu$h5DLPqI1$orrrMKUU&3Wb3~XA1*^j4D`| zW9|k91{rmb%Rok~0VNgr^&p#l%s^JWHe+C5^aCYl1`bw`EBrxh77mFmpt&{>n}g#L z=xm}u5Ss^VG-D9RXaNSM3Xt=H-%2nru$yjVU=UyoO#_(;atdQOhzUvTj1f5?VFosi zZD6-MN`giPL6${=nCw5-Gcaf{Mz52EjtMfxf}F;{aUHbSAr2%A>6J3ZZ;@nR0QE|X zBtcP73o=Jy69WTd0*J{TxfwKnlXMScSr5o*DIg|D_T_E+!px{kC3Kn<;CP5Bm zTDqQrfiVXZ!tD3ggS?WbFU7zhSjfOw0Aet3IrW+~-vc3@o-FVP+p`1_s&q_+kbY&~=L-W*!5JEl8PssvJlaDCH|KC{6?g><9SbN(7?ovUj;%v7xdEbHH5lZr9wtU>83qQmA_m4DCQu$_P@fO7@d^_t;$gFe z+2DB^kf(cO7#K7_!~Bd_m_Tfg7i3vi@Qk^!}2 zz-eQNwveqaIx!2#%`qz-(uiisQpgGRst1_s6tOrYet;{_Yk+K)`2QpMpV z8-q63+K)^iwI^P(F=&9Z=|?6|@(*~$#-MfiFarbQXRr-7K>T`;$G(6a=JT43LCX)s z|H=eP)GuGNG3Y#KW?;dFz7Lc zf^JXL-vbrj<-)+Ae;CR+>cYUFdk4z7?ZUvY5~`z)L60$n(NK>ukdyh=WymTw27@P1 zmET<;=7JW1>)wL~2*|>#5bcZ(47xv{!k`%Y3=w9k0htO~5vSh^H3{sRDNqi`H3n;; z9FS{3ZpZ2p1~v|GygUFEH1f=#c=-U;cONvN18NS=@luAxi##(ZUber1#fv;MC|<&&FVM8RSA$W>5=`dE$dQqx+x|6UP5gXY>_hK8)W`Z=?c>XBdA% zy%rbft`aq7P>ECYfsH}yG-w$HgrE44jlnpRfl-|qRPd;MVq-AQg7G$eVq?&11=*#+ z3>tW0{LIE+l3G;6z?jJ_aT=0BOf#hz7`#tGIG|0ljPA^fx%(4L2I4c; ziA%ulmW4(-$ldZvV9(=r_bsSmQ1M^}QOsz=p!*pr3@RuRAi@wc(2FfyuCtJI12Ui- zq7r0)GE^93z(R;H4gXkjqArc@L8Thg>O=45u>K^N<+V zuZPNjOT{)Q2jpsl`A`nXt)Nm79LeY&Wl%i=%JFNN7;BUn7?i+u+gc`2GfV~CCRz`! zPLx5z5R4EugQ_1$$809Xbs!y&rZF%u&SnC!Av)%QD;$Up2%CY815^iDOaSTI&vXIm zt{I@L3odonsW32TmoPByX9BT&zCp^UgG`_bYQ{Iv@lnbQ3?HD{f?(Q?Gfh@uU@!|; zW?=_BI?Tzyz*q%d*fKHS zU43p`zBB_v0#pWm)BBvmASZ5+W?=XTa?l=W1_rIaYz!_P zP=&DDKG%XA1X2cZ5W2J*NTYr)R1?%0c~CB@GeG@<1yGqRcx>fsFff?il4f8?{L98* z^-P+9;Q+`PKcEU>&M?$qU~mN~V+^)LmvaN@(*Fz90da%wW+)fr22Tx8Is#wKrF$4E z{@9g)ArT?2{}d_?*6#dMnt?&!0wh*_;BNf_ax0e%Xz4BJP#PHqhDra}7<4YQFfbg4 zN!|I!#$XK+aTAkaV9@u48Ui+!L01{d1-a@z!gd2=s5r<~usrF01k}ze1h*p-pz=_P zfpN}PZCJ~-5ZrPV{SRw-7K3$^{AXh@;sE7m2p_ajNoy;pC0W7@8t+tMU}w3rxh25~YU_yFyR=!!wLfsFJ5YXiHZ8!Bui$G}ho76v5@PytiM z0B#8CF-9;NFz8Q#ssqKG{t75(i!uX)?o}v9*o}c<71#vOU5v0Ml*(m@PeA5_HfKPh z6K+0JJp;r7-F75}f566|I0UNDkU@7IRH3;W1A~Y*#Q*gS47$6a;-GNxhKggW^ugis z0v0a%2cX)(9y^P~(Y+1jfSMDvPy=fi7<9it#X%M=hl*pfh(VY63dHju6ZSz=GP*J7 zN<)P~20Vuf8-QXSt@o&}096Qb8-s@#Xorjhivd)y0iF?ibQl=4L4%%*5-cG0S0;9F zH%AIQZsEwx&R`B2E69Lql!s|lVoA|qU@$X~VPL3WW@pf^hAIG6g$(+hP#!3f^=Cmj zASwODNE`+>j!8P8nurlJToewAAd5aE9se%#K1TeBAEmY+u)jZfHh?@Fzy63h&(}qNQ}EcqkCT9!5Ih>+;ZCu z>hpQofO7dBP+jW9+6B5}7R;;xiS7jrt9qT%0PXn(S>**DL)Zth+DluEfq@as^al6( zZFm&cL8p4N4Ef;DH@Rd3~4{g~205%9S7|i-IMUSArCZ zfoE_aP80`=GbpYH1(Fe%!#DwMf)UsRNIWrt5|uJ|=)#l_*1rum41Y(IL*j#1sXs;C$C?PVqzn#axz^DZt#@hhR9E(Bw)4=WfSJtq) zNs9%PQf4!=GeDwW2do1!k_BNav#>K5gHn<%3&@3$EU;=NmF4VpNMT|+Lx+LkF4Pu= zRt5$}bCzTS1_s|1It&cfAOWk*It&b^tn3Vadvzd3BpC=_g=qG00~MGdoXoHmEx5{n zHOloEgIFfs0Ik_%Fg>foz@P}#WdO3pmgRvl1B2OJ9R`M%tn3WdAVI4aIt&aG+1MGZ zKIt$psI#*(SpC*vV36TpXRu<^Wnj1gVhHLoFnDsZGg!&!GB9X!u`^hy>oPFJbF(v8 z8R;@GbaAsY_<_b{L8C!dplO)xJnRfs!MY3#TX@+S{Nr>P82X@o4*-o7J3?IrYYZ`- zG%#dfFyobGVED_+&S1tQ&A_mhkDbBnyA%UMEI&H~Lm+7MI033{1IRNsXM?oZOEWMi z2(mMnnMgA*_<$Ii(hLj*g6s?i_BS8_)(#G^P;m7V!e|L@=Yr}*D?P>tPUa==A=RrM zVA^9;6a9 zjH4vX4jJ{y1+8ukm@CD=unwvcb^- z!NO<^)&_0^fyQQaU7(U%-53}oA(Bk>0t~vdpyHs;i2+y~)GyI}4HZ7`#=sB-77k&V z_yRQc6bO#lAVvddGh_xdrGYBC60pipuu2Bq1yC7K2eJnu!vtz~xItyeB?xi- zD5yB7^468S1&IbecLs)wU`5CdV9;NF7a|W*%)np@s&p1Z?PF+XU|@7+IcmzlVAi3_ zz>p}$&R`7^w3?#Jz_3@0oxy6pE(3$4I6H&iYF$u%Ur=wgU6+C3usAz|)nQ!*hFA%9 z2DkIN3=CGcbr~4WO0Y9nJ=bMm*e1!&;QvvVfkE~j#By^`?ux$y;lK|fb7t%}fo894 zDQNaO4Pt<@m$o!J1A{3jd$mIKC4ijk!T1xT2b8}IWuWdbpyrjFDL93IROz0Cih~lI2UHw;1MMNy6`;6FfT}EG zV9@;o6L)1`sD+3l(w+r0u0U})2P_HNiT96N(0x&kY4JqCtpa_kINN_q?oXFv=+Jq89% zd3FXXD?J8=1bKD_{lidGLEd6uFa=ejXP`n8K+zM+B4q(;_~|n+{E=s8um%ZQ+2}Jc z7%8wbSh?#nFw}q;!TJmg#}wEZ{NnXN$*QB?FB{C5P;XVP&%hv{$j)HZtk1v@t;o)x ze-UZ{$aA_ApvfH+1J}(UF$0MKELr{mJj-u_YSVRRVEBrp4Rn$NgYJ2#tdBbb1HU=Q z&=5#%rT-NwljF|7p#LAr0d*dAxo<;KbE7*0gCkfE_;Pk#X{h)lcLs)3s5sbxOdJKE z&Pg2ugRVVP4amNFuo{?ssZbfPefdxh*uH8g2V~zyupWqgbD-iI-5D58Ld9zt7<3Oo z(-J5NKZJ^d`WUyN;-CQJwgCARR%Sx_2oEkoi%Iy-mMO9`Sc3$uQuG)Ytd-aqtn&337z#m*YCQ%91!Z;yt9CsGhF{9;41SaK zK#|^3?>8UJ=&08b=wM**wPaxM1MMJXo;ac2ZwFYasow7hn9*FXTYVD};GjU;0(Lkw z&~*EuGN;`c7%qWjAYKXt=h?+jSe29E zV3-J#hK;j<(h8`+FoP;`_h4XH4^|Wk>6C-!5%i;=vf%WR0_A|yi#If}f*kY^st=U* z+Mq=RDE=j^5FSRKVFY((*TLMX-wo9U@+<>`>0MBp9$J(+bTBY5hO&TeOEG(=$H1^o zg`L3~Bxv~`liU7=}-@VW&R;^T%qm)$>^?x zazJ^t2VyX&H-8W+408Kcs4&*v{4uCPkYV~%ybwN#b1|x8Xa{~*g|HnLWMZM8(P!DS(i;4p%`){Z>ngZ%&ZDP>}^LNx6gO@&R z2Cp*M4bm^LiGhJ}3k#?t#5{3HJ)}3Y73|_(Em-%3n>FttB+7JmK)nHK*WIuI$2GWa z1x-v|hssUzU|{$Ik%MLqk)oYLm%8)qu(iJ0vv_6D*;! z4?P$dl8|H}<5r*+A%kupR1L_41|&5Q6AGZR44w=OOOa%uCNNLjP|u*-0ac{x$-rk;NV0GvcGNTI9)~Idg%yh(*sqYVa$wMX1eF6V7mxwVfe$Fs)rSUj zrY8eKDOenu9l=w)ft<{+kz8=u0ULq_cV}S3;(CmcoXoIcd_AUe4p5HOb%q)YYRmLO z4K8C~&`*VmgA%N68I%K3v1C6jU5k(XE1tgEG}ah&U)y9fk^nG8MNy*yE5$ z26-QOg@yhJs7jFS`frgre~~!4e2*Zh24s&H)Id-{s}2d5 zVg;2azS^+zM2Hn6_YcGemnTB3pz`E~4y-&8VFi^Zx_YqkM3fb^JP~6>ElmdE$@fLje{wpz_2Kss>aV^dYH%WaT)htiKln!zLtIXjTUI z63d`!Kqg#3QUftzDpWSli-F-Qk}T8&NO`grst8n`2s?p23ib@dh%->xUM~g)3nW>% z5uozq4O9^*tU@4)AYtXepvw*oP*8c21D1o7Cm~RAPMsN|5dPyiXzN2h5R0;^^u@IUsvxK@9{I0Paw6kVQwJ;#dm+{XnQnklDKNP!7nr zmr!jW<0_!yAmfDLYsj$773j`GQt5?9CAd5ZfR`tWq1r&MU|{2jb75fM1+5_F2Ca@P zc7cw4OEVZ<2Q>qV7&~1+M}al`&;)l_c|iw*1c6Q!lU)Hi6(UF|gn>bJC5RaeI>k%w z5_n!2WVkSB^qwIkTaJN2*{FhzfgxC_j)6gbE_jq2>^)GK#1IO)-A^ZAyRM(7#Bd7>E@Btt=(SArY>b~32w69#b^*f>B=2nU@M#uNoo1e)mtn+3K3 z>^X+;ugwe$ieaFI0-!#DFlgZ^LpY};1A~}9NDnBpLIi`~)iE&0T>*zFXu1s?reK00 zTx}x*gK!3DC>t~}2Jt_LCk!S)0nQK#Iw40m6*LA67G((SpUc3&(~#lDz#t4d)|z$IbH%ZPJ_PnAmUyJ=opa~9SjU0cY?;^gh4Yb!l3PmV820FproY0 z5Dq$jjVTGy>|BLQ)l|b`b_uVGQBcDhv!_HXvuExic_; z16mlwVqoI{MM=1i8v}!wD@Y#HNfQRutqh@d1q=*g`k>JRs44tn3=GoNAXOVdrhwA{ zk|koGZ5Rn4X;AkU5<8%+unge_$_xyWU>Q)=0C6260fjenGcZVcfi!};r%2ME^%xA{ zphMVrQoxfj9hI@2^nx4gM7vi{yUa|LDC*14eH=Q z3(erz1eJsB8;Kq|p% zz*(A&V-H9WWFuG)I3=-hT!aaN&48+W1`-67m|!!Zg18ED~Gox{K&4USz^ zFIXsmSzr&cacFxnFz|vln}i=oWnhqY1KA8}qdwn}k0f;_3f z4Vr}Ia00PaxItT`IGjOj4Ia>{0S=efVhjuh+@P&e9IhY57#J+LL0hFb+)Nl47#z4k zTctQ4Tcx-`TctQWK;i-1psi9Io*;7~7?`hHGB9vzePLu^;G7r0z`zCG>cn9X!NkD8 z4chg{0om%r4ch9&0om%r4ch9&0om#VaiA&4FbR++%|M=1;0A4V;sDiW3>w^^txg;k z4?s?3U_PhCz`&)E%*4RJnO@Goz`O!<@|bfm_z3xNK;g;{gF+oRmuyOohU|`?^A7IA))r5tCfg5yy8TU7kZU%190cPBg z1I)NV2bgg~4lv^e9bm={Ilzn?bbuK*T0cPA> zj72OA3>gBT1I)O2z$cRx2!IYS;}!sON(4X$m~lf7FcSbBV8$)R2uj=y0-yuTxW&O` zeUkv_05fh0#tIe&hGqfK0cPAXj2$ct3>^ZX1I)Og2bc+f4lv_}9$+Q_I>3w@dVrY# z=m0Zr=mBN|paaafp$C`=fDSO@Hev)No*e?91I)Nhz-~Jr06M^o+l;Y~1$2O!Am|J- z?pa*mGsw6XfIP_!I)jXRA&AMqtzgH%z`Y3MT@G&08D!kcK+yx*EoKcq)@loAJ0vJN zLC)p?V5G8FXeC zuR183GlR|y<23~{L1%{XfzJ$M2Avtk2R<{58FXeCpY#{73x2UMFmQp-4C9m41|3fY z8gk>4yA0ZA!3|nJ!6$zY9RoDh4(Vkbxo~4SlQ(418b$ zY&RPR$Te&nRjdpQBA`G6X+X&HK^4tnWnf?iof*cb2~y7tIx~z<`#ktitevb33|yI@ zGsF00-ho^STK2&&3vv+y12gE%Fn+nKtPBjI0{jY6Yzz$Cpv^@5ibp^PwsC{b4C7Y< zspVi`2Avtkuly5a-XzeOVFHFpAoa{93=9HByFtARZqS)w0>&UV6F2D0FaZ+~n}r*6 zq=tYeh|R{p3_3GRz)O{lfq@HrW|)A_B#=3v0#Cqq1K1qUaT)@CAYm2;aH+kBje!B2 zGT0fIL1%^u_=9$vaDmSZ69@#|t;7vF97G@peA&S1gA<+_#6W10-YHqsD756 zfq@HrT9}Zg1_uKJchC|B1|cnoy`XcrgtUKv4$Wd<2Avirq{Ga?z`zAQElfzy2c#Z! z0+*0Jh{?df4l*GIbXXK9_cAgtgH8(*GB5_|0iPBo)I5WOfq@5fT9{A^*bZ*cX<DwbgyjP` z85p=hYpsP9z&3G%7E=i;g4itJ+E5shQ@C|&85o3>K^_-iU(2j2rRghi=ZqR9AB1RxK3r~IlgNP-F$-xaeElk836x;&b(;65UL~I^#GB7BB zgGI#d8R)bt1}^YvVLU+~8E(+&FFe5@4Gi3%)53T{Kx_eS&>1#7;UGgnTU$ZavvEXo zGB7ZMP74#U2Qj(8r-g|)YJfJngAV8taRL>t4BVhIfJB@@!WT}z)uSUIhBpWgo}ZJ8FX40PtyvJ%c|=b7&3TT7eZau25}ka zv@o7_PzZ29BC5ll3sjbX&XwZn1c^IvgH8+M0Vm@CZc9+W^nyYugc}@seIPXv49uX@ z!g%_fxEUC@z^8@rO#I5tzyMB+Jd;4`8Ni8=XEKP*!VNlIhi3|i&A|;i1c+xUh|L3z zOrB{Vt)PNq4L1V=Gw8H1p6MVa7wDh|0iK!WJTQ}IfpjrIOqdN~L$W8&9FP)FMq}e) zU|A6YVsn6-oIH@z!ni@_ z{_rdXiAzA-xeR0sDDirLYyq7X#v$L#xE570Fl^vCSPnJ)5X5wFY#at9 zYYuQ$=Q+Xw&X=Im!g!8?$`J={aI78!*%ZJHIxURnIH)*C;0B!*#&ZHxE>3{hdJ<&o z3I=A-X<5-5?c2_T0y6UHSsbqFjuNFF!0<5F}c8}h4DP%=7WURV~Br1r-ku60ktkzAbIl{ z*jxr?&}m^j&+mW?0i71c^I8#f9u(-bFfQ;3T|Do?`9M7-&^fH{L3$X#l`zi-5F464 zK7y2h(uW!!0|PVYgf5;>ASM_1gf5;hy&w}nRX-1CmnZ`RH>m38`3|Z2nL#IX@%+f( z1059$I-!f_mpVTKg8-;G!6O8^`Iv!009?9>fH^DzpcA@yL>WO@i$ef(LKlx1m}4LS zHdmYxq(4F!)IQ*mVASPjU`P-EozTT230~HkApkm|iwAl_mjLL5E*@F1Ooafb>Bl3- zXv_~ep(_@Aj19>7Y#cNA7#PGri-UON89{7u@CjW!ii}f0t_7zTB}Pz!VGsb9(aK;q zNeF;W=;Bdf1SJy(0dVG21^XiaVwoCPKj?%mSAGTtF%!^PZ|aO7wmA5NE*?$B+i(lC zpca5m=;F}^b0i?f=zvv$PUu<&G6r-)7mqF@h%F92p^HbK(Nlnd0TOlwV5c!a!p;!P z(GUQaAx2QAf%=9#&=a}@z&X_fERz7S%@k}H=!CAfAlpDEbn%!mg4p5;pcA@yEEre8 zZL*KKrn}eK|+Bin9&rjCWP@0*if*VP%uY;K|+8hk}(pb26Q49PZZ-tuo}>b zTs+ZW4hP7tSjJkA8qkScJaJ&-83aHla`D82IV=p43OpW+&jlG6q`-mf2_9*e0-eak zVqg$1W#CB!yPiR~ zjDaT=>_`Ox(1~0;Xd^Qe{OT<7Ya`9w>T_p}WOh|$!n{l@g1A_qQL@plaiChAp6S;VD!6tJ+tjU9@ zVqoKF6JlTx1D(jllg|iZi-S+(;+e+CB+S4d06IE}XF8b8AOJd%i)RU=kT3&7g8=A6 zE}msze|IpjgR&z?9PB#KiCiE7G0=%zJj)r+3NbJUK~Ci2S;zPQWG?7LE}r#Z-3$Vt z@eiI2U=E7_=wKzDO<+|V0-zJQcs4UW18ETeoyf(rh4BsObSeSRiCjEe89#weCv7jYAAva9M)S-&0X7Y`3piWmdK4{6X@Ts*u?1)%XfMrm+@ z5@iB~ItQaP=qxTC38oq`28IqsY0z0*JW@_@ILW9nffCjQMrqJlTs)dgQ^Y`LaY=)kR6IINv&29#Da|&G zfq_S#X#vQ|jMAX9xOj}2mVw;NC=JR!JQhrQ#6V|pNgoBBj&98aIuyu&Ng8w(7mqCy zD1}=vNrTSf;;{$w9GIj*XL0d3GJz7j2a`1DEG`~bFfW2h8gv#Hk2@2nr<=hf4LXa9 z#}mxUVPar+6JuZiVR(uGP57{J1c8s5G6$cb6$fU6TE%P}SztD(UB$*x24;g=aBLhU z;4}eBRBRl5U~y1eg^lAk*o4L4v*c_+N0x3AV_*PbuzhR{Y#dcWpwx5_OkU(=U=RbH z#l_DTk#!C{sax&vUf8u2#A_1i8C;8g9;i^ zOOPcDJfN-RqE;X_2LrQEHv@yH^DyYV+hy(|5A#K5$vBC13^p%B{Qi0hcXNd3cgUz8yN-$DG89KP)0Wfg*2!z z$PO2XFjFxD!VdTuDoWW8&uGC=~Mfo3+26QE!Ooq5mb z1`5Uos7@{T$uk!uVZrDI3dUArb_QASf)+QBjDiU}gFGlGJU~Hl(S)4=vgHRn$gOY6 z&LE#(%)sb%N0Nb|%9Nc!VLQ}(CRqlCUl2bq8iKswz@WhK91@1IvJ4FJl3;H$m4W;R z@+D|VRSg4!f)-Q}C~QCnq9`(g!^RmZ3<{eBWW6BMkyk${c|cWy!bTwj$~h{_z|acS zR?NVlR00(TD{6sqKvu4VC}Ol>P?-xA23ZM;Zm?6)tz%&0xCTDw2Q=-$#sNy$G8cIm z7#Ooap}hfWL?}Gb_JK}z0w>xmP-qL9u`|em(_t1U=3+rya4O9LDV%1;&Y=7VluC0z z$@07zJA=Y4sF5JYhDm_~5tMd5Lxn+(ZGZ@aLKU7q5ozZKR3*sk3~U^r+#s_poq>U| z4&-8i7mzrLg}c}RbZ8XV#dRPTOPRx5TnBP-8i)&OKrz;V6wWt?xwrx3;v44d3<^$A zBS9|akcPWB8Y&EOu@OWVhl>-SDxoe0wDRf1hC zV;{l5zz80azW^0ag@^7u&}MOP=q>}fRmT$M)@2~KR)DymMj+!dkiwgmFt@G%xmC!D zok4*Me2oSJ1IVpuGVss^U$nu%zyNaV1c)#Ww`xEQ0K1iqV~Gr?oL?owz`zfhUQ-3F zWD#2dnp;o>tz;2f31X^&mZWebg0%2~oXrP{2=HW*I)esiJ&U9f=-v}e(0Uee$a)q~ zfdE#fdoMW0~<$yECT~SBx6>C}3VsHUYL zL6DQc#zO^n$ucnTf(jAv1R8@jXxWX#V@3#%A%F!mr`r|L+MsAFfg#k%w%8?V3s-$8rv3{#lXNU-3LmG;3F-WWoCke8Q2%j1}T#N0^)&c zX=YHPiGhLRI7h6KD4#1_nlv|Dd^2Xb7l- zYynr>0ibq3Ap@frNSZN#+YhQjpM`;ehcT4Vfq{V&bm1JMB4|pv04kdf*AJRA<1J)h zR0QcSvSMf8O=VzI1o`5k6*~i8BfWsn=)tl1g3yP^7#O$S|c$7l&M{Q*=q9&S1h zXr{D~fzc9V`UGo;>6ReV-&?~>w+5M>V#ChB{Tr$u$#j0urGkusARP{`AZ2MV+;o4? zjC3IbV<5qxPXDN1Z0U|_f95(ljz zDb8SEw__0p-G7vp$G~ns36vMuK?^+C9Y7A0U z1t9F81t9De0wAw2FoPEQa3dD_aDx~6aB=uDF)%>a_b@>#2uO9o4H{@G0^L6bYC179 zFyDy)UBy+)#K6FsZOp*HoZ!U3z@yd+;&*sMSCp_h&t+m@;Nk!+9N|3#T0H^^cs?)- zRK4<@1&Q4N)puMRCqTataqm3TQ!&tt;XE3t~&KgVvSs{sXZU*g@+`c>jag8sK#$ybO#W zjsbXG2`?jc3Z2geieQYIz_29UFPK&c3nG(fS*4T|b>pc)+{D$Kx&w628Dhv_Y3T?wBr z(+0%45`J}1f&{NC;Wq^{LF-BcBtS6>H;}tR#1rsT2~?<&&SHZz`+h0q!Un(1T76=U<0o!5l~(Ny3P!=u0%iu#AILx ztt$~w1rq-QbKx(1uN(7ZPLCZ0~>q>+SL9;*X zps6n*qf?+-gdMc5M93J#W?~1eD-kjQv02zb6G1|rAU1SeiI7()$QMAbUaO ztdQ>xusNVfAR#}HFbe~?d;u*H0jCUh$hs0C|G6Oj;B_TJfuNgw*+J_{gn~eBW#9zO z8wmx2ggF?vI6#`g>q>+|?!wlU2!+XkmQZkjt|nsxuPYIX$YqDdyig>_XAJD1m=}uT z04<~d$GlK9==28(cF?*Kp%_pqQeX$ID-ntV#hnHN8+cubP|_-P&?TJp3=BfaASMGi z6$qt(+{VEUT2~^J3S#rHgJvg$(m-rbZU7k$URNTN4sr?`cwLFG9Ox!;cF?*KVR?{B z22Rkr5@Chs;HngqAcYk{Nq>-Glt9;Zff|XzsvsrT3ysktX<3t}&5#fFGBXi}5|vaUo#2ed|n z4ZN;IM6U*<9<)|NL?6UtVBiLs&;wdI0?NINkaZ;@2Fal7%fRbOM4C@{MhT240L2MRqkc+f|lz?109kfCOw5~*?9mHeB0V=j_JY@yi1dMOs%8hfwI9S}VBluqWMHu1WMBZNGU&PzkqP{u#S`Fl zC8F{Tpo_mi>q&t-Q`kZ4N<@`G9*3?g5mj*pnFC%|A|`(y zWDaOuiI^%#F9SPhU5S_xh|R*8U%(({31V`vgVvRZS%ZRGfE~21M9fBji-AD_94umX zVqBnG=-9yPO8A06GVHfDGBEH3gETO(gVvSsg@D)s?4X%fzHpGC3JhEvAnUm}x_JR6@VXK)#|SRam81;}3}Q~8!j*v?w5~+V86*rT{KQ=1!0Sr5I6${GaB+a{ zEMo?(D-m-AGePT0_#!}duz}Z=@I}oB83~$O<%q_|IK`TYr!0Sr* z($8`+FmM(#@MVBXD+YGZx)Q!jP{{;cSHhRI3UuQGcwGr!ZZtPUbsj`DXk7_kKB%bY zU2Q;0fpjrIOqdN~L$W8|9FP)FMl%Dg_yDad;hPI$vVkT+ zH24;r1(^U^SHiatq>F(aw62725r_?S+hP!#gB{e8q_{x-UH2HfY+7q9b67I{Sd@-aBLg~C2J0FR_8kcDmN6^LF-ERj)KY& z2S}_Q1KAY74q8{jcN|n4B(Q_lmGGSam5UP~ww?sp3SC#icghBIofvps3E$1%p!?21 zl`G#ZkQxSX<;r&(#AX3ku6%buYz}tNx)Q#-AoB$vp>+?W)q#PFV-sje2xwgi-+d61 z4ZN;|?~yfVc}Ot>-(!e>K~`4|{jLF-D` z!0Sr*-nH{FFhEk+dypOma3#$50mO!;kB=ZFp!5+2G6uA+gzpoG$p&6m!uMq_$OKT; z&-WEk_49p)RQ=#}C44_XOEuWQH^1@y3I{Eu05vE0gus*fkaZ<|B47?=T?wBkcvS^t zT?wBUm;+f?!Y9rM(hpfz!Y9EP3tv~lhrF(YPX^p-F95GA;X__m!Y9X=3|?2l#Q|DT z!o>k{J{QLc*t!xvc}5T$vaWV4=4?a_{VHpfu9I~Kg9}LwD418vcAU0%O37-YyEx2u#U<;tOS%Eo_ zbtQb(;820AE8(+Y1m!TO1-4-Q4Gdfy-5?7<>q_|S7(r|aHa;&#SV=7!t2xAhc zQwm;t!WRnWfYz1pMKX4R)PUBN@I^6#+GY#_pmimD(O?c}T?t<-<64j!(7F=7II!`M zbtQc9U=Cq_{-!N!2s zmGDJ?y$D`c!WRQ}KX_dUUjo>j43aE-NsN{tn?UPI_>v(yLF-ERQowBJx)MHj#umuB z623&R>p|;E_)@`+gsdy!O9QKbtSjM5XPg3GSHhRUxCFeegd5ay0*OQ0R@^^W7#KiO zP(Bw2$R*%)C48A+S3%a5@MSZ;hOH~%%K--l19)8tUoO~W$hs1~Jcud=E{<)mbtQcH zj372-T?yYbMkCN-4$v|HzUg2#WL*j05=JNZx)Q!+V1IWoaD%cVNF3}sb_OmEkN|jH z3Ey(?MQzY^C4B1`1whLxK;c;X4Bk4am9@zO#(; zKz?T6;_wmy*%}60SHgFW5yXbBE8%;>mk9?@FA}&;rjv(Cdj%HzW-orAnQu_xWJ1-AnQu_xWVf_AnQu_c)&|LAnQu_ zc)@Eq7$ECP_(YjNp$=VF!Y9GB2C=S$Pl{<1VqFQJJktimx)MGmrX7fNC44GiIq13) zJ~bv#!h)_V;nQR~f>>9=r^9p#v95$qpXmZ(T?wBt(>27p5q_`+nLsHVx~_!J9?XNTE8%lw0ws9px)MHDFb}$}gwLG`)YFBoE8+75^FVjJffk2= zFg(S88gg75pk*~&9OG17__d0&kH;- z#Ki#`LgL~8?ZV;W01YIvg4UIAafpe7G^vBhOQ30|pIbl|IVlE#?$iR!STQPtf|r34 zw626v1;plHV4k>@fq_v~7j*o`F0lHYpnJ>M!RmK|q6(^h4~Wgdz-$Imzc*H#fkEap zXqrGBG{DKYMVtY;KtdhFlq*UsW?@DiN}P;@FdvNI@wPdknTnLW{wok7_dIx}LR#=zhu z$-tn(2wJ1Vpd1VpcU5CxNJ5CK_Cm#@)EF2T61ClpA!@4a5Vgyl){>K46Ec^@4``p(2RW`WHwsFsRQJXJANmW@pe?ABiZd`=1v%pZ=-xjUb_UHEP=zpO2!d`C04akw16^7Nq)~M)R1?%0bxOzRKZgg=y-m0CJ6=xR~L2$4PFV*RaWc_8WIu=42-Vq44M+}AR!8K&ti~!K;CH zAyfpk9Tns$#yOyM8S0=_VHU3J3>y9t3=C^Qj3@~PhVQQI40;nf7#QMZ7#P56^xW7P z^g$vzAcZ<0MXIGxBcX10hH^n}FPA~MJs2tua(h2QTsHwK4sts*V=<_K3YiH|2}IzR z$ucmgmr5`&q`R>*Xf#MLFdPFhx+NGGl-=1GG=D&KzyeR+ffs0S89xgZZ%$Ra$j87dC)Kmd~X0jM}A-DV)fRi8q|!N#hANxqd__41 z2K8kU3=C23>zEVWdx-a z(5-b_LC+U?B;v1Gbn+N zWd|=|WAkQbP+ktTAXSZlVFkopjD{+VA&d?T%0HlT4QdPwjPejSmocb-XTViJOCt@S z0jvVLeF2tLRX}G&8$k1e3g~b(SoNR+T?wcx^#KwMvg!;B5)gwK-58XOp~A-M3=B>X zVJ5JBAQvI81XQ(vssshPYA6y%IT_0FS7%@-ftpdwz@SrRGpK(>Nn z0IQ7*TpYXQ85sCKfoC{DwI~+{s9rYM%*DXK*bR!R1yFNxAu+|kIHy$s7FFG#s7m!_ zXHWp2u-*-l*$Cq916|+QJwbti;gvTu=6XRfr|bicIR?f)P?ebP16myhH6P??Uj=Y% zfCB9|R2by^a)>ZfF({HS0*&D#B-BAc0P3nMFfj0gCW*N?rYV3LU2{MJhZq?c4Bq%M zFfc9xc}f7P2jQtGMVO}+fjqU#2j;0oAWyORvNI?Mf-W>#1oD)C2n)(b3jQ>CmZ-9y;GR{XO zn8W{p9M10t%Q*i)8RxPeJA)ypre|OTId!@}JA+{ns2#w_2xFdFKhlm7qWe=N*nukiZ7z9a|-^#~^7L><`evgwV_d&O5M@7MyorRWCU2 zz*;QuyrTs*7!+b55Q9OEAG5wEM(2Vz^DukxD8Mphy=AknSsFsT%aj4f+FU106T*T zIGZRlg5=Bt*%?g0`9zr!l%@&-p)sWjj;WO(ZczyXqZ%WqQT8>Eok95%)Dn;zE0n>3 z4+t$!ABYR`i7nVCUcu}P%1Ka5Kt6F&f%~Ky zDh%>T4n!D-Pa2>qp+0F)0p;{I6$S?WO^gf-#`DA&7#IV={+R$(iSUn?D$GBDVE;@F zhWRHD?4O4qeiFlK=L!|(+X+X(+isloh{0rtMYl+yA;q5jDN`)37+3-V7k*gs!F*%_3bpq7CA^9bT_P)du23WNN^ zqXzaS4*w)TRf7Fv3R;pYz5=u?!xXe6S9~RiX$CqnO(G1`GXu3A_(1&>1~bDJ1_lXm z{|TfOB4`d;petNxD|2=Qvo!jAF@Fz=zHG@udc(g0FK!p4Yb@1 zX#pIwVFgG8X#pIwaTiDga%dAXXdN6gWE~u6Ar}i`Ar}jHAs2MX9xDeM$3c)b$jV== zCpLxpF)*;IgNCx%L5ux3z^nY&7&JkP=|HRe*cd<-GjM`d`9at$oS;>HYz#X=OA|Oj ztNb8r&^&Jz0|UFN2_xu!cF+a&9M(5M3wS}NE>(gDA;70DC4v^^ae+@=0&P1H-~=to z<8T0(C&39?lm|U^i4!zq1U+?$6SOD~dg>AvXi*-AOBbl?!wFiH$Kg5wbU{5QXi*;Y z)Fn>PqC5_F&=OD&PSBz}=&4JbphbD`QX9or?q- zoaca?q{0bWbjJZcNri(GwCIk*1Z1!P#DS1ScU%Pw9A+R-DsX}p-9b-X;shMi!~{hy8wY67ItO@l9M@MB76t}R(CRoY$m%#w(CRoYkoy@pI6NqYA#u)hOI4&;6B>3t$F67m5Tmsf(Er9tK+yN7&BO4r!H~HFoKR?f}Faw8( zT>4-RWOW>u0hj|>9miz|=0H}*aUrjc<3e5?$7RM?hgcoQHH!nhI*w}r$djC))p1-4 zK}-ft(CRp@MIi5TaDrCHaY0wdv2kdF_rWazt&RglC&=00^Z{BQ4~k80kSOSAC$Ok6 z13S{{IBp-NwUE_u+`de_qM%X;eCiU9Iw+}uSI6;~f|Cds9jOFf9mgvx4O&zNS{=tLcNlc)5+`Vl6t6sJfR%&Orka6)SK%i3)FtMF zO`ro$!Fs_9<#?6zK`ZM(3*~rKKuiWs&_X$0RZuCy!3A0<$EyZ%5f3M5p&YMzKWLR5 z=j=KL240PI;DvH*93ZnrKpHwh3+2EB*cogbYM^y)9Hp>@av%+SaCttcqN%Wja=e-# z_27kayxO4Ub8H-2SQ!{NzzgO0WI&7NI6({L_+&vYVqjnf9l*pVcL96=6Q6=88)$by z2?GP4;(pLVIZn_*IX)$jTIfPKK4s7fIu7tcIex=fkb2NuKEDyTg}@0~D93LMVl#1q z7RvFPfY>aYpqXucPY@frP>$bA0d%Ypc%dA>PcO(EP}#@t3)xUclL4Fx_(5lEFmP~!7RvFbg51f&DP6_D zz@G+UgHAyK84g}3$Da;z3I}+hoPgYRkZGWWasu)ol?+^nK46T!6+C~XQTf|P(v zXoD@36Ho#%Ilv3$1XMsRI!@3+IRRA=lYtYoP)-1xDna417o-ccP)r&A|3Dn@LODS_caVC};xj>g5R-v{8)QN_ zXrUaa_=hf(6Ex5R9d!g=C@0uF8FauAXrY{73)pX*poMaRtspiFILHOtKuSQaOoA_JSA63HE&k=>og8AH-x} z;64vh2wEryb*n=i1B2j%TOd8)g>pjj-kb~!oS=nrLJDA;I6-TZgcLz+7I4KV1j#9! zpoMZm${>$J7s?5#@PUqY0xy&kmR|)j2eeR5SQVs~ffKY)PS^;&U=X$hF*!Iv z3+05ZLBTD+30f#8Y;zlQ&wr&2Ivr@Vg~LEP-(@$333c~Ca7eBE|lZWDg~(qFO=iX zRRc}J6*F+>K~#el%5mp|N_`Ga&_X%x0+0d$h$)32QzSS+3+1?rbU+K`I6({LxQjt- z15VIFIqq^$hgSAy79oS=nr+>Ic%H3J(5$f;}``mlv^+)bc6 z{5Zf1<+xktKwZ`baT#c#9Cte?1UMiO)d4yrRDu(

~w4xTS4P1bON|qu}?4*{&7nJ1ZLHdE{Ne5&UxRynP zEJG34_aG-in)paTot#(zN-c2z27qQ3KoJ8iazG;_Fh9Z~7MzyASroJ?6UhtUt`@ik ztt<5)M44HYLtw5l~&7hJHo)aMH7v4R~FUd$P0x#)H ziO-1UN45NeoQP$P0ugN zOhuGouE7Ct-Jvc4KAs`2(1ZXQmtaT+cc9=Zd>td;>>$T*NP+~fsLh8o7olMTN-&UK zJ_Be;BV=p`Ne(09fFm3-<_1m_sMZDrhaoa0I9T&i%Rv(#P$NNS8N*T`ETI+U=RgdB zssWX@(9#{A%V1IkWoF;u5l+=O}&>Fyu)S}E1(AYS1`yR-n#fUlpR$ahSxr?(i+@~O=;DI-!WSv_8^Cv7J zK<4g2tNjxt1Fglt846Mq!0au9Ed*i6&CJUOHCQ0~Vv0fSr4l3f zC}Tc&X&6dP2lXa6N9E?g-H4p2z+Gf`pys6J8G(B8;0S~|50p^A5zdgA2MKX_`U(kv zCPYY)jpW1l_+m&i58TuN83C>*p>YD54 zQUAhAFi`SBo6-Zd3ZNc@DT5__NCjO2DHG$1Qo$`wSV;s+H(;m1;|6LHBwxamBtwde zO1KA{{e1(VMG1UuH#F5V;65=n0`I{w0!`yW3OaBXEgqgsps@!k?344rV;-PX z1dT4ZLj8}?i9l{i37K1A|cyKv-KspSdlhwc>0bi62smnl70$WlAnY;k0Lem2) zc3?9HMXBK1j2W_1i}FCtd4@tz+<{so;9`maZW*ZO4PBB5ZswzPH^6IcAeAcvL@FNI zA4|$lFD-@+us~&ygrJM((Oe12?C4g5`kkO58?fzgXMy(&z`9q^aDa@1fYM_zv?H7d z-KmeJ4N|Vfr{;C#OP&SMp1Xl2gGs5^5IQ9!TUtV*xS@1Id{TkZyMhIP5{QEMSY^ z`oLZ5*bwHM&B}<5VVOa$ zz|&&!8Tp_g93Fg20%z^y{DMk^dL)CuHo)^dpg zS_%huG_uNk(DI$iJa7X5B?`ew5M(YWF2K!aXheY};4VZ&4#ZM$)`4v;MzIwsI6+kh z#9#2x07XG&UUE(;Xulaqv^XB@eo#gRjnP4rgWQAUdgy2py2p^pE+k9A{s5T_8j1#= z(hAlG5&IEugqquK?r9}*m(bOdiX zf((KCITJK2391V~Qy4HAc$o(E4lD_YdH$fo}8omdG0(iOr+x(R>o%*$7dzxg5wS1oTQZ0q|$Uy7a}RO z2-GnJuO_Ul1ZVHc%6Ra!e^O~0XtogMVbDEZm6f2P2C@#IvNArgBp$R>6l6nQW(i0t zEwu<_A(GZokQ?LEa#AbeA(leSfSL^2wVx02Ry_DRPe|k=CWR|2p`|1!qQM3y7K7uV zq=+FhGTtfF&CN9^J~+fN#1*vevm`$Uhc;1g+fwnF1OQiwBkAi8IqQFOa(0?03{TVdoxp@GN9wSKuSS9uKYAmJUDwe27w%sR1BLA1VvnOVqQEVoWb^i zySR|V!H}O?RFszw(gulj5DP64Bo}~IdZogn2OLe{z>H7M0J#RF2Qt_I;z1K1D12bt z`1m|XnUn)M0wXsuxhNlW;2}sK#2KKtO3nuF(gG#p;`sd1BA8}Sa$$&vCKm8g_{z%o zlA_9Zzfd305J_G!Lt;sOCfG1gQv`fHOJyavq5>^C&&)3cw?0ciNfwl}U>N|!hDHU5 z0=0&dQi~vUUm~bH1m#$8vlNs9%2FBP+U67})B}yb@z0NF0I3;Xtauxen5<0Hvf<@S*!qO_i0PAOI%>kgqbqwMlA8 zJeX0G3O<4gl)Di2fcy#$()eVM@8jVo#epI)FSCRpH?aboQa}UBAQg~w0dsP4Mm9r! zDJV?Piz-kwfebGK75}Mupo8F`+Ciep*$g0;f)Z_rZmNVa)wdA&6Np-chJ1BLha_a)wI9KxsyX z02mWy4HHAD1dKfys*H)DSORAHVW=1*Lja5kvk;^Vo}{3RK&bLkD9y+a0+oSNFeNaT zFfxR|m|0L=%nSwf^I@Dks30Rl0E`JU9jp#+FqE+zs(v$+#*!8;K&3uGX-0-n7*m}E zVhkfg42-D-6@mmijAsHBWn>72F=OFE7)1!CjUg}_RpGkAU`&`DAR9yG!+0=j7#Tuf zOoWXXi5b(zK$wl1a2sP`Oqd-Y8w0V}7>H?OEF_>1BpcLNm|0i?J^`wv97;1Xgu<9p z;X=VM<~+DiD2zEDDg<(8=zJJ&4lBeJSh7GVRKqkV&BzdjsS6|UmOxeChSH1-Au#4$ zs1PXi|An%-;P!_YLz!++nvo#{#teiCfpmsL*$9_oP1kR1Dt^b*pvkjmO)q-peo)&X-0-n7*h-q(+m(F!FbwGQAUPP7&9I!1W^bv z7eQj$5CXG76snPtAq>WZSpzaFWIl`svx$)*1jYVo4AdGnuE))i1!dwC}D-esj zFe)OL@p*8wf?&)JxKJRBiR{!M%u)xc0j8RfAp*uk));}Iu@|cGFqCFw2!?qCrV&e! zE`_Sz38g{x7nFGuN@Hmuv_Pd`E+g8Jm>P);7EFzyFw0@SV`i9$87dE;F8ujP9HBSQ#`3DXTSD+G&K81059P&2+mX-0-X81pw& zh!r#xqpSck8sV@&a8^emV1~h518N_@*vO`ZV44O}iewU)DF6)ubtuiq5DH@oaDr7p zOHc?KK^j0cm_lhrh5#7zC0r=x4U`Ep3ro|%7^>tolxAdz#ngpSxx;k9>;#REz}Tzd zCIw@fgi(&!LRH5?X-0-X7&93v#LiI6V60NhpoH)~gyjKMFdIrUGK68;h*5<2LRFKd z3l@ZG&}d;~2!t{7;6hX}k^B7y@Iyf(wPhn8;>fdW8vUlOUAF;uV+@b+{4?ufXgufh!4x zF(*KUuzLlj(Hg3Xks$=e^nwe8!kEZrVR|JAs-X}{WAO@1NjqE#hF4&AOo1y2g)u)s zg|K@Crg1h@6(d6kjJXOf6bfS^n}z9>Lr@JDp)?k+z?3|PE5Yyz%#JT`C802805`q_ z2GjT(t}z70ZkrNrx-Jh!vO}C2%F7Fy;lQ5O%M?G*&}Zf$9|~vkOXt zhIpY&WV0~6vIwebBb3JC6_}Faa3vUCf!T2dt|S!3G~mUTz+f70!!?G$n6KbMp)e-0 zS(sj7g4!eqrLlMgrbHdC1j8#ZJ51n8LSf7aP$BHG0@G*>RRyY7piD0)&Bzc6V6Ij?hC(Qf#Var+?QkU+UV+&$1+F9%#{2*k!tNEA#@SF+pn3($Tm_{W8A4%9WV0~6 zatNy7B9zAB6_}Fea3vUCf!Xl|t|S!34B*3;z+f7G!!?G$m|Reepn3($L^cc4D@ssR zhEN)dS71uq;Yu*P0<$9ot|S!3+yE8A9xE`7(NI;OdIieNg3^o(p)e-0S(sjFf@+uu zrLlMgrerx>35Hi-c5Hzw3578Q`0>RGOyh32#t<0u6kI42#zZy?(<_gl8a_g4EM9>r zVTU>jTfG965`of;452V)0aOTktiT*64OIoISD;KSD9y+a3S%Ohh3OS1sD?l&jm0Z4 zCFyV_7_kDgqXe!b6vn&&6~gWnn8s?TDp0)wWp+VnP`v_WBAbQjl|@ih8=*87ufUWX zhbzJG3e1ixa3!HIrU7)|6uVbo8gIijhQOGw;6kA=CbC(WUSWdTBnYLkcm<|J9j*k! zD=<4u;7USa%n48->|TLsw1%nz)hke@7nBCoD^Mn~S(si)f~qQn(pbC#Q_>Dsg5edI z9aG>+LSf7gP$BGIfoYr#RRyY7pv+ZJ8dR@9naE~gdgTyQ)kP?c#Var+&*4fiyaKc1 z3tUMkj2R$^uLOf>{0-L_0%LMPHG=9DC==N%Os^rafd6x@CwY15V(?1 z7;^(u2z#u+G)6;Jf$9|~GYd+C>J=yx*(^-2G(lBOgwj~N0#mXat^~s?Fgv!um4w2W z0z&v=1*UN~Tw@4~c?vER3S%Ohh3Sq2 zu>y0PG*lI+UV$>TpfspnfijWJ!t{z0R8=69#^M#2l61Hdj97u$Q36*I3S(Y?3SsvO zOk*`v6{uc;GP|HOs9u3Gk@a~V3578yK!vc!3QVIlR28UR zfik_IG^k#IGLg-~^hy#`RUwqd;uV;ZcDND@ufXh>0#_0WV}5`NVfPA5<7}uZP`v_W zu7c8_dIicvHVe}$hoGu1LTM~sfhlpl>N#W=4OIoISD?%+C=IGt zpiE@5Ful?QRW%VxWAO@1$#S?746nfK*aBA)3S$b0;)@lS#@%p@Au#4CxKJpJiEI|8 zR~|t%e1y_iyaH3g4s{f^dIc&a0;NIq3Y1v@rLo5f%yH6CRiJtW%G83=pn3($L^cc4 zD^5^VflwNYS71ug;Yu)K1!hMHTuCU5c>yYf-77GS)lgNSdIietg3_RR1Tg5edI9arE=LSak;F?|-p{hXj3Y57DN`vYZC==N%Os^b*s=5fJv3Lch z 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%0D%QzoII!?BSQd;Sq&8etAlZ1 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 Jfa4LIgaISp`7Hnd literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/setools/policyrep/boolcond.py b/lib/python2.7/site-packages/setools/policyrep/boolcond.py new file mode 100644 index 0000000..c3c0608 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/boolcond.py @@ -0,0 +1,167 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol + + +def boolean_factory(policy, name): + """Factory function for creating Boolean statement objects.""" + + if isinstance(name, Boolean): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_bool_t): + return Boolean(policy, name) + + try: + return Boolean(policy, qpol.qpol_bool_t(policy, str(name))) + except ValueError: + raise exception.InvalidBoolean("{0} is not a valid Boolean".format(name)) + + +def condexpr_factory(policy, name): + """Factory function for creating conditional expression objects.""" + + if not isinstance(name, qpol.qpol_cond_t): + raise TypeError("Conditional expressions cannot be looked up.") + + return ConditionalExpr(policy, name) + + +class Boolean(symbol.PolicySymbol): + + """A Boolean.""" + + @property + def state(self): + """The default state of the Boolean.""" + return bool(self.qpol_symbol.state(self.policy)) + + def statement(self): + """The policy statement.""" + return "bool {0} {1};".format(self, str(self.state).lower()) + + +class ConditionalExpr(symbol.PolicySymbol): + + """A conditional policy expression.""" + + _cond_expr_val_to_text = { + qpol.QPOL_COND_EXPR_NOT: "!", + qpol.QPOL_COND_EXPR_OR: "||", + qpol.QPOL_COND_EXPR_AND: "&&", + qpol.QPOL_COND_EXPR_XOR: "^", + qpol.QPOL_COND_EXPR_EQ: "==", + qpol.QPOL_COND_EXPR_NEQ: "!="} + + _cond_expr_val_to_precedence = { + qpol.QPOL_COND_EXPR_NOT: 5, + qpol.QPOL_COND_EXPR_OR: 1, + qpol.QPOL_COND_EXPR_AND: 3, + qpol.QPOL_COND_EXPR_XOR: 2, + qpol.QPOL_COND_EXPR_EQ: 4, + qpol.QPOL_COND_EXPR_NEQ: 4} + + def __contains__(self, other): + for expr_node in self.qpol_symbol.expr_node_iter(self.policy): + expr_node_type = expr_node.expr_type(self.policy) + + if expr_node_type == qpol.QPOL_COND_EXPR_BOOL and other == \ + boolean_factory(self.policy, expr_node.get_boolean(self.policy)): + return True + + return False + + def __str__(self): + # qpol representation is in postfix notation. This code + # converts it to infix notation. Parentheses are added + # to ensure correct expressions, though they may end up + # being overused. Set previous operator at start to the + # highest precedence (NOT) so if there is a single binary + # operator, no parentheses are output + stack = [] + prev_op_precedence = self._cond_expr_val_to_precedence[qpol.QPOL_COND_EXPR_NOT] + for expr_node in self.qpol_symbol.expr_node_iter(self.policy): + expr_node_type = expr_node.expr_type(self.policy) + + if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: + # append the boolean name + nodebool = boolean_factory( + self.policy, expr_node.get_boolean(self.policy)) + stack.append(str(nodebool)) + elif expr_node_type == qpol.QPOL_COND_EXPR_NOT: # unary operator + operand = stack.pop() + operator = self._cond_expr_val_to_text[expr_node_type] + op_precedence = self._cond_expr_val_to_precedence[expr_node_type] + + # NOT is the highest precedence, so only need + # parentheses if the operand is a subexpression + if isinstance(operand, list): + subexpr = [operator, "(", operand, ")"] + else: + subexpr = [operator, operand] + + stack.append(subexpr) + prev_op_precedence = op_precedence + else: + operand1 = stack.pop() + operand2 = stack.pop() + operator = self._cond_expr_val_to_text[expr_node_type] + op_precedence = self._cond_expr_val_to_precedence[expr_node_type] + + if prev_op_precedence > op_precedence: + # if previous operator is of higher precedence + # no parentheses are needed. + subexpr = [operand1, operator, operand2] + else: + subexpr = ["(", operand1, operator, operand2, ")"] + + stack.append(subexpr) + prev_op_precedence = op_precedence + + return self.__unwind_subexpression(stack) + + def __unwind_subexpression(self, expr): + ret = [] + + # do a string.join on sublists (subexpressions) + for i in expr: + if isinstance(i, list): + ret.append(self.__unwind_subexpression(i)) + else: + ret.append(i) + + return ' '.join(ret) + + @property + def booleans(self): + """The set of Booleans in the expression.""" + bools = set() + + for expr_node in self.qpol_symbol.expr_node_iter(self.policy): + expr_node_type = expr_node.expr_type(self.policy) + + if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: + bools.add(boolean_factory(self.policy, expr_node.get_boolean(self.policy))) + + return bools + + def statement(self): + raise exception.NoStatement diff --git a/lib/python2.7/site-packages/setools/policyrep/constraint.py b/lib/python2.7/site-packages/setools/policyrep/constraint.py new file mode 100644 index 0000000..9994c5b --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/constraint.py @@ -0,0 +1,297 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import role +from . import symbol +from . import objclass +from . import typeattr +from . import user + + +def _is_mls(policy, sym): + """Determine if this is a regular or MLS constraint/validatetrans.""" + # this can only be determined by inspecting the expression. + for expr_node in sym.expr_iter(policy): + sym_type = expr_node.sym_type(policy) + expr_type = expr_node.expr_type(policy) + + if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR and sym_type >= qpol.QPOL_CEXPR_SYM_L1L2: + return True + + return False + + +def validate_ruletype(types): + """Validate constraint rule types.""" + for t in types: + if t not in ["constrain", "mlsconstrain", "validatetrans", "mlsvalidatetrans"]: + raise exception.InvalidConstraintType("{0} is not a valid constraint type.".format(t)) + + +def constraint_factory(policy, sym): + """Factory function for creating constraint objects.""" + + try: + if _is_mls(policy, sym): + if isinstance(sym, qpol.qpol_constraint_t): + return Constraint(policy, sym, "mlsconstrain") + else: + return Validatetrans(policy, sym, "mlsvalidatetrans") + else: + if isinstance(sym, qpol.qpol_constraint_t): + return Constraint(policy, sym, "constrain") + else: + return Validatetrans(policy, sym, "validatetrans") + + except AttributeError: + raise TypeError("Constraints cannot be looked-up.") + + +class BaseConstraint(symbol.PolicySymbol): + + """Base class for constraint rules.""" + + _expr_type_to_text = { + qpol.QPOL_CEXPR_TYPE_NOT: "not", + qpol.QPOL_CEXPR_TYPE_AND: "and", + qpol.QPOL_CEXPR_TYPE_OR: "\n\tor"} + + _expr_op_to_text = { + qpol.QPOL_CEXPR_OP_EQ: "==", + qpol.QPOL_CEXPR_OP_NEQ: "!=", + qpol.QPOL_CEXPR_OP_DOM: "dom", + qpol.QPOL_CEXPR_OP_DOMBY: "domby", + qpol.QPOL_CEXPR_OP_INCOMP: "incomp"} + + _sym_to_text = { + qpol.QPOL_CEXPR_SYM_USER: "u1", + qpol.QPOL_CEXPR_SYM_ROLE: "r1", + qpol.QPOL_CEXPR_SYM_TYPE: "t1", + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET: "u2", + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET: "r2", + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET: "t2", + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_XTARGET: "u3", + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_XTARGET: "r3", + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_XTARGET: "t3", + qpol.QPOL_CEXPR_SYM_L1L2: "l1", + qpol.QPOL_CEXPR_SYM_L1H2: "l1", + qpol.QPOL_CEXPR_SYM_H1L2: "h1", + qpol.QPOL_CEXPR_SYM_H1H2: "h1", + qpol.QPOL_CEXPR_SYM_L1H1: "l1", + qpol.QPOL_CEXPR_SYM_L2H2: "l2", + qpol.QPOL_CEXPR_SYM_L1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2", + qpol.QPOL_CEXPR_SYM_L1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2", + qpol.QPOL_CEXPR_SYM_H1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2", + qpol.QPOL_CEXPR_SYM_H1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2", + qpol.QPOL_CEXPR_SYM_L1H1 + qpol.QPOL_CEXPR_SYM_TARGET: "h1", + qpol.QPOL_CEXPR_SYM_L2H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2"} + + # Boolean operators + _expr_type_to_precedence = { + qpol.QPOL_CEXPR_TYPE_NOT: 3, + qpol.QPOL_CEXPR_TYPE_AND: 2, + qpol.QPOL_CEXPR_TYPE_OR: 1} + + # Logical operators have the same precedence + _logical_op_precedence = 4 + + def __init__(self, policy, qpol_symbol, ruletype): + symbol.PolicySymbol.__init__(self, policy, qpol_symbol) + self.ruletype = ruletype + + def __str__(self): + raise NotImplementedError + + def _build_expression(self): + # qpol representation is in postfix notation. This code + # converts it to infix notation. Parentheses are added + # to ensure correct expressions, though they may end up + # being overused. Set previous operator at start to the + # highest precedence (op) so if there is a single binary + # operator, no parentheses are output + + stack = [] + prev_op_precedence = self._logical_op_precedence + for expr_node in self.qpol_symbol.expr_iter(self.policy): + op = expr_node.op(self.policy) + sym_type = expr_node.sym_type(self.policy) + expr_type = expr_node.expr_type(self.policy) + + if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR: + # logical operator with symbol (e.g. u1 == u2) + operand1 = self._sym_to_text[sym_type] + operand2 = self._sym_to_text[sym_type + qpol.QPOL_CEXPR_SYM_TARGET] + operator = self._expr_op_to_text[op] + + stack.append([operand1, operator, operand2]) + + prev_op_precedence = self._logical_op_precedence + elif expr_type == qpol.QPOL_CEXPR_TYPE_NAMES: + # logical operator with type or attribute list (e.g. t1 == { spam_t eggs_t }) + operand1 = self._sym_to_text[sym_type] + operator = self._expr_op_to_text[op] + + names = list(expr_node.names_iter(self.policy)) + + if not names: + operand2 = "" + elif len(names) == 1: + operand2 = names[0] + else: + operand2 = "{{ {0} }}".format(' '.join(names)) + + stack.append([operand1, operator, operand2]) + + prev_op_precedence = self._logical_op_precedence + elif expr_type == qpol.QPOL_CEXPR_TYPE_NOT: + # unary operator (not) + operand = stack.pop() + operator = self._expr_type_to_text[expr_type] + + stack.append([operator, "(", operand, ")"]) + + prev_op_precedence = self._expr_type_to_precedence[expr_type] + else: + # binary operator (and/or) + operand1 = stack.pop() + operand2 = stack.pop() + operator = self._expr_type_to_text[expr_type] + op_precedence = self._expr_type_to_precedence[expr_type] + + # if previous operator is of higher precedence + # no parentheses are needed. + if op_precedence < prev_op_precedence: + stack.append([operand1, operator, operand2]) + else: + stack.append(["(", operand1, operator, operand2, ")"]) + + prev_op_precedence = op_precedence + + return self.__unwind_subexpression(stack) + + def _get_symbols(self, syms, factory): + """ + Internal generator for getting users/roles/types in a constraint + expression. Symbols will be yielded multiple times if they appear + in the expression multiple times. + + Parameters: + syms List of qpol symbol types. + factory The factory function related to these symbols. + """ + for expr_node in self.qpol_symbol.expr_iter(self.policy): + sym_type = expr_node.sym_type(self.policy) + expr_type = expr_node.expr_type(self.policy) + + if expr_type == qpol.QPOL_CEXPR_TYPE_NAMES and sym_type in syms: + for s in expr_node.names_iter(self.policy): + yield factory(self.policy, s) + + def __unwind_subexpression(self, expr): + ret = [] + + # do a string.join on sublists (subexpressions) + for i in expr: + if isinstance(i, list): + ret.append(self.__unwind_subexpression(i)) + else: + ret.append(i) + + return ' '.join(ret) + + # There is no levels function as specific + # levels cannot be used in expressions, only + # the l1, h1, etc. symbols + + @property + def roles(self): + """The roles used in the expression.""" + role_syms = [qpol.QPOL_CEXPR_SYM_ROLE, + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET, + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_XTARGET] + + return set(self._get_symbols(role_syms, role.role_factory)) + + @property + def perms(self): + raise NotImplementedError + + def statement(self): + return str(self) + + @property + def tclass(self): + """Object class for this constraint.""" + return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) + + @property + def types(self): + """The types and type attributes used in the expression.""" + type_syms = [qpol.QPOL_CEXPR_SYM_TYPE, + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET, + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_XTARGET] + + return set(self._get_symbols(type_syms, typeattr.type_or_attr_factory)) + + @property + def users(self): + """The users used in the expression.""" + user_syms = [qpol.QPOL_CEXPR_SYM_USER, + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET, + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_XTARGET] + + return set(self._get_symbols(user_syms, user.user_factory)) + + +class Constraint(BaseConstraint): + + """A constraint rule (constrain/mlsconstrain).""" + + def __str__(self): + rule_string = "{0.ruletype} {0.tclass} ".format(self) + + perms = self.perms + if len(perms) > 1: + rule_string += "{{ {0} }} (\n".format(' '.join(perms)) + else: + # convert to list since sets cannot be indexed + rule_string += "{0} (\n".format(list(perms)[0]) + + rule_string += "\t{0}\n);".format(self._build_expression()) + + return rule_string + + @property + def perms(self): + """The constraint's permission set.""" + return set(self.qpol_symbol.perm_iter(self.policy)) + + +class Validatetrans(BaseConstraint): + + """A validatetrans rule (validatetrans/mlsvalidatetrans).""" + + def __str__(self): + return "{0.ruletype} {0.tclass}\n\t{1}\n);".format(self, self._build_expression()) + + @property + def perms(self): + raise exception.ConstraintUseError("{0} rules do not have permissions.". + format(self.ruletype)) diff --git a/lib/python2.7/site-packages/setools/policyrep/context.py b/lib/python2.7/site-packages/setools/policyrep/context.py new file mode 100644 index 0000000..f2f3fc7 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/context.py @@ -0,0 +1,68 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol +from . import user +from . import role +from . import typeattr +from . import mls + + +def context_factory(policy, name): + """Factory function for creating context objects.""" + + if not isinstance(name, qpol.qpol_context_t): + raise TypeError("Contexts cannot be looked-up.") + + return Context(policy, name) + + +class Context(symbol.PolicySymbol): + + """A SELinux security context/security attribute.""" + + def __str__(self): + try: + return "{0.user}:{0.role}:{0.type_}:{0.range_}".format(self) + except exception.MLSDisabled: + return "{0.user}:{0.role}:{0.type_}".format(self) + + @property + def user(self): + """The user portion of the context.""" + return user.user_factory(self.policy, self.qpol_symbol.user(self.policy)) + + @property + def role(self): + """The role portion of the context.""" + return role.role_factory(self.policy, self.qpol_symbol.role(self.policy)) + + @property + def type_(self): + """The type portion of the context.""" + return typeattr.type_factory(self.policy, self.qpol_symbol.type_(self.policy)) + + @property + def range_(self): + """The MLS range of the context.""" + return mls.range_factory(self.policy, self.qpol_symbol.range(self.policy)) + + def statement(self): + raise exception.NoStatement diff --git a/lib/python2.7/site-packages/setools/policyrep/default.py b/lib/python2.7/site-packages/setools/policyrep/default.py new file mode 100644 index 0000000..175b709 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/default.py @@ -0,0 +1,128 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import symbol +from . import objclass +from . import qpol + + +def default_factory(policy, sym): + """Factory generator for creating default_* statement objects.""" + + # The low level policy groups default_* settings by object class. + # Since each class can have up to four default_* statements, + # this factory function is a generator which yields up to + # four Default objects. + + if not isinstance(sym, qpol.qpol_default_object_t): + raise NotImplementedError + + # qpol will essentially iterate over all classes + # and emit None for classes that don't set a default + if not sym.object_class(policy): + raise exception.NoDefaults + + if sym.user_default(policy): + yield UserDefault(policy, sym) + + if sym.role_default(policy): + yield RoleDefault(policy, sym) + + if sym.type_default(policy): + yield TypeDefault(policy, sym) + + if sym.range_default(policy): + yield RangeDefault(policy, sym) + + +class Default(symbol.PolicySymbol): + + """Base class for default_* statements.""" + + def __str__(self): + raise NotImplementedError + + @property + def object_class(self): + """The object class.""" + return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) + + @property + def default(self): + raise NotImplementedError + + def statement(self): + return str(self) + + +class UserDefault(Default): + + """A default_user statement.""" + + def __str__(self): + return "default_user {0.object_class} {0.default};".format(self) + + @property + def default(self): + """The default user location (source/target).""" + return self.qpol_symbol.user_default(self.policy) + + +class RoleDefault(Default): + + """A default_role statement.""" + + def __str__(self): + return "default_role {0.object_class} {0.default};".format(self) + + @property + def default(self): + """The default role location (source/target).""" + return self.qpol_symbol.role_default(self.policy) + + +class TypeDefault(Default): + + """A default_type statement.""" + + def __str__(self): + return "default_type {0.object_class} {0.default};".format(self) + + @property + def default(self): + """The default type location (source/target).""" + return self.qpol_symbol.type_default(self.policy) + + +class RangeDefault(Default): + + """A default_range statement.""" + + def __str__(self): + return "default_range {0.object_class} {0.default} {0.default_range};".format(self) + + @property + def default(self): + """The default range location (source/target).""" + return self.qpol_symbol.range_default(self.policy).split()[0] + + @property + def default_range(self): + """The default range setting (low/high/low_high).""" + return self.qpol_symbol.range_default(self.policy).split()[1] diff --git a/lib/python2.7/site-packages/setools/policyrep/exception.py b/lib/python2.7/site-packages/setools/policyrep/exception.py new file mode 100644 index 0000000..ce367c0 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/exception.py @@ -0,0 +1,248 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from ..exception import SEToolsException + +# +# Policyrep base exception +# + + +class PolicyrepException(SEToolsException): + + """Base class for all policyrep exceptions.""" + pass + + +# +# General Policyrep exceptions +# + + +class InvalidPolicy(SyntaxError, PolicyrepException): + + """Exception for invalid policy.""" + pass + + +class MLSDisabled(PolicyrepException): + + """ + Exception when MLS is disabled. + """ + pass + + +# +# Invalid component exceptions +# +class InvalidSymbol(ValueError, PolicyrepException): + + """ + Base class for invalid symbols. Typically this is attempting to + look up an object in the policy, but it does not exist. + """ + pass + + +class InvalidBoolean(InvalidSymbol): + + """Exception for invalid Booleans.""" + pass + + +class InvalidCategory(InvalidSymbol): + + """Exception for invalid MLS categories.""" + pass + + +class InvalidClass(InvalidSymbol): + + """Exception for invalid object classes.""" + pass + + +class InvalidCommon(InvalidSymbol): + + """Exception for invalid common permission sets.""" + pass + + +class InvalidInitialSid(InvalidSymbol): + + """Exception for invalid initial sids.""" + pass + + +class InvalidLevel(InvalidSymbol): + + """ + Exception for an invalid level. + """ + pass + + +class InvalidLevelDecl(InvalidSymbol): + + """ + Exception for an invalid level declaration. + """ + pass + + +class InvalidRange(InvalidSymbol): + + """ + Exception for an invalid range. + """ + pass + + +class InvalidRole(InvalidSymbol): + + """Exception for invalid roles.""" + pass + + +class InvalidSensitivity(InvalidSymbol): + + """ + Exception for an invalid sensitivity. + """ + pass + + +class InvalidType(InvalidSymbol): + + """Exception for invalid types and attributes.""" + pass + + +class InvalidUser(InvalidSymbol): + + """Exception for invalid users.""" + pass + +# +# Rule type exceptions +# + + +class InvalidRuleType(InvalidSymbol): + + """Exception for invalid rule types.""" + pass + + +class InvalidConstraintType(InvalidSymbol): + + """Exception for invalid constraint types.""" + # This is not a rule but is similar. + pass + + +class InvalidMLSRuleType(InvalidRuleType): + + """Exception for invalid MLS rule types.""" + pass + + +class InvalidRBACRuleType(InvalidRuleType): + + """Exception for invalid RBAC rule types.""" + pass + + +class InvalidTERuleType(InvalidRuleType): + + """Exception for invalid TE rule types.""" + pass + + +# +# Object use errors +# +class SymbolUseError(PolicyrepException): + + """ + Base class for incorrectly using an object. Typically this is + for classes with strong similarities, but with slight variances in + functionality, e.g. allow vs type_transition rules. + """ + pass + + +class RuleUseError(SymbolUseError): + + """ + Base class for incorrect parameters for a rule. For + example, trying to get the permissions of a rule that has no + permissions. + """ + pass + + +class ConstraintUseError(SymbolUseError): + + """Exception when getting permissions from a validatetrans.""" + pass + + +class NoStatement(SymbolUseError): + + """ + Exception for objects that have no inherent statement, such + as conditional expressions and MLS ranges. + """ + pass + + +# +# Other exceptions +# +class NoCommon(PolicyrepException): + + """ + Exception when a class does not inherit a common permission set. + """ + pass + + +class NoDefaults(InvalidSymbol): + + """Exception for classes that have no default_* statements.""" + pass + + +class RuleNotConditional(PolicyrepException): + + """ + Exception when getting the conditional expression for rules + that are unconditional (not conditional). + """ + pass + + +class TERuleNoFilename(PolicyrepException): + + """ + Exception when getting the file name of a + type_transition rule that has no file name. + """ + pass diff --git a/lib/python2.7/site-packages/setools/policyrep/fscontext.py b/lib/python2.7/site-packages/setools/policyrep/fscontext.py new file mode 100644 index 0000000..a17b0bc --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/fscontext.py @@ -0,0 +1,123 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import stat + +from . import qpol +from . import symbol +from . import context + + +def fs_use_factory(policy, name): + """Factory function for creating fs_use_* objects.""" + + if not isinstance(name, qpol.qpol_fs_use_t): + raise TypeError("fs_use_* cannot be looked-up.") + + return FSUse(policy, name) + + +def genfscon_factory(policy, name): + """Factory function for creating genfscon objects.""" + + if not isinstance(name, qpol.qpol_genfscon_t): + raise TypeError("Genfscons cannot be looked-up.") + + return Genfscon(policy, name) + + +class FSContext(symbol.PolicySymbol): + + """Base class for in-policy labeling rules.""" + + def __str__(self): + raise NotImplementedError + + @property + def fs(self): + """The filesystem type for this statement.""" + return self.qpol_symbol.name(self.policy) + + @property + def context(self): + """The context for this statement.""" + return context.context_factory(self.policy, self.qpol_symbol.context(self.policy)) + + def statement(self): + return str(self) + + +class Genfscon(FSContext): + + """A genfscon statement.""" + + _filetype_to_text = { + 0: "", + stat.S_IFBLK: "-b", + stat.S_IFCHR: "-c", + stat.S_IFDIR: "-d", + stat.S_IFIFO: "-p", + stat.S_IFREG: "--", + stat.S_IFLNK: "-l", + stat.S_IFSOCK: "-s"} + + def __str__(self): + return "genfscon {0.fs} {0.path} {1} {0.context}".format( + self, self._filetype_to_text[self.filetype]) + + def __eq__(self, other): + # Libqpol allocates new C objects in the + # genfscons iterator, so pointer comparison + # in the PolicySymbol object doesn't work. + try: + return (self.fs == other.fs and + self.path == other.path and + self.filetype == other.filetype and + self.context == other.context) + except AttributeError: + return str(self) == str(other) + + @property + def filetype(self): + """The file type (e.g. stat.S_IFBLK) for this genfscon statement.""" + return self.qpol_symbol.object_class(self.policy) + + @property + def path(self): + """The path for this genfscon statement.""" + return self.qpol_symbol.path(self.policy) + + +class FSUse(FSContext): + + """A fs_use_* statement.""" + + # there are more rule types, but modern SELinux + # only supports these three. + _ruletype_to_text = { + qpol.QPOL_FS_USE_XATTR: 'fs_use_xattr', + qpol.QPOL_FS_USE_TRANS: 'fs_use_trans', + qpol.QPOL_FS_USE_TASK: 'fs_use_task'} + + def __str__(self): + return "{0.ruletype} {0.fs} {0.context};".format(self) + + @property + def ruletype(self): + """The rule type for this fs_use_* statement.""" + return self._ruletype_to_text[self.qpol_symbol.behavior(self.policy)] diff --git a/lib/python2.7/site-packages/setools/policyrep/initsid.py b/lib/python2.7/site-packages/setools/policyrep/initsid.py new file mode 100644 index 0000000..0197c74 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/initsid.py @@ -0,0 +1,50 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol +from . import context + + +def initialsid_factory(policy, name): + """Factory function for creating initial sid objects.""" + + if isinstance(name, InitialSID): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_isid_t): + return InitialSID(policy, name) + + try: + return InitialSID(policy, qpol.qpol_isid_t(policy, name)) + except ValueError: + raise exception.InvalidInitialSid("{0} is not a valid initial sid".format(name)) + + +class InitialSID(symbol.PolicySymbol): + + """An initial SID statement.""" + + @property + def context(self): + """The context for this initial SID.""" + return context.context_factory(self.policy, self.qpol_symbol.context(self.policy)) + + def statement(self): + return "sid {0} {0.context}".format(self) diff --git a/lib/python2.7/site-packages/setools/policyrep/mls.py b/lib/python2.7/site-packages/setools/policyrep/mls.py new file mode 100644 index 0000000..2541704 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/mls.py @@ -0,0 +1,463 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=protected-access +import itertools + +from . import exception +from . import qpol +from . import symbol + +# qpol does not expose an equivalent of a sensitivity declaration. +# qpol_level_t is equivalent to the level declaration: +# level s0:c0.c1023; + +# qpol_mls_level_t represents a level as used in contexts, +# such as range_transitions or labeling statements such as +# portcon and nodecon. + +# Here qpol_level_t is also used for MLSSensitivity +# since it has the sensitivity name, dominance, and there +# is a 1:1 correspondence between the sensitivity declarations +# and level declarations. + +# Hashing has to be handled below because the qpol references, +# normally used for a hash key, are not the same for multiple +# instances of the same object (except for level decl). + + +def enabled(policy): + """Determine if MLS is enabled.""" + return policy.capability(qpol.QPOL_CAP_MLS) + + +def category_factory(policy, sym): + """Factory function for creating MLS category objects.""" + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Category): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_cat_t): + if sym.isalias(policy): + raise TypeError("{0} is an alias".format(sym.name(policy))) + + return Category(policy, sym) + + try: + return Category(policy, qpol.qpol_cat_t(policy, str(sym))) + except ValueError: + raise exception.InvalidCategory("{0} is not a valid category".format(sym)) + + +def sensitivity_factory(policy, sym): + """Factory function for creating MLS sensitivity objects.""" + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Sensitivity): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_level_t): + if sym.isalias(policy): + raise TypeError("{0} is an alias".format(sym.name(policy))) + + return Sensitivity(policy, sym) + + try: + return Sensitivity(policy, qpol.qpol_level_t(policy, str(sym))) + except ValueError: + raise exception.InvalidSensitivity("{0} is not a valid sensitivity".format(sym)) + + +def level_factory(policy, sym): + """ + Factory function for creating MLS level objects (e.g. levels used + in contexts of labeling statements) + """ + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Level): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_mls_level_t): + return Level(policy, sym) + + sens_split = str(sym).split(":") + + sens = sens_split[0] + try: + semantic_level = qpol.qpol_semantic_level_t(policy, sens) + except ValueError: + raise exception.InvalidLevel("{0} is invalid ({1} is not a valid sensitivity)". + format(sym, sens)) + + try: + cats = sens_split[1] + except IndexError: + pass + else: + for group in cats.split(","): + catrange = group.split(".") + + if len(catrange) == 2: + try: + semantic_level.add_cats(policy, catrange[0], catrange[1]) + except ValueError: + raise exception.InvalidLevel( + "{0} is invalid ({1} is not a valid category range)".format(sym, group)) + elif len(catrange) == 1: + try: + semantic_level.add_cats(policy, catrange[0], catrange[0]) + except ValueError: + raise exception.InvalidLevel("{0} is invalid ({1} is not a valid category)". + format(sym, group)) + else: + raise exception.InvalidLevel("{0} is invalid (level parsing error)".format(sym)) + + # convert to level object + try: + policy_level = qpol.qpol_mls_level_t(policy, semantic_level) + except ValueError: + raise exception.InvalidLevel( + "{0} is invalid (one or more categories are not associated with the sensitivity)". + format(sym)) + + return Level(policy, policy_level) + + +def level_decl_factory(policy, sym): + """ + Factory function for creating MLS level declaration objects. + (level statements) Lookups are only by sensitivity name. + """ + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, LevelDecl): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_level_t): + if sym.isalias(policy): + raise TypeError("{0} is an alias".format(sym.name(policy))) + + return LevelDecl(policy, sym) + + try: + return LevelDecl(policy, qpol.qpol_level_t(policy, str(sym))) + except ValueError: + raise exception.InvalidLevelDecl("{0} is not a valid sensitivity".format(sym)) + + +def range_factory(policy, sym): + """Factory function for creating MLS range objects.""" + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Range): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_mls_range_t): + return Range(policy, sym) + + # build range: + levels = str(sym).split("-") + + # strip() levels to handle ranges with spaces in them, + # e.g. s0:c1 - s0:c0.c255 + try: + low = level_factory(policy, levels[0].strip()) + except exception.InvalidLevel as ex: + raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) + + try: + high = level_factory(policy, levels[1].strip()) + except exception.InvalidLevel as ex: + raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) + except IndexError: + high = low + + # convert to range object + try: + policy_range = qpol.qpol_mls_range_t(policy, low.qpol_symbol, high.qpol_symbol) + except ValueError: + raise exception.InvalidRange("{0} is not a valid range ({1} is not dominated by {2})". + format(sym, low, high)) + + return Range(policy, policy_range) + + +class BaseMLSComponent(symbol.PolicySymbol): + + """Base class for sensitivities and categories.""" + + @property + def _value(self): + """ + The value of the component. + + This is a low-level policy detail exposed for internal use only. + """ + return self.qpol_symbol.value(self.policy) + + def aliases(self): + """Generator that yields all aliases for this category.""" + + for alias in self.qpol_symbol.alias_iter(self.policy): + yield alias + + +class Category(BaseMLSComponent): + + """An MLS category.""" + + def statement(self): + aliases = list(self.aliases()) + stmt = "category {0}".format(self) + if aliases: + if len(aliases) > 1: + stmt += " alias {{ {0} }}".format(' '.join(aliases)) + else: + stmt += " alias {0}".format(aliases[0]) + stmt += ";" + return stmt + + +class Sensitivity(BaseMLSComponent): + + """An MLS sensitivity""" + + def __eq__(self, other): + try: + return self._value == other._value + except AttributeError: + return str(self) == str(other) + + def __ge__(self, other): + return self._value >= other._value + + def __gt__(self, other): + return self._value > other._value + + def __le__(self, other): + return self._value <= other._value + + def __lt__(self, other): + return self._value < other._value + + def statement(self): + aliases = list(self.aliases()) + stmt = "sensitivity {0}".format(self) + if aliases: + if len(aliases) > 1: + stmt += " alias {{ {0} }}".format(' '.join(aliases)) + else: + stmt += " alias {0}".format(aliases[0]) + stmt += ";" + return stmt + + +class BaseMLSLevel(symbol.PolicySymbol): + + """Base class for MLS levels.""" + + def __str__(self): + lvl = str(self.sensitivity) + + # sort by policy declaration order + cats = sorted(self.categories(), key=lambda k: k._value) + + if cats: + # generate short category notation + shortlist = [] + for _, i in itertools.groupby(cats, key=lambda k, + c=itertools.count(): k._value - next(c)): + group = list(i) + if len(group) > 1: + shortlist.append("{0}.{1}".format(group[0], group[-1])) + else: + shortlist.append(str(group[0])) + + lvl += ":" + ','.join(shortlist) + + return lvl + + @property + def sensitivity(self): + raise NotImplementedError + + def categories(self): + """ + Generator that yields all individual categories for this level. + All categories are yielded, not a compact notation such as + c0.c255 + """ + + for cat in self.qpol_symbol.cat_iter(self.policy): + yield category_factory(self.policy, cat) + + +class LevelDecl(BaseMLSLevel): + + """ + The declaration statement for MLS levels, e.g: + + level s7:c0.c1023; + """ + # below comparisons are only based on sensitivity + # dominance since, in this context, the allowable + # category set is being defined for the level. + # object type is asserted here because this cannot + # be compared to a Level instance. + + def __eq__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + + try: + return self.sensitivity == other.sensitivity + except AttributeError: + return str(self) == str(other) + + def __ge__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity >= other.sensitivity + + def __gt__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity > other.sensitivity + + def __le__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity <= other.sensitivity + + def __lt__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity < other.sensitivity + + @property + def sensitivity(self): + """The sensitivity of the level.""" + # since the qpol symbol for levels is also used for + # MLSSensitivity objects, use self's qpol symbol + return sensitivity_factory(self.policy, self.qpol_symbol) + + def statement(self): + return "level {0};".format(self) + + +class Level(BaseMLSLevel): + + """An MLS level used in contexts.""" + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + try: + othercats = set(other.categories()) + except AttributeError: + return str(self) == str(other) + else: + selfcats = set(self.categories()) + return self.sensitivity == other.sensitivity and selfcats == othercats + + def __ge__(self, other): + """Dom operator.""" + selfcats = set(self.categories()) + othercats = set(other.categories()) + return self.sensitivity >= other.sensitivity and selfcats >= othercats + + def __gt__(self, other): + selfcats = set(self.categories()) + othercats = set(other.categories()) + return ((self.sensitivity > other.sensitivity and selfcats >= othercats) or + (self.sensitivity >= other.sensitivity and selfcats > othercats)) + + def __le__(self, other): + """Domby operator.""" + selfcats = set(self.categories()) + othercats = set(other.categories()) + return self.sensitivity <= other.sensitivity and selfcats <= othercats + + def __lt__(self, other): + selfcats = set(self.categories()) + othercats = set(other.categories()) + return ((self.sensitivity < other.sensitivity and selfcats <= othercats) or + (self.sensitivity <= other.sensitivity and selfcats < othercats)) + + def __xor__(self, other): + """Incomp operator.""" + return not (self >= other or self <= other) + + @property + def sensitivity(self): + """The sensitivity of the level.""" + return sensitivity_factory(self.policy, self.qpol_symbol.sens_name(self.policy)) + + def statement(self): + raise exception.NoStatement + + +class Range(symbol.PolicySymbol): + + """An MLS range""" + + def __str__(self): + high = self.high + low = self.low + if high == low: + return str(low) + + return "{0} - {1}".format(low, high) + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + try: + return self.low == other.low and self.high == other.high + except AttributeError: + # remove all spaces in the string representations + # to handle cases where the other object does not + # have spaces around the '-' + other_str = str(other).replace(" ", "") + self_str = str(self).replace(" ", "") + return self_str == other_str + + def __contains__(self, other): + return self.low <= other <= self.high + + @property + def high(self): + """The high end/clearance level of this range.""" + return level_factory(self.policy, self.qpol_symbol.high_level(self.policy)) + + @property + def low(self): + """The low end/current level of this range.""" + return level_factory(self.policy, self.qpol_symbol.low_level(self.policy)) + + def statement(self): + raise exception.NoStatement diff --git a/lib/python2.7/site-packages/setools/policyrep/mlsrule.py b/lib/python2.7/site-packages/setools/policyrep/mlsrule.py new file mode 100644 index 0000000..5c91c59 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/mlsrule.py @@ -0,0 +1,62 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import rule +from . import typeattr +from . import mls + + +def mls_rule_factory(policy, symbol): + """Factory function for creating MLS rule objects.""" + if not isinstance(symbol, qpol.qpol_range_trans_t): + raise TypeError("MLS rules cannot be looked-up.") + + return MLSRule(policy, symbol) + + +def validate_ruletype(types): + """Validate MLS rule types.""" + for t in types: + if t not in ["range_transition"]: + raise exception.InvalidMLSRuleType("{0} is not a valid MLS rule type.".format(t)) + + +class MLSRule(rule.PolicyRule): + + """An MLS rule.""" + + def __str__(self): + # TODO: If we ever get more MLS rules, fix this format. + return "range_transition {0.source} {0.target}:{0.tclass} {0.default};".format(self) + + @property + def source(self): + """The rule's source type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.source_type(self.policy)) + + @property + def target(self): + """The rule's target type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.target_type(self.policy)) + + @property + def default(self): + """The rule's default range.""" + return mls.range_factory(self.policy, self.qpol_symbol.range(self.policy)) diff --git a/lib/python2.7/site-packages/setools/policyrep/netcontext.py b/lib/python2.7/site-packages/setools/policyrep/netcontext.py new file mode 100644 index 0000000..5aeed5c --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/netcontext.py @@ -0,0 +1,167 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import socket +from collections import namedtuple + +from . import qpol +from . import symbol +from . import context + +port_range = namedtuple("port_range", ["low", "high"]) + + +def netifcon_factory(policy, name): + """Factory function for creating netifcon objects.""" + + if not isinstance(name, qpol.qpol_netifcon_t): + raise NotImplementedError + + return Netifcon(policy, name) + + +def nodecon_factory(policy, name): + """Factory function for creating nodecon objects.""" + + if not isinstance(name, qpol.qpol_nodecon_t): + raise NotImplementedError + + return Nodecon(policy, name) + + +def portcon_factory(policy, name): + """Factory function for creating portcon objects.""" + + if not isinstance(name, qpol.qpol_portcon_t): + raise NotImplementedError + + return Portcon(policy, name) + + +class NetContext(symbol.PolicySymbol): + + """Base class for in-policy network labeling rules.""" + + def __str__(self): + raise NotImplementedError + + @property + def context(self): + """The context for this statement.""" + return context.context_factory(self.policy, self.qpol_symbol.context(self.policy)) + + def statement(self): + return str(self) + + +class Netifcon(NetContext): + + """A netifcon statement.""" + + def __str__(self): + return "netifcon {0.netif} {0.context} {0.packet}".format(self) + + @property + def netif(self): + """The network interface name.""" + return self.qpol_symbol.name(self.policy) + + @property + def context(self): + """The context for the interface.""" + return context.context_factory(self.policy, self.qpol_symbol.if_con(self.policy)) + + @property + def packet(self): + """The context for the packets.""" + return context.context_factory(self.policy, self.qpol_symbol.msg_con(self.policy)) + + +class Nodecon(NetContext): + + """A nodecon statement.""" + + def __str__(self): + return "nodecon {0.address} {0.netmask} {0.context}".format(self) + + def __eq__(self, other): + # Libqpol allocates new C objects in the + # nodecons iterator, so pointer comparison + # in the PolicySymbol object doesn't work. + try: + return (self.address == other.address and + self.netmask == other.netmask and + self.context == other.context) + except AttributeError: + return (str(self) == str(other)) + + @property + def ip_version(self): + """ + The IP version for the nodecon (socket.AF_INET or + socket.AF_INET6). + """ + return self.qpol_symbol.protocol(self.policy) + + @property + def address(self): + """The network address for the nodecon.""" + return self.qpol_symbol.addr(self.policy) + + @property + def netmask(self): + """The network mask for the nodecon.""" + return self.qpol_symbol.mask(self.policy) + + +class Portcon(NetContext): + + """A portcon statement.""" + + _proto_to_text = {socket.IPPROTO_TCP: 'tcp', + socket.IPPROTO_UDP: 'udp'} + + def __str__(self): + low, high = self.ports + proto = self._proto_to_text[self.protocol] + + if low == high: + return "portcon {0} {1} {2}".format(proto, low, self.context) + else: + return "portcon {0} {1}-{2} {3}".format(proto, low, high, self.context) + + @property + def protocol(self): + """ + The protocol number for the portcon (socket.IPPROTO_TCP + or socket.IPPROTO_UDP). + """ + return self.qpol_symbol.protocol(self.policy) + + @property + def ports(self): + """ + The port range for this portcon. + + Return: Tuple(low, high) + low The low port of the range. + high The high port of the range. + """ + low = self.qpol_symbol.low_port(self.policy) + high = self.qpol_symbol.high_port(self.policy) + return port_range(low, high) diff --git a/lib/python2.7/site-packages/setools/policyrep/objclass.py b/lib/python2.7/site-packages/setools/policyrep/objclass.py new file mode 100644 index 0000000..bf9a553 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/objclass.py @@ -0,0 +1,110 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import symbol +from . import qpol + + +def common_factory(policy, name): + """Factory function for creating common permission set objects.""" + + if isinstance(name, Common): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_common_t): + return Common(policy, name) + + try: + return Common(policy, qpol.qpol_common_t(policy, str(name))) + except ValueError: + raise exception.InvalidCommon("{0} is not a valid common".format(name)) + + +def class_factory(policy, name): + """Factory function for creating object class objects.""" + + if isinstance(name, ObjClass): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_class_t): + return ObjClass(policy, name) + + try: + return ObjClass(policy, qpol.qpol_class_t(policy, str(name))) + except ValueError: + raise exception.InvalidClass("{0} is not a valid object class".format(name)) + + +class Common(symbol.PolicySymbol): + + """A common permission set.""" + + def __contains__(self, other): + return other in self.perms + + @property + def perms(self): + """The list of the common's permissions.""" + return set(self.qpol_symbol.perm_iter(self.policy)) + + def statement(self): + return "common {0}\n{{\n\t{1}\n}}".format(self, '\n\t'.join(self.perms)) + + +class ObjClass(Common): + + """An object class.""" + + def __contains__(self, other): + try: + if other in self.common.perms: + return True + except exception.NoCommon: + pass + + return other in self.perms + + @property + def common(self): + """ + The common that the object class inherits. + + Exceptions: + NoCommon The object class does not inherit a common. + """ + + try: + return common_factory(self.policy, self.qpol_symbol.common(self.policy)) + except ValueError: + raise exception.NoCommon("{0} does not inherit a common.".format(self)) + + def statement(self): + stmt = "class {0}\n".format(self) + + try: + stmt += "inherits {0}\n".format(self.common) + except exception.NoCommon: + pass + + # a class that inherits may not have additional permissions + perms = self.perms + if len(perms) > 0: + stmt += "{{\n\t{0}\n}}".format('\n\t'.join(perms)) + + return stmt diff --git a/lib/python2.7/site-packages/setools/policyrep/polcap.py b/lib/python2.7/site-packages/setools/policyrep/polcap.py new file mode 100644 index 0000000..8ab164d --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/polcap.py @@ -0,0 +1,40 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import qpol +from . import symbol + + +def polcap_factory(policy, name): + """Factory function for creating policy capability objects.""" + + if isinstance(name, PolicyCapability): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_polcap_t): + return PolicyCapability(policy, name) + else: + raise TypeError("Policy capabilities cannot be looked up.") + + +class PolicyCapability(symbol.PolicySymbol): + + """A policy capability.""" + + def statement(self): + return "policycap {0};".format(self) diff --git a/lib/python2.7/site-packages/setools/policyrep/qpol.py b/lib/python2.7/site-packages/setools/policyrep/qpol.py new file mode 100644 index 0000000..97e602b --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/qpol.py @@ -0,0 +1,1114 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 2.0.11 +# +# Do not make changes to this file unless you know what you are doing--modify +# the SWIG interface file instead. + + + + + +from sys import version_info +if version_info >= (2,6,0): + def swig_import_helper(): + from os.path import dirname + import imp + fp = None + try: + fp, pathname, description = imp.find_module('_qpol', [dirname(__file__)]) + except ImportError: + import _qpol + return _qpol + if fp is not None: + try: + _mod = imp.load_module('_qpol', fp, pathname, description) + finally: + fp.close() + return _mod + _qpol = swig_import_helper() + del swig_import_helper +else: + import _qpol +del version_info +try: + _swig_property = property +except NameError: + pass # Python < 2.2 doesn't have 'property'. +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'SwigPyObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError(name) + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +try: + _object = object + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 + + + +def to_str(*args): + return _qpol.to_str(*args) +to_str = _qpol.to_str +import logging +from functools import wraps + +def QpolGenerator(cast): + """ + A decorator which converts qpol iterators into Python generators. + + Qpol iterators use void* to be generic about their contents. + The purpose of the _from_void functions below is to wrap + the pointer casting, hence the "cast" variable name here. + + Decorator parameter: + cast A wrapper function which casts the qpol iterator return pointer + to the proper C data type pointer. The Python function + reference to the C Python extension is used, for example: + + @QpolGenerator(_qpol.qpol_type_from_void) + """ + + def decorate(func): + @wraps(func) + def wrapper(*args, **kwargs): + qpol_iter = func(*args) + while not qpol_iter.isend(): + yield cast(qpol_iter.item()) + qpol_iter.next_() + + return wrapper + return decorate + +def qpol_logger(level, msg): + """Log qpol messages via Python logging.""" + logging.getLogger("libqpol").debug(msg) + +def qpol_policy_factory(path): + """Factory function for qpol policy objects.""" + # The main purpose here is to hook in the + # above logger callback. + return qpol_policy_t(path, 0, qpol_logger) + +QPOL_POLICY_OPTION_NO_NEVERALLOWS = _qpol.QPOL_POLICY_OPTION_NO_NEVERALLOWS +QPOL_POLICY_OPTION_NO_RULES = _qpol.QPOL_POLICY_OPTION_NO_RULES +QPOL_POLICY_OPTION_MATCH_SYSTEM = _qpol.QPOL_POLICY_OPTION_MATCH_SYSTEM +QPOL_POLICY_MAX_VERSION = _qpol.QPOL_POLICY_MAX_VERSION +QPOL_POLICY_MIN_VERSION = _qpol.QPOL_POLICY_MIN_VERSION +class qpol_policy_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_policy_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_policy_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_policy_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_policy_t + __del__ = lambda self : None; + def version(self): return _qpol.qpol_policy_t_version(self) + def handle_unknown(self): return _qpol.qpol_policy_t_handle_unknown(self) + def capability(self, *args): return _qpol.qpol_policy_t_capability(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def type_iter(self): return _qpol.qpol_policy_t_type_iter(self) + def type_count(self): return _qpol.qpol_policy_t_type_count(self) + @QpolGenerator(_qpol.qpol_role_from_void) + def role_iter(self): return _qpol.qpol_policy_t_role_iter(self) + def role_count(self): return _qpol.qpol_policy_t_role_count(self) + @QpolGenerator(_qpol.qpol_level_from_void) + def level_iter(self): return _qpol.qpol_policy_t_level_iter(self) + def level_count(self): return _qpol.qpol_policy_t_level_count(self) + @QpolGenerator(_qpol.qpol_cat_from_void) + def cat_iter(self): return _qpol.qpol_policy_t_cat_iter(self) + def cat_count(self): return _qpol.qpol_policy_t_cat_count(self) + @QpolGenerator(_qpol.qpol_user_from_void) + def user_iter(self): return _qpol.qpol_policy_t_user_iter(self) + def user_count(self): return _qpol.qpol_policy_t_user_count(self) + @QpolGenerator(_qpol.qpol_bool_from_void) + def bool_iter(self): return _qpol.qpol_policy_t_bool_iter(self) + def bool_count(self): return _qpol.qpol_policy_t_bool_count(self) + @QpolGenerator(_qpol.qpol_class_from_void) + def class_iter(self, perm=None): return _qpol.qpol_policy_t_class_iter(self, perm) + def class_count(self): return _qpol.qpol_policy_t_class_count(self) + @QpolGenerator(_qpol.qpol_common_from_void) + def common_iter(self, perm=None): return _qpol.qpol_policy_t_common_iter(self, perm) + def common_count(self): return _qpol.qpol_policy_t_common_count(self) + @QpolGenerator(_qpol.qpol_fs_use_from_void) + def fs_use_iter(self): return _qpol.qpol_policy_t_fs_use_iter(self) + def fs_use_count(self): return _qpol.qpol_policy_t_fs_use_count(self) + @QpolGenerator(_qpol.qpol_genfscon_from_void) + def genfscon_iter(self): return _qpol.qpol_policy_t_genfscon_iter(self) + def genfscon_count(self): return _qpol.qpol_policy_t_genfscon_count(self) + @QpolGenerator(_qpol.qpol_isid_from_void) + def isid_iter(self): return _qpol.qpol_policy_t_isid_iter(self) + def isid_count(self): return _qpol.qpol_policy_t_isid_count(self) + @QpolGenerator(_qpol.qpol_netifcon_from_void) + def netifcon_iter(self): return _qpol.qpol_policy_t_netifcon_iter(self) + def netifcon_count(self): return _qpol.qpol_policy_t_netifcon_count(self) + @QpolGenerator(_qpol.qpol_nodecon_from_void) + def nodecon_iter(self): return _qpol.qpol_policy_t_nodecon_iter(self) + def nodecon_count(self): return _qpol.qpol_policy_t_nodecon_count(self) + @QpolGenerator(_qpol.qpol_portcon_from_void) + def portcon_iter(self): return _qpol.qpol_policy_t_portcon_iter(self) + def portcon_count(self): return _qpol.qpol_policy_t_portcon_count(self) + @QpolGenerator(_qpol.qpol_constraint_from_void) + def constraint_iter(self): return _qpol.qpol_policy_t_constraint_iter(self) + def constraint_count(self): return _qpol.qpol_policy_t_constraint_count(self) + @QpolGenerator(_qpol.qpol_validatetrans_from_void) + def validatetrans_iter(self): return _qpol.qpol_policy_t_validatetrans_iter(self) + def validatetrans_count(self): return _qpol.qpol_policy_t_validatetrans_count(self) + @QpolGenerator(_qpol.qpol_role_allow_from_void) + def role_allow_iter(self): return _qpol.qpol_policy_t_role_allow_iter(self) + def role_allow_count(self): return _qpol.qpol_policy_t_role_allow_count(self) + @QpolGenerator(_qpol.qpol_role_trans_from_void) + def role_trans_iter(self): return _qpol.qpol_policy_t_role_trans_iter(self) + def role_trans_count(self): return _qpol.qpol_policy_t_role_trans_count(self) + @QpolGenerator(_qpol.qpol_range_trans_from_void) + def range_trans_iter(self): return _qpol.qpol_policy_t_range_trans_iter(self) + def range_trans_count(self): return _qpol.qpol_policy_t_range_trans_count(self) + @QpolGenerator(_qpol.qpol_avrule_from_void) + def avrule_iter(self): return _qpol.qpol_policy_t_avrule_iter(self) + def avrule_allow_count(self): return _qpol.qpol_policy_t_avrule_allow_count(self) + def avrule_auditallow_count(self): return _qpol.qpol_policy_t_avrule_auditallow_count(self) + def avrule_neverallow_count(self): return _qpol.qpol_policy_t_avrule_neverallow_count(self) + def avrule_dontaudit_count(self): return _qpol.qpol_policy_t_avrule_dontaudit_count(self) + @QpolGenerator(_qpol.qpol_terule_from_void) + def terule_iter(self): return _qpol.qpol_policy_t_terule_iter(self) + def terule_trans_count(self): return _qpol.qpol_policy_t_terule_trans_count(self) + def terule_change_count(self): return _qpol.qpol_policy_t_terule_change_count(self) + def terule_member_count(self): return _qpol.qpol_policy_t_terule_member_count(self) + def cond_iter(self): return _qpol.qpol_policy_t_cond_iter(self) + def cond_count(self): return _qpol.qpol_policy_t_cond_count(self) + @QpolGenerator(_qpol.qpol_filename_trans_from_void) + def filename_trans_iter(self): return _qpol.qpol_policy_t_filename_trans_iter(self) + def filename_trans_count(self): return _qpol.qpol_policy_t_filename_trans_count(self) + @QpolGenerator(_qpol.qpol_type_from_void) + def permissive_iter(self): return _qpol.qpol_policy_t_permissive_iter(self) + def permissive_count(self): return _qpol.qpol_policy_t_permissive_count(self) + def typebounds_iter(self): return _qpol.qpol_policy_t_typebounds_iter(self) + def typebounds_count(self): return _qpol.qpol_policy_t_typebounds_count(self) + @QpolGenerator(_qpol.qpol_polcap_from_void) + def polcap_iter(self): return _qpol.qpol_policy_t_polcap_iter(self) + def polcap_count(self): return _qpol.qpol_policy_t_polcap_count(self) + @QpolGenerator(_qpol.qpol_default_object_from_void) + def default_iter(self): return _qpol.qpol_policy_t_default_iter(self) +qpol_policy_t_swigregister = _qpol.qpol_policy_t_swigregister +qpol_policy_t_swigregister(qpol_policy_t) + +QPOL_CAP_ATTRIB_NAMES = _qpol.QPOL_CAP_ATTRIB_NAMES +QPOL_CAP_SYN_RULES = _qpol.QPOL_CAP_SYN_RULES +QPOL_CAP_LINE_NUMBERS = _qpol.QPOL_CAP_LINE_NUMBERS +QPOL_CAP_CONDITIONALS = _qpol.QPOL_CAP_CONDITIONALS +QPOL_CAP_MLS = _qpol.QPOL_CAP_MLS +QPOL_CAP_MODULES = _qpol.QPOL_CAP_MODULES +QPOL_CAP_RULES_LOADED = _qpol.QPOL_CAP_RULES_LOADED +QPOL_CAP_SOURCE = _qpol.QPOL_CAP_SOURCE +QPOL_CAP_NEVERALLOW = _qpol.QPOL_CAP_NEVERALLOW +QPOL_CAP_POLCAPS = _qpol.QPOL_CAP_POLCAPS +QPOL_CAP_BOUNDS = _qpol.QPOL_CAP_BOUNDS +QPOL_CAP_DEFAULT_OBJECTS = _qpol.QPOL_CAP_DEFAULT_OBJECTS +QPOL_CAP_DEFAULT_TYPE = _qpol.QPOL_CAP_DEFAULT_TYPE +QPOL_CAP_PERMISSIVE = _qpol.QPOL_CAP_PERMISSIVE +QPOL_CAP_FILENAME_TRANS = _qpol.QPOL_CAP_FILENAME_TRANS +QPOL_CAP_ROLETRANS = _qpol.QPOL_CAP_ROLETRANS +class qpol_iterator_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_iterator_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_iterator_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_iterator_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_iterator_t + __del__ = lambda self : None; + def item(self): return _qpol.qpol_iterator_t_item(self) + def next_(self): return _qpol.qpol_iterator_t_next_(self) + def isend(self): return _qpol.qpol_iterator_t_isend(self) + def size(self): return _qpol.qpol_iterator_t_size(self) +qpol_iterator_t_swigregister = _qpol.qpol_iterator_t_swigregister +qpol_iterator_t_swigregister(qpol_iterator_t) + +class qpol_type_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_type_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_type_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_type_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_type_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_type_t_name(self, *args) + def value(self, *args): return _qpol.qpol_type_t_value(self, *args) + def isalias(self, *args): return _qpol.qpol_type_t_isalias(self, *args) + def isattr(self, *args): return _qpol.qpol_type_t_isattr(self, *args) + def ispermissive(self, *args): return _qpol.qpol_type_t_ispermissive(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def type_iter(self, *args): return _qpol.qpol_type_t_type_iter(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def attr_iter(self, *args): return _qpol.qpol_type_t_attr_iter(self, *args) + @QpolGenerator(_qpol.to_str) + def alias_iter(self, *args): return _qpol.qpol_type_t_alias_iter(self, *args) +qpol_type_t_swigregister = _qpol.qpol_type_t_swigregister +qpol_type_t_swigregister(qpol_type_t) + + +def qpol_type_from_void(*args): + return _qpol.qpol_type_from_void(*args) +qpol_type_from_void = _qpol.qpol_type_from_void +class qpol_role_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_role_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_role_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_role_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_role_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_role_t_value(self, *args) + def name(self, *args): return _qpol.qpol_role_t_name(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def type_iter(self, *args): return _qpol.qpol_role_t_type_iter(self, *args) + def dominate_iter(self, *args): return _qpol.qpol_role_t_dominate_iter(self, *args) +qpol_role_t_swigregister = _qpol.qpol_role_t_swigregister +qpol_role_t_swigregister(qpol_role_t) + + +def qpol_role_from_void(*args): + return _qpol.qpol_role_from_void(*args) +qpol_role_from_void = _qpol.qpol_role_from_void +class qpol_level_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_level_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_level_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_level_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_level_t + __del__ = lambda self : None; + def isalias(self, *args): return _qpol.qpol_level_t_isalias(self, *args) + def value(self, *args): return _qpol.qpol_level_t_value(self, *args) + def name(self, *args): return _qpol.qpol_level_t_name(self, *args) + @QpolGenerator(_qpol.qpol_cat_from_void) + def cat_iter(self, *args): return _qpol.qpol_level_t_cat_iter(self, *args) + @QpolGenerator(_qpol.to_str) + def alias_iter(self, *args): return _qpol.qpol_level_t_alias_iter(self, *args) +qpol_level_t_swigregister = _qpol.qpol_level_t_swigregister +qpol_level_t_swigregister(qpol_level_t) + + +def qpol_level_from_void(*args): + return _qpol.qpol_level_from_void(*args) +qpol_level_from_void = _qpol.qpol_level_from_void +class qpol_cat_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_cat_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_cat_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_cat_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_cat_t + __del__ = lambda self : None; + def isalias(self, *args): return _qpol.qpol_cat_t_isalias(self, *args) + def value(self, *args): return _qpol.qpol_cat_t_value(self, *args) + def name(self, *args): return _qpol.qpol_cat_t_name(self, *args) + @QpolGenerator(_qpol.to_str) + def alias_iter(self, *args): return _qpol.qpol_cat_t_alias_iter(self, *args) +qpol_cat_t_swigregister = _qpol.qpol_cat_t_swigregister +qpol_cat_t_swigregister(qpol_cat_t) + + +def qpol_cat_from_void(*args): + return _qpol.qpol_cat_from_void(*args) +qpol_cat_from_void = _qpol.qpol_cat_from_void +class qpol_mls_range_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_mls_range_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_mls_range_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_mls_range_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_mls_range_t + __del__ = lambda self : None; + def high_level(self, *args): return _qpol.qpol_mls_range_t_high_level(self, *args) + def low_level(self, *args): return _qpol.qpol_mls_range_t_low_level(self, *args) +qpol_mls_range_t_swigregister = _qpol.qpol_mls_range_t_swigregister +qpol_mls_range_t_swigregister(qpol_mls_range_t) + + +def qpol_mls_range_from_void(*args): + return _qpol.qpol_mls_range_from_void(*args) +qpol_mls_range_from_void = _qpol.qpol_mls_range_from_void +class qpol_semantic_level_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_semantic_level_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_semantic_level_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_semantic_level_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_semantic_level_t + __del__ = lambda self : None; + def add_cats(self, *args): return _qpol.qpol_semantic_level_t_add_cats(self, *args) +qpol_semantic_level_t_swigregister = _qpol.qpol_semantic_level_t_swigregister +qpol_semantic_level_t_swigregister(qpol_semantic_level_t) + +class qpol_mls_level_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_mls_level_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_mls_level_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_mls_level_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_mls_level_t + __del__ = lambda self : None; + def sens_name(self, *args): return _qpol.qpol_mls_level_t_sens_name(self, *args) + @QpolGenerator(_qpol.qpol_cat_from_void) + def cat_iter(self, *args): return _qpol.qpol_mls_level_t_cat_iter(self, *args) +qpol_mls_level_t_swigregister = _qpol.qpol_mls_level_t_swigregister +qpol_mls_level_t_swigregister(qpol_mls_level_t) + + +def qpol_mls_level_from_void(*args): + return _qpol.qpol_mls_level_from_void(*args) +qpol_mls_level_from_void = _qpol.qpol_mls_level_from_void +class qpol_user_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_user_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_user_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_user_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_user_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_user_t_value(self, *args) + @QpolGenerator(_qpol.qpol_role_from_void) + def role_iter(self, *args): return _qpol.qpol_user_t_role_iter(self, *args) + def range(self, *args): return _qpol.qpol_user_t_range(self, *args) + def name(self, *args): return _qpol.qpol_user_t_name(self, *args) + def dfltlevel(self, *args): return _qpol.qpol_user_t_dfltlevel(self, *args) +qpol_user_t_swigregister = _qpol.qpol_user_t_swigregister +qpol_user_t_swigregister(qpol_user_t) + + +def qpol_user_from_void(*args): + return _qpol.qpol_user_from_void(*args) +qpol_user_from_void = _qpol.qpol_user_from_void +class qpol_bool_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_bool_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_bool_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_bool_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_bool_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_bool_t_value(self, *args) + def state(self, *args): return _qpol.qpol_bool_t_state(self, *args) + def name(self, *args): return _qpol.qpol_bool_t_name(self, *args) +qpol_bool_t_swigregister = _qpol.qpol_bool_t_swigregister +qpol_bool_t_swigregister(qpol_bool_t) + + +def qpol_bool_from_void(*args): + return _qpol.qpol_bool_from_void(*args) +qpol_bool_from_void = _qpol.qpol_bool_from_void +class qpol_context_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_context_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_context_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_context_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_context_t + __del__ = lambda self : None; + def user(self, *args): return _qpol.qpol_context_t_user(self, *args) + def role(self, *args): return _qpol.qpol_context_t_role(self, *args) + def type_(self, *args): return _qpol.qpol_context_t_type_(self, *args) + def range(self, *args): return _qpol.qpol_context_t_range(self, *args) +qpol_context_t_swigregister = _qpol.qpol_context_t_swigregister +qpol_context_t_swigregister(qpol_context_t) + + +def qpol_context_from_void(*args): + return _qpol.qpol_context_from_void(*args) +qpol_context_from_void = _qpol.qpol_context_from_void +class qpol_class_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_class_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_class_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_class_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_class_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_class_t_value(self, *args) + def common(self, *args): return _qpol.qpol_class_t_common(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_class_t_perm_iter(self, *args) + @QpolGenerator(_qpol.qpol_constraint_from_void) + def constraint_iter(self, *args): return _qpol.qpol_class_t_constraint_iter(self, *args) + @QpolGenerator(_qpol.qpol_validatetrans_from_void) + def validatetrans_iter(self, *args): return _qpol.qpol_class_t_validatetrans_iter(self, *args) + def name(self, *args): return _qpol.qpol_class_t_name(self, *args) +qpol_class_t_swigregister = _qpol.qpol_class_t_swigregister +qpol_class_t_swigregister(qpol_class_t) + + +def qpol_class_from_void(*args): + return _qpol.qpol_class_from_void(*args) +qpol_class_from_void = _qpol.qpol_class_from_void +class qpol_common_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_common_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_common_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_common_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_common_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_common_t_value(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_common_t_perm_iter(self, *args) + def name(self, *args): return _qpol.qpol_common_t_name(self, *args) +qpol_common_t_swigregister = _qpol.qpol_common_t_swigregister +qpol_common_t_swigregister(qpol_common_t) + + +def qpol_common_from_void(*args): + return _qpol.qpol_common_from_void(*args) +qpol_common_from_void = _qpol.qpol_common_from_void +QPOL_FS_USE_XATTR = _qpol.QPOL_FS_USE_XATTR +QPOL_FS_USE_TRANS = _qpol.QPOL_FS_USE_TRANS +QPOL_FS_USE_TASK = _qpol.QPOL_FS_USE_TASK +QPOL_FS_USE_GENFS = _qpol.QPOL_FS_USE_GENFS +QPOL_FS_USE_NONE = _qpol.QPOL_FS_USE_NONE +QPOL_FS_USE_PSID = _qpol.QPOL_FS_USE_PSID +class qpol_fs_use_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_fs_use_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_fs_use_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_fs_use_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_fs_use_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_fs_use_t_name(self, *args) + def behavior(self, *args): return _qpol.qpol_fs_use_t_behavior(self, *args) + def context(self, *args): return _qpol.qpol_fs_use_t_context(self, *args) +qpol_fs_use_t_swigregister = _qpol.qpol_fs_use_t_swigregister +qpol_fs_use_t_swigregister(qpol_fs_use_t) + + +def qpol_fs_use_from_void(*args): + return _qpol.qpol_fs_use_from_void(*args) +qpol_fs_use_from_void = _qpol.qpol_fs_use_from_void +QPOL_CLASS_ALL = _qpol.QPOL_CLASS_ALL +QPOL_CLASS_BLK_FILE = _qpol.QPOL_CLASS_BLK_FILE +QPOL_CLASS_CHR_FILE = _qpol.QPOL_CLASS_CHR_FILE +QPOL_CLASS_DIR = _qpol.QPOL_CLASS_DIR +QPOL_CLASS_FIFO_FILE = _qpol.QPOL_CLASS_FIFO_FILE +QPOL_CLASS_FILE = _qpol.QPOL_CLASS_FILE +QPOL_CLASS_LNK_FILE = _qpol.QPOL_CLASS_LNK_FILE +QPOL_CLASS_SOCK_FILE = _qpol.QPOL_CLASS_SOCK_FILE +class qpol_genfscon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_genfscon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_genfscon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_genfscon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_genfscon_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_genfscon_t_name(self, *args) + def path(self, *args): return _qpol.qpol_genfscon_t_path(self, *args) + def object_class(self, *args): return _qpol.qpol_genfscon_t_object_class(self, *args) + def context(self, *args): return _qpol.qpol_genfscon_t_context(self, *args) +qpol_genfscon_t_swigregister = _qpol.qpol_genfscon_t_swigregister +qpol_genfscon_t_swigregister(qpol_genfscon_t) + + +def qpol_genfscon_from_void(*args): + return _qpol.qpol_genfscon_from_void(*args) +qpol_genfscon_from_void = _qpol.qpol_genfscon_from_void +class qpol_isid_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_isid_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_isid_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_isid_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_isid_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_isid_t_name(self, *args) + def context(self, *args): return _qpol.qpol_isid_t_context(self, *args) +qpol_isid_t_swigregister = _qpol.qpol_isid_t_swigregister +qpol_isid_t_swigregister(qpol_isid_t) + + +def qpol_isid_from_void(*args): + return _qpol.qpol_isid_from_void(*args) +qpol_isid_from_void = _qpol.qpol_isid_from_void +class qpol_netifcon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_netifcon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_netifcon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_netifcon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_netifcon_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_netifcon_t_name(self, *args) + def msg_con(self, *args): return _qpol.qpol_netifcon_t_msg_con(self, *args) + def if_con(self, *args): return _qpol.qpol_netifcon_t_if_con(self, *args) +qpol_netifcon_t_swigregister = _qpol.qpol_netifcon_t_swigregister +qpol_netifcon_t_swigregister(qpol_netifcon_t) + + +def qpol_netifcon_from_void(*args): + return _qpol.qpol_netifcon_from_void(*args) +qpol_netifcon_from_void = _qpol.qpol_netifcon_from_void +QPOL_IPV4 = _qpol.QPOL_IPV4 +QPOL_IPV6 = _qpol.QPOL_IPV6 +class qpol_nodecon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_nodecon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_nodecon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_nodecon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_nodecon_t + __del__ = lambda self : None; + def addr(self, *args): return _qpol.qpol_nodecon_t_addr(self, *args) + def mask(self, *args): return _qpol.qpol_nodecon_t_mask(self, *args) + def protocol(self, *args): return _qpol.qpol_nodecon_t_protocol(self, *args) + def context(self, *args): return _qpol.qpol_nodecon_t_context(self, *args) +qpol_nodecon_t_swigregister = _qpol.qpol_nodecon_t_swigregister +qpol_nodecon_t_swigregister(qpol_nodecon_t) + + +def qpol_nodecon_from_void(*args): + return _qpol.qpol_nodecon_from_void(*args) +qpol_nodecon_from_void = _qpol.qpol_nodecon_from_void +IPPROTO_TCP = _qpol.IPPROTO_TCP +IPPROTO_UDP = _qpol.IPPROTO_UDP +class qpol_portcon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_portcon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_portcon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_portcon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_portcon_t + __del__ = lambda self : None; + def low_port(self, *args): return _qpol.qpol_portcon_t_low_port(self, *args) + def high_port(self, *args): return _qpol.qpol_portcon_t_high_port(self, *args) + def protocol(self, *args): return _qpol.qpol_portcon_t_protocol(self, *args) + def context(self, *args): return _qpol.qpol_portcon_t_context(self, *args) +qpol_portcon_t_swigregister = _qpol.qpol_portcon_t_swigregister +qpol_portcon_t_swigregister(qpol_portcon_t) + + +def qpol_portcon_from_void(*args): + return _qpol.qpol_portcon_from_void(*args) +qpol_portcon_from_void = _qpol.qpol_portcon_from_void +class qpol_constraint_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_constraint_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_constraint_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_constraint_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_constraint_t + __del__ = lambda self : None; + def object_class(self, *args): return _qpol.qpol_constraint_t_object_class(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_constraint_t_perm_iter(self, *args) + @QpolGenerator(_qpol.qpol_constraint_expr_node_from_void) + def expr_iter(self, *args): return _qpol.qpol_constraint_t_expr_iter(self, *args) +qpol_constraint_t_swigregister = _qpol.qpol_constraint_t_swigregister +qpol_constraint_t_swigregister(qpol_constraint_t) + + +def qpol_constraint_from_void(*args): + return _qpol.qpol_constraint_from_void(*args) +qpol_constraint_from_void = _qpol.qpol_constraint_from_void +class qpol_validatetrans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_validatetrans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_validatetrans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_validatetrans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_validatetrans_t + __del__ = lambda self : None; + def object_class(self, *args): return _qpol.qpol_validatetrans_t_object_class(self, *args) + @QpolGenerator(_qpol.qpol_constraint_expr_node_from_void) + def expr_iter(self, *args): return _qpol.qpol_validatetrans_t_expr_iter(self, *args) +qpol_validatetrans_t_swigregister = _qpol.qpol_validatetrans_t_swigregister +qpol_validatetrans_t_swigregister(qpol_validatetrans_t) + + +def qpol_validatetrans_from_void(*args): + return _qpol.qpol_validatetrans_from_void(*args) +qpol_validatetrans_from_void = _qpol.qpol_validatetrans_from_void +QPOL_CEXPR_TYPE_NOT = _qpol.QPOL_CEXPR_TYPE_NOT +QPOL_CEXPR_TYPE_AND = _qpol.QPOL_CEXPR_TYPE_AND +QPOL_CEXPR_TYPE_OR = _qpol.QPOL_CEXPR_TYPE_OR +QPOL_CEXPR_TYPE_ATTR = _qpol.QPOL_CEXPR_TYPE_ATTR +QPOL_CEXPR_TYPE_NAMES = _qpol.QPOL_CEXPR_TYPE_NAMES +QPOL_CEXPR_SYM_USER = _qpol.QPOL_CEXPR_SYM_USER +QPOL_CEXPR_SYM_ROLE = _qpol.QPOL_CEXPR_SYM_ROLE +QPOL_CEXPR_SYM_TYPE = _qpol.QPOL_CEXPR_SYM_TYPE +QPOL_CEXPR_SYM_TARGET = _qpol.QPOL_CEXPR_SYM_TARGET +QPOL_CEXPR_SYM_XTARGET = _qpol.QPOL_CEXPR_SYM_XTARGET +QPOL_CEXPR_SYM_L1L2 = _qpol.QPOL_CEXPR_SYM_L1L2 +QPOL_CEXPR_SYM_L1H2 = _qpol.QPOL_CEXPR_SYM_L1H2 +QPOL_CEXPR_SYM_H1L2 = _qpol.QPOL_CEXPR_SYM_H1L2 +QPOL_CEXPR_SYM_H1H2 = _qpol.QPOL_CEXPR_SYM_H1H2 +QPOL_CEXPR_SYM_L1H1 = _qpol.QPOL_CEXPR_SYM_L1H1 +QPOL_CEXPR_SYM_L2H2 = _qpol.QPOL_CEXPR_SYM_L2H2 +QPOL_CEXPR_OP_EQ = _qpol.QPOL_CEXPR_OP_EQ +QPOL_CEXPR_OP_NEQ = _qpol.QPOL_CEXPR_OP_NEQ +QPOL_CEXPR_OP_DOM = _qpol.QPOL_CEXPR_OP_DOM +QPOL_CEXPR_OP_DOMBY = _qpol.QPOL_CEXPR_OP_DOMBY +QPOL_CEXPR_OP_INCOMP = _qpol.QPOL_CEXPR_OP_INCOMP +class qpol_constraint_expr_node_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_constraint_expr_node_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_constraint_expr_node_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_constraint_expr_node_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_constraint_expr_node_t + __del__ = lambda self : None; + def expr_type(self, *args): return _qpol.qpol_constraint_expr_node_t_expr_type(self, *args) + def sym_type(self, *args): return _qpol.qpol_constraint_expr_node_t_sym_type(self, *args) + def op(self, *args): return _qpol.qpol_constraint_expr_node_t_op(self, *args) + @QpolGenerator(_qpol.to_str) + def names_iter(self, *args): return _qpol.qpol_constraint_expr_node_t_names_iter(self, *args) +qpol_constraint_expr_node_t_swigregister = _qpol.qpol_constraint_expr_node_t_swigregister +qpol_constraint_expr_node_t_swigregister(qpol_constraint_expr_node_t) + + +def qpol_constraint_expr_node_from_void(*args): + return _qpol.qpol_constraint_expr_node_from_void(*args) +qpol_constraint_expr_node_from_void = _qpol.qpol_constraint_expr_node_from_void +class qpol_role_allow_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_role_allow_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_role_allow_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_role_allow_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_role_allow_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "allow" + + def source_role(self, *args): return _qpol.qpol_role_allow_t_source_role(self, *args) + def target_role(self, *args): return _qpol.qpol_role_allow_t_target_role(self, *args) +qpol_role_allow_t_swigregister = _qpol.qpol_role_allow_t_swigregister +qpol_role_allow_t_swigregister(qpol_role_allow_t) + + +def qpol_role_allow_from_void(*args): + return _qpol.qpol_role_allow_from_void(*args) +qpol_role_allow_from_void = _qpol.qpol_role_allow_from_void +class qpol_role_trans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_role_trans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_role_trans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_role_trans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_role_trans_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "role_transition" + + def source_role(self, *args): return _qpol.qpol_role_trans_t_source_role(self, *args) + def target_type(self, *args): return _qpol.qpol_role_trans_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_role_trans_t_object_class(self, *args) + def default_role(self, *args): return _qpol.qpol_role_trans_t_default_role(self, *args) +qpol_role_trans_t_swigregister = _qpol.qpol_role_trans_t_swigregister +qpol_role_trans_t_swigregister(qpol_role_trans_t) + + +def qpol_role_trans_from_void(*args): + return _qpol.qpol_role_trans_from_void(*args) +qpol_role_trans_from_void = _qpol.qpol_role_trans_from_void +class qpol_range_trans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_range_trans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_range_trans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_range_trans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_range_trans_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "range_transition" + + def source_type(self, *args): return _qpol.qpol_range_trans_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_range_trans_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_range_trans_t_object_class(self, *args) + def range(self, *args): return _qpol.qpol_range_trans_t_range(self, *args) +qpol_range_trans_t_swigregister = _qpol.qpol_range_trans_t_swigregister +qpol_range_trans_t_swigregister(qpol_range_trans_t) + + +def qpol_range_trans_from_void(*args): + return _qpol.qpol_range_trans_from_void(*args) +qpol_range_trans_from_void = _qpol.qpol_range_trans_from_void +QPOL_RULE_ALLOW = _qpol.QPOL_RULE_ALLOW +QPOL_RULE_NEVERALLOW = _qpol.QPOL_RULE_NEVERALLOW +QPOL_RULE_AUDITALLOW = _qpol.QPOL_RULE_AUDITALLOW +QPOL_RULE_DONTAUDIT = _qpol.QPOL_RULE_DONTAUDIT +class qpol_avrule_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_avrule_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_avrule_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_avrule_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_avrule_t + __del__ = lambda self : None; + def rule_type(self, *args): return _qpol.qpol_avrule_t_rule_type(self, *args) + def source_type(self, *args): return _qpol.qpol_avrule_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_avrule_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_avrule_t_object_class(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_avrule_t_perm_iter(self, *args) + def cond(self, *args): return _qpol.qpol_avrule_t_cond(self, *args) + def is_enabled(self, *args): return _qpol.qpol_avrule_t_is_enabled(self, *args) + def which_list(self, *args): return _qpol.qpol_avrule_t_which_list(self, *args) + def syn_avrule_iter(self, *args): return _qpol.qpol_avrule_t_syn_avrule_iter(self, *args) +qpol_avrule_t_swigregister = _qpol.qpol_avrule_t_swigregister +qpol_avrule_t_swigregister(qpol_avrule_t) + + +def qpol_avrule_from_void(*args): + return _qpol.qpol_avrule_from_void(*args) +qpol_avrule_from_void = _qpol.qpol_avrule_from_void +QPOL_RULE_TYPE_TRANS = _qpol.QPOL_RULE_TYPE_TRANS +QPOL_RULE_TYPE_CHANGE = _qpol.QPOL_RULE_TYPE_CHANGE +QPOL_RULE_TYPE_MEMBER = _qpol.QPOL_RULE_TYPE_MEMBER +class qpol_terule_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_terule_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_terule_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_terule_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_terule_t + __del__ = lambda self : None; + def rule_type(self, *args): return _qpol.qpol_terule_t_rule_type(self, *args) + def source_type(self, *args): return _qpol.qpol_terule_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_terule_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_terule_t_object_class(self, *args) + def default_type(self, *args): return _qpol.qpol_terule_t_default_type(self, *args) + def cond(self, *args): return _qpol.qpol_terule_t_cond(self, *args) + def is_enabled(self, *args): return _qpol.qpol_terule_t_is_enabled(self, *args) + def which_list(self, *args): return _qpol.qpol_terule_t_which_list(self, *args) + def syn_terule_iter(self, *args): return _qpol.qpol_terule_t_syn_terule_iter(self, *args) +qpol_terule_t_swigregister = _qpol.qpol_terule_t_swigregister +qpol_terule_t_swigregister(qpol_terule_t) + + +def qpol_terule_from_void(*args): + return _qpol.qpol_terule_from_void(*args) +qpol_terule_from_void = _qpol.qpol_terule_from_void +class qpol_cond_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_cond_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_cond_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_cond_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_cond_t + __del__ = lambda self : None; + @QpolGenerator(_qpol.qpol_cond_expr_node_from_void) + def expr_node_iter(self, *args): return _qpol.qpol_cond_t_expr_node_iter(self, *args) + def av_true_iter(self, *args): return _qpol.qpol_cond_t_av_true_iter(self, *args) + def av_false_iter(self, *args): return _qpol.qpol_cond_t_av_false_iter(self, *args) + def te_true_iter(self, *args): return _qpol.qpol_cond_t_te_true_iter(self, *args) + def te_false_iter(self, *args): return _qpol.qpol_cond_t_te_false_iter(self, *args) + def evaluate(self, *args): return _qpol.qpol_cond_t_evaluate(self, *args) +qpol_cond_t_swigregister = _qpol.qpol_cond_t_swigregister +qpol_cond_t_swigregister(qpol_cond_t) + + +def qpol_cond_from_void(*args): + return _qpol.qpol_cond_from_void(*args) +qpol_cond_from_void = _qpol.qpol_cond_from_void +QPOL_COND_EXPR_BOOL = _qpol.QPOL_COND_EXPR_BOOL +QPOL_COND_EXPR_NOT = _qpol.QPOL_COND_EXPR_NOT +QPOL_COND_EXPR_OR = _qpol.QPOL_COND_EXPR_OR +QPOL_COND_EXPR_AND = _qpol.QPOL_COND_EXPR_AND +QPOL_COND_EXPR_XOR = _qpol.QPOL_COND_EXPR_XOR +QPOL_COND_EXPR_EQ = _qpol.QPOL_COND_EXPR_EQ +QPOL_COND_EXPR_NEQ = _qpol.QPOL_COND_EXPR_NEQ +class qpol_cond_expr_node_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_cond_expr_node_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_cond_expr_node_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_cond_expr_node_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_cond_expr_node_t + __del__ = lambda self : None; + def expr_type(self, *args): return _qpol.qpol_cond_expr_node_t_expr_type(self, *args) + def get_boolean(self, *args): return _qpol.qpol_cond_expr_node_t_get_boolean(self, *args) +qpol_cond_expr_node_t_swigregister = _qpol.qpol_cond_expr_node_t_swigregister +qpol_cond_expr_node_t_swigregister(qpol_cond_expr_node_t) + + +def qpol_cond_expr_node_from_void(*args): + return _qpol.qpol_cond_expr_node_from_void(*args) +qpol_cond_expr_node_from_void = _qpol.qpol_cond_expr_node_from_void +class qpol_filename_trans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_filename_trans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_filename_trans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_filename_trans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_filename_trans_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "type_transition" + + def source_type(self, *args): return _qpol.qpol_filename_trans_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_filename_trans_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_filename_trans_t_object_class(self, *args) + def default_type(self, *args): return _qpol.qpol_filename_trans_t_default_type(self, *args) + def filename(self, *args): return _qpol.qpol_filename_trans_t_filename(self, *args) +qpol_filename_trans_t_swigregister = _qpol.qpol_filename_trans_t_swigregister +qpol_filename_trans_t_swigregister(qpol_filename_trans_t) + + +def qpol_filename_trans_from_void(*args): + return _qpol.qpol_filename_trans_from_void(*args) +qpol_filename_trans_from_void = _qpol.qpol_filename_trans_from_void +class qpol_polcap_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_polcap_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_polcap_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_polcap_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_polcap_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_polcap_t_name(self, *args) +qpol_polcap_t_swigregister = _qpol.qpol_polcap_t_swigregister +qpol_polcap_t_swigregister(qpol_polcap_t) + + +def qpol_polcap_from_void(*args): + return _qpol.qpol_polcap_from_void(*args) +qpol_polcap_from_void = _qpol.qpol_polcap_from_void +class qpol_typebounds_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_typebounds_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_typebounds_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_typebounds_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_typebounds_t + __del__ = lambda self : None; + def parent_name(self, *args): return _qpol.qpol_typebounds_t_parent_name(self, *args) + def child_name(self, *args): return _qpol.qpol_typebounds_t_child_name(self, *args) +qpol_typebounds_t_swigregister = _qpol.qpol_typebounds_t_swigregister +qpol_typebounds_t_swigregister(qpol_typebounds_t) + + +def qpol_typebounds_from_void(*args): + return _qpol.qpol_typebounds_from_void(*args) +qpol_typebounds_from_void = _qpol.qpol_typebounds_from_void +class qpol_rolebounds_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_rolebounds_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_rolebounds_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_rolebounds_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_rolebounds_t + __del__ = lambda self : None; + def parent_name(self, *args): return _qpol.qpol_rolebounds_t_parent_name(self, *args) + def child_name(self, *args): return _qpol.qpol_rolebounds_t_child_name(self, *args) +qpol_rolebounds_t_swigregister = _qpol.qpol_rolebounds_t_swigregister +qpol_rolebounds_t_swigregister(qpol_rolebounds_t) + + +def qpol_rolebounds_from_void(*args): + return _qpol.qpol_rolebounds_from_void(*args) +qpol_rolebounds_from_void = _qpol.qpol_rolebounds_from_void +class qpol_userbounds_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_userbounds_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_userbounds_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_userbounds_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_userbounds_t + __del__ = lambda self : None; + def parent_name(self, *args): return _qpol.qpol_userbounds_t_parent_name(self, *args) + def child_name(self, *args): return _qpol.qpol_userbounds_t_child_name(self, *args) +qpol_userbounds_t_swigregister = _qpol.qpol_userbounds_t_swigregister +qpol_userbounds_t_swigregister(qpol_userbounds_t) + + +def qpol_userbounds_from_void(*args): + return _qpol.qpol_userbounds_from_void(*args) +qpol_userbounds_from_void = _qpol.qpol_userbounds_from_void +class qpol_default_object_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_default_object_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_default_object_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_default_object_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_default_object_t + __del__ = lambda self : None; + def object_class(self, *args): return _qpol.qpol_default_object_t_object_class(self, *args) + def user_default(self, *args): return _qpol.qpol_default_object_t_user_default(self, *args) + def role_default(self, *args): return _qpol.qpol_default_object_t_role_default(self, *args) + def type_default(self, *args): return _qpol.qpol_default_object_t_type_default(self, *args) + def range_default(self, *args): return _qpol.qpol_default_object_t_range_default(self, *args) +qpol_default_object_t_swigregister = _qpol.qpol_default_object_t_swigregister +qpol_default_object_t_swigregister(qpol_default_object_t) + + +def qpol_default_object_from_void(*args): + return _qpol.qpol_default_object_from_void(*args) +qpol_default_object_from_void = _qpol.qpol_default_object_from_void +# This file is compatible with both classic and new-style classes. + + diff --git a/lib/python2.7/site-packages/setools/policyrep/rbacrule.py b/lib/python2.7/site-packages/setools/policyrep/rbacrule.py new file mode 100644 index 0000000..aa6a0d0 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/rbacrule.py @@ -0,0 +1,92 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import rule +from . import role +from . import typeattr + + +def rbac_rule_factory(policy, name): + """Factory function for creating RBAC rule objects.""" + + if isinstance(name, qpol.qpol_role_allow_t): + return RoleAllow(policy, name) + elif isinstance(name, qpol.qpol_role_trans_t): + return RoleTransition(policy, name) + else: + raise TypeError("RBAC rules cannot be looked up.") + + +def validate_ruletype(types): + """Validate RBAC rule types.""" + for t in types: + if t not in ["allow", "role_transition"]: + raise exception.InvalidRBACRuleType("{0} is not a valid RBAC rule type.".format(t)) + + +class RoleAllow(rule.PolicyRule): + + """A role allow rule.""" + + def __str__(self): + return "allow {0.source} {0.target};".format(self) + + @property + def source(self): + """The rule's source role.""" + return role.role_factory(self.policy, self.qpol_symbol.source_role(self.policy)) + + @property + def target(self): + """The rule's target role.""" + return role.role_factory(self.policy, self.qpol_symbol.target_role(self.policy)) + + @property + def tclass(self): + """The rule's object class.""" + raise exception.RuleUseError("Role allow rules do not have an object class.") + + @property + def default(self): + """The rule's default role.""" + raise exception.RuleUseError("Role allow rules do not have a default role.") + + +class RoleTransition(rule.PolicyRule): + + """A role_transition rule.""" + + def __str__(self): + return "role_transition {0.source} {0.target}:{0.tclass} {0.default};".format(self) + + @property + def source(self): + """The rule's source role.""" + return role.role_factory(self.policy, self.qpol_symbol.source_role(self.policy)) + + @property + def target(self): + """The rule's target type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.target_type(self.policy)) + + @property + def default(self): + """The rule's default role.""" + return role.role_factory(self.policy, self.qpol_symbol.default_role(self.policy)) diff --git a/lib/python2.7/site-packages/setools/policyrep/role.py b/lib/python2.7/site-packages/setools/policyrep/role.py new file mode 100644 index 0000000..1d9fbe1 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/role.py @@ -0,0 +1,81 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol +from . import typeattr + + +def role_factory(qpol_policy, name): + """Factory function for creating Role objects.""" + + if isinstance(name, Role): + assert name.policy == qpol_policy + return name + elif isinstance(name, qpol.qpol_role_t): + return Role(qpol_policy, name) + + try: + return Role(qpol_policy, qpol.qpol_role_t(qpol_policy, str(name))) + except ValueError: + raise exception.InvalidRole("{0} is not a valid role".format(name)) + + +class BaseRole(symbol.PolicySymbol): + + """Role/role attribute base class.""" + + def expand(self): + raise NotImplementedError + + def types(self): + raise NotImplementedError + + +class Role(BaseRole): + + """A role.""" + + def expand(self): + """Generator that expands this into its member roles.""" + yield self + + def types(self): + """Generator which yields the role's set of types.""" + + for type_ in self.qpol_symbol.type_iter(self.policy): + yield typeattr.type_or_attr_factory(self.policy, type_) + + def statement(self): + types = list(str(t) for t in self.types()) + stmt = "role {0}".format(self) + if types: + if (len(types) > 1): + stmt += " types {{ {0} }}".format(' '.join(types)) + else: + stmt += " types {0}".format(types[0]) + stmt += ";" + return stmt + + +class RoleAttribute(BaseRole): + + """A role attribute.""" + + pass diff --git a/lib/python2.7/site-packages/setools/policyrep/rule.py b/lib/python2.7/site-packages/setools/policyrep/rule.py new file mode 100644 index 0000000..73fc812 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/rule.py @@ -0,0 +1,72 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import symbol +from . import objclass + + +class PolicyRule(symbol.PolicySymbol): + + """This is base class for policy rules.""" + + def __str__(self): + raise NotImplementedError + + @property + def ruletype(self): + """The rule type for the rule.""" + return self.qpol_symbol.rule_type(self.policy) + + @property + def source(self): + """ + The source for the rule. This should be overridden by + subclasses. + """ + raise NotImplementedError + + @property + def target(self): + """ + The target for the rule. This should be overridden by + subclasses. + """ + raise NotImplementedError + + @property + def tclass(self): + """The object class for the rule.""" + return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) + + @property + def default(self): + """ + The default for the rule. This should be overridden by + subclasses. + """ + raise NotImplementedError + + @property + def conditional(self): + """The conditional expression for this rule.""" + # Most rules cannot be conditional. + raise exception.RuleNotConditional + + def statement(self): + return str(self) diff --git a/lib/python2.7/site-packages/setools/policyrep/symbol.py b/lib/python2.7/site-packages/setools/policyrep/symbol.py new file mode 100644 index 0000000..4712d7f --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/symbol.py @@ -0,0 +1,74 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + + +class PolicySymbol(object): + + """This is a base class for all policy objects.""" + + def __init__(self, policy, qpol_symbol): + """ + Parameters: + policy The low-level policy object. + qpol_symbol The low-level policy symbol object. + """ + + assert qpol_symbol + + self.policy = policy + self.qpol_symbol = qpol_symbol + + def __str__(self): + return self.qpol_symbol.name(self.policy) + + def __hash__(self): + return hash(self.qpol_symbol.name(self.policy)) + + def __eq__(self, other): + try: + return self.qpol_symbol.this == other.qpol_symbol.this + except AttributeError: + return str(self) == str(other) + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + """Comparison used by Python sorting functions.""" + return str(self) < str(other) + + def __repr__(self): + return "<{0.__class__.__name__}(,\"{0}\")>".format( + self, id(self.policy)) + + def __deepcopy__(self, memo): + # shallow copy as all of the members are immutable + cls = self.__class__ + newobj = cls.__new__(cls) + newobj.policy = self.policy + newobj.qpol_symbol = self.qpol_symbol + memo[id(self)] = newobj + return newobj + + def statement(self): + """ + A rendering of the policy statement. This should be + overridden by subclasses. + """ + raise NotImplementedError diff --git a/lib/python2.7/site-packages/setools/policyrep/terule.py b/lib/python2.7/site-packages/setools/policyrep/terule.py new file mode 100644 index 0000000..d8a9e94 --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/terule.py @@ -0,0 +1,155 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import rule +from . import typeattr +from . import boolcond + + +def te_rule_factory(policy, symbol): + """Factory function for creating TE rule objects.""" + + if isinstance(symbol, qpol.qpol_avrule_t): + return AVRule(policy, symbol) + elif isinstance(symbol, (qpol.qpol_terule_t, qpol.qpol_filename_trans_t)): + return TERule(policy, symbol) + else: + raise TypeError("TE rules cannot be looked-up.") + + +def validate_ruletype(types): + """Validate TE Rule types.""" + for t in types: + if t not in ["allow", "auditallow", "dontaudit", "neverallow", + "type_transition", "type_member", "type_change"]: + raise exception.InvalidTERuleType("{0} is not a valid TE rule type.".format(t)) + + +class BaseTERule(rule.PolicyRule): + + """A type enforcement rule.""" + + @property + def source(self): + """The rule's source type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.source_type(self.policy)) + + @property + def target(self): + """The rule's target type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.target_type(self.policy)) + + @property + def filename(self): + raise NotImplementedError + + @property + def conditional(self): + """The rule's conditional expression.""" + try: + return boolcond.condexpr_factory(self.policy, self.qpol_symbol.cond(self.policy)) + except (AttributeError, ValueError): + # AttributeError: name filetrans rules cannot be conditional + # so no member function + # ValueError: The rule is not conditional + raise exception.RuleNotConditional + + +class AVRule(BaseTERule): + + """An access vector type enforcement rule.""" + + def __str__(self): + rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} ".format( + self) + + perms = self.perms + + # allow/dontaudit/auditallow/neverallow rules + if len(perms) > 1: + rule_string += "{{ {0} }};".format(' '.join(perms)) + else: + # convert to list since sets cannot be indexed + rule_string += "{0};".format(list(perms)[0]) + + try: + rule_string += " [ {0} ]".format(self.conditional) + except exception.RuleNotConditional: + pass + + return rule_string + + @property + def perms(self): + """The rule's permission set.""" + return set(self.qpol_symbol.perm_iter(self.policy)) + + @property + def default(self): + """The rule's default type.""" + raise exception.RuleUseError("{0} rules do not have a default type.".format(self.ruletype)) + + @property + def filename(self): + raise exception.RuleUseError("{0} rules do not have file names".format(self.ruletype)) + + +class TERule(BaseTERule): + + """A type_* type enforcement rule.""" + + def __str__(self): + rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} {0.default}".format(self) + + try: + rule_string += " \"{0}\";".format(self.filename) + except (exception.TERuleNoFilename, exception.RuleUseError): + # invalid use for type_change/member + rule_string += ";" + + try: + rule_string += " [ {0} ]".format(self.conditional) + except exception.RuleNotConditional: + pass + + return rule_string + + @property + def perms(self): + """The rule's permission set.""" + raise exception.RuleUseError( + "{0} rules do not have a permission set.".format(self.ruletype)) + + @property + def default(self): + """The rule's default type.""" + return typeattr.type_factory(self.policy, self.qpol_symbol.default_type(self.policy)) + + @property + def filename(self): + """The type_transition rule's file name.""" + try: + return self.qpol_symbol.filename(self.policy) + except AttributeError: + if self.ruletype == "type_transition": + raise exception.TERuleNoFilename + else: + raise exception.RuleUseError("{0} rules do not have file names". + format(self.ruletype)) diff --git a/lib/python2.7/site-packages/setools/policyrep/typeattr.py b/lib/python2.7/site-packages/setools/policyrep/typeattr.py new file mode 100644 index 0000000..a52c69a --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/typeattr.py @@ -0,0 +1,174 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol + + +def _symbol_lookup(qpol_policy, name): + """Look up the low-level qpol policy reference""" + if isinstance(name, qpol.qpol_type_t): + return name + + try: + return qpol.qpol_type_t(qpol_policy, str(name)) + except ValueError: + raise exception.InvalidType("{0} is not a valid type/attribute".format(name)) + + +def attribute_factory(qpol_policy, name): + """Factory function for creating attribute objects.""" + + if isinstance(name, TypeAttribute): + assert name.policy == qpol_policy + return name + + qpol_symbol = _symbol_lookup(qpol_policy, name) + + if not qpol_symbol.isattr(qpol_policy): + raise TypeError("{0} is a type".format(qpol_symbol.name(qpol_policy))) + + return TypeAttribute(qpol_policy, qpol_symbol) + + +def type_factory(qpol_policy, name, deref=False): + """Factory function for creating type objects.""" + + if isinstance(name, Type): + assert name.policy == qpol_policy + return name + + qpol_symbol = _symbol_lookup(qpol_policy, name) + + if qpol_symbol.isattr(qpol_policy): + raise TypeError("{0} is an attribute".format(qpol_symbol.name(qpol_policy))) + elif qpol_symbol.isalias(qpol_policy) and not deref: + raise TypeError("{0} is an alias.".format(qpol_symbol.name(qpol_policy))) + + return Type(qpol_policy, qpol_symbol) + + +def type_or_attr_factory(qpol_policy, name, deref=False): + """Factory function for creating type or attribute objects.""" + + if isinstance(name, (Type, TypeAttribute)): + assert name.policy == qpol_policy + return name + + qpol_symbol = _symbol_lookup(qpol_policy, name) + + if qpol_symbol.isalias(qpol_policy) and not deref: + raise TypeError("{0} is an alias.".format(qpol_symbol.name(qpol_policy))) + + if qpol_symbol.isattr(qpol_policy): + return TypeAttribute(qpol_policy, qpol_symbol) + else: + return Type(qpol_policy, qpol_symbol) + + +class BaseType(symbol.PolicySymbol): + + """Type/attribute base class.""" + + @property + def ispermissive(self): + raise NotImplementedError + + def expand(self): + """Generator that expands this attribute into its member types.""" + raise NotImplementedError + + def attributes(self): + """Generator that yields all attributes for this type.""" + raise NotImplementedError + + def aliases(self): + """Generator that yields all aliases for this type.""" + raise NotImplementedError + + +class Type(BaseType): + + """A type.""" + + @property + def ispermissive(self): + """(T/F) the type is permissive.""" + return self.qpol_symbol.ispermissive(self.policy) + + def expand(self): + """Generator that expands this into its member types.""" + yield self + + def attributes(self): + """Generator that yields all attributes for this type.""" + for attr in self.qpol_symbol.attr_iter(self.policy): + yield attribute_factory(self.policy, attr) + + def aliases(self): + """Generator that yields all aliases for this type.""" + for alias in self.qpol_symbol.alias_iter(self.policy): + yield alias + + def statement(self): + attrs = list(self.attributes()) + aliases = list(self.aliases()) + stmt = "type {0}".format(self) + if aliases: + if len(aliases) > 1: + stmt += " alias {{ {0} }}".format(' '.join(aliases)) + else: + stmt += " alias {0}".format(aliases[0]) + for attr in attrs: + stmt += ", {0}".format(attr) + stmt += ";" + return stmt + + +class TypeAttribute(BaseType): + + """An attribute.""" + + def __contains__(self, other): + for type_ in self.expand(): + if other == type_: + return True + + return False + + def expand(self): + """Generator that expands this attribute into its member types.""" + for type_ in self.qpol_symbol.type_iter(self.policy): + yield type_factory(self.policy, type_) + + def attributes(self): + """Generator that yields all attributes for this type.""" + raise TypeError("{0} is an attribute, thus does not have attributes.".format(self)) + + def aliases(self): + """Generator that yields all aliases for this type.""" + raise TypeError("{0} is an attribute, thus does not have aliases.".format(self)) + + @property + def ispermissive(self): + """(T/F) the type is permissive.""" + raise TypeError("{0} is an attribute, thus cannot be permissive.".format(self)) + + def statement(self): + return "attribute {0};".format(self) diff --git a/lib/python2.7/site-packages/setools/policyrep/user.py b/lib/python2.7/site-packages/setools/policyrep/user.py new file mode 100644 index 0000000..94f81bc --- /dev/null +++ b/lib/python2.7/site-packages/setools/policyrep/user.py @@ -0,0 +1,86 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import role +from . import mls +from . import symbol + + +def user_factory(qpol_policy, name): + """Factory function for creating User objects.""" + + if isinstance(name, User): + assert name.policy == qpol_policy + return name + elif isinstance(name, qpol.qpol_user_t): + return User(qpol_policy, name) + + try: + return User(qpol_policy, qpol.qpol_user_t(qpol_policy, str(name))) + except ValueError: + raise exception.InvalidUser("{0} is not a valid user".format(name)) + + +class User(symbol.PolicySymbol): + + """A user.""" + + @property + def roles(self): + """The user's set of roles.""" + + roleset = set() + + for role_ in self.qpol_symbol.role_iter(self.policy): + item = role.role_factory(self.policy, role_) + + # object_r is implicitly added to all roles by the compiler. + # technically it is incorrect to skip it, but policy writers + # and analysts don't expect to see it in results, and it + # will confuse, especially for role set equality user queries. + if item != "object_r": + roleset.add(item) + + return roleset + + @property + def mls_level(self): + """The user's default MLS level.""" + return mls.level_factory(self.policy, self.qpol_symbol.dfltlevel(self.policy)) + + @property + def mls_range(self): + """The user's MLS range.""" + return mls.range_factory(self.policy, self.qpol_symbol.range(self.policy)) + + def statement(self): + roles = list(str(r) for r in self.roles) + stmt = "user {0} roles ".format(self) + if len(roles) > 1: + stmt += "{{ {0} }}".format(' '.join(roles)) + else: + stmt += roles[0] + + try: + stmt += " level {0.mls_level} range {0.mls_range};".format(self) + except exception.MLSDisabled: + stmt += ";" + + return stmt diff --git a/lib/python2.7/site-packages/setools/portconquery.py b/lib/python2.7/site-packages/setools/portconquery.py new file mode 100644 index 0000000..798a828 --- /dev/null +++ b/lib/python2.7/site-packages/setools/portconquery.py @@ -0,0 +1,146 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +from socket import IPPROTO_TCP, IPPROTO_UDP + +from . import contextquery +from .policyrep.netcontext import port_range + + +class PortconQuery(contextquery.ContextQuery): + + """ + Port context query. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + protocol The protocol to match (socket.IPPROTO_TCP for + TCP or socket.IPPROTO_UDP for UDP) + + ports A 2-tuple of the port range to match. (Set both to + the same value for a single port) + ports_subset If true, the criteria will match if it is a subset + of the portcon's range. + ports_overlap If true, the criteria will match if it overlaps + any of the portcon's range. + ports_superset If true, the criteria will match if it is a superset + of the portcon's range. + ports_proper If true, use proper superset/subset operations. + No effect if not using set operations. + + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + _protocol = None + _ports = None + ports_subset = False + ports_overlap = False + ports_superset = False + ports_proper = False + + @property + def ports(self): + return self._ports + + @ports.setter + def ports(self, value): + pending_ports = port_range(*value) + + if all(pending_ports): + if pending_ports.low < 1 or pending_ports.high < 1: + raise ValueError("Port numbers must be positive: {0.low}-{0.high}". + format(pending_ports)) + + if pending_ports.low > pending_ports.high: + raise ValueError( + "The low port must be smaller than the high port: {0.low}-{0.high}". + format(pending_ports)) + + self._ports = pending_ports + else: + self._ports = None + + @property + def protocol(self): + return self._protocol + + @protocol.setter + def protocol(self, value): + if value: + if not (value == IPPROTO_TCP or value == IPPROTO_UDP): + raise ValueError( + "The protocol must be {0} for TCP or {1} for UDP.". + format(IPPROTO_TCP, IPPROTO_UDP)) + + self._protocol = value + else: + self._protocol = None + + def results(self): + """Generator which yields all matching portcons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ports: {0.ports}, overlap: {0.ports_overlap}, " + "subset: {0.ports_subset}, superset: {0.ports_superset}, " + "proper: {0.ports_proper}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for portcon in self.policy.portcons(): + + if self.ports and not self._match_range( + portcon.ports, + self.ports, + self.ports_subset, + self.ports_overlap, + self.ports_superset, + self.ports_proper): + continue + + if self.protocol and self.protocol != portcon.protocol: + continue + + if not self._match_context(portcon.context): + continue + + yield portcon diff --git a/lib/python2.7/site-packages/setools/query.py b/lib/python2.7/site-packages/setools/query.py new file mode 100644 index 0000000..358a095 --- /dev/null +++ b/lib/python2.7/site-packages/setools/query.py @@ -0,0 +1,192 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + + +class PolicyQuery(object): + + """Base class for SELinux policy queries.""" + + def __init__(self, policy, **kwargs): + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + + # keys are sorted in reverse order so regex settings + # are set before the criteria, e.g. name_regex + # is set before name. This ensures correct behavior + # since the criteria descriptors are sensitve to + # regex settings. + for name in sorted(kwargs.keys(), reverse=True): + attr = getattr(self, name, None) # None is not callable + if callable(attr): + raise ValueError("Keyword parameter {0} conflicts with a callable.".format(name)) + + setattr(self, name, kwargs[name]) + + @staticmethod + def _match_regex(obj, criteria, regex): + """ + Match the object with optional regular expression. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + """ + + if regex: + return bool(criteria.search(str(obj))) + else: + return obj == criteria + + @staticmethod + def _match_set(obj, criteria, equal): + """ + Match the object (a set) with optional set equality. + + Parameters: + obj The object to match. (a set) + criteria The criteria to match. (a set) + equal If set equality should be used. Otherwise + any set intersection will match. + """ + + if equal: + return obj == criteria + else: + return bool(obj.intersection(criteria)) + + @staticmethod + def _match_in_set(obj, criteria, regex): + """ + Match if the criteria is in the list, with optional + regular expression matching. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + """ + + if regex: + return [m for m in obj if criteria.search(str(m))] + else: + return criteria in obj + + @staticmethod + def _match_indirect_regex(obj, criteria, indirect, regex): + """ + Match the object with optional regular expression and indirection. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + indirect If object indirection should be used, e.g. + expanding an attribute. + """ + + if indirect: + return PolicyQuery._match_in_set((obj.expand()), criteria, regex) + else: + return PolicyQuery._match_regex(obj, criteria, regex) + + @staticmethod + def _match_regex_or_set(obj, criteria, equal, regex): + """ + Match the object (a set) with either set comparisons + (equality or intersection) or by regex matching of the + set members. Regular expression matching will override + the set equality option. + + Parameters: + obj The object to match. (a set) + criteria The criteria to match. + equal If set equality should be used. Otherwise + any set intersection will match. Ignored + if regular expression matching is used. + regex If regular expression matching should be used. + """ + + if regex: + return [m for m in obj if criteria.search(str(m))] + else: + return PolicyQuery._match_set(obj, set(criteria), equal) + + @staticmethod + def _match_range(obj, criteria, subset, overlap, superset, proper): + """ + Match ranges of objects. + + obj An object with attributes named "low" and "high", representing the range. + criteria An object with attributes named "low" and "high", representing the criteria. + subset If true, the criteria will match if it is a subset obj's range. + overlap If true, the criteria will match if it overlaps any of the obj's range. + superset If true, the criteria will match if it is a superset of the obj's range. + proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + if overlap: + return ((obj.low <= criteria.low <= obj.high) or ( + obj.low <= criteria.high <= obj.high) or ( + criteria.low <= obj.low and obj.high <= criteria.high)) + elif subset: + if proper: + return ((obj.low < criteria.low and criteria.high <= obj.high) or ( + obj.low <= criteria.low and criteria.high < obj.high)) + else: + return obj.low <= criteria.low and criteria.high <= obj.high + elif superset: + if proper: + return ((criteria.low < obj.low and obj.high <= criteria.high) or ( + criteria.low <= obj.low and obj.high < criteria.high)) + else: + return (criteria.low <= obj.low and obj.high <= criteria.high) + else: + return criteria.low == obj.low and obj.high == criteria.high + + @staticmethod + def _match_level(obj, criteria, dom, domby, incomp): + """ + Match the an MLS level. + + obj The level to match. + criteria The criteria to match. (a level) + dom If true, the criteria will match if it dominates obj. + domby If true, the criteria will match if it is dominated by obj. + incomp If true, the criteria will match if it is incomparable to obj. + """ + + if dom: + return (criteria >= obj) + elif domby: + return (criteria <= obj) + elif incomp: + return (criteria ^ obj) + else: + return (criteria == obj) + + def results(self): + """ + Generator which returns the matches for the query. This method + should be overridden by subclasses. + """ + raise NotImplementedError diff --git a/lib/python2.7/site-packages/setools/rbacrulequery.py b/lib/python2.7/site-packages/setools/rbacrulequery.py new file mode 100644 index 0000000..240b921 --- /dev/null +++ b/lib/python2.7/site-packages/setools/rbacrulequery.py @@ -0,0 +1,147 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor +from .policyrep.exception import InvalidType, RuleUseError + + +class RBACRuleQuery(mixins.MatchObjClass, query.PolicyQuery): + + """ + Query the RBAC rules. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source role/attribute to match. + source_indirect If true, members of an attribute will be + matched rather than the attribute itself. + source_regex If true, regular expression matching will + be used on the source role/attribute. + Obeys the source_indirect option. + target The name of the target role/attribute to match. + target_indirect If true, members of an attribute will be + matched rather than the attribute itself. + target_regex If true, regular expression matching will + be used on the target role/attribute. + Obeys target_indirect option. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + default The name of the default role to match. + default_regex If true, regular expression matching will + be used on the default role. + """ + + ruletype = RuletypeDescriptor("validate_rbac_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_role") + source_regex = False + source_indirect = True + _target = None + target_regex = False + target_indirect = True + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + default = CriteriaDescriptor("default_regex", "lookup_role") + default_regex = False + + @property + def target(self): + return self._target + + @target.setter + def target(self, value): + if not value: + self._target = None + elif self.target_regex: + self._target = re.compile(value) + else: + try: + self._target = self.policy.lookup_type_or_attr(value) + except InvalidType: + self._target = self.policy.lookup_role(value) + + def results(self): + """Generator which yields all matching RBAC rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Source: {0.source!r}, indirect: {0.source_indirect}, " + "regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, indirect: {0.target_indirect}, " + "regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Default: {0.default!r}, regex: {0.default_regex}".format(self)) + + for rule in self.policy.rbacrules(): + # + # Matching on rule type + # + if self.ruletype: + if rule.ruletype not in self.ruletype: + continue + + # + # Matching on source role + # + if self.source and not self._match_indirect_regex( + rule.source, + self.source, + self.source_indirect, + self.source_regex): + continue + + # + # Matching on target type (role_transition)/role(allow) + # + if self.target and not self._match_indirect_regex( + rule.target, + self.target, + self.target_indirect, + self.target_regex): + continue + + # + # Matching on object class + # + try: + if not self._match_object_class(rule): + continue + except RuleUseError: + continue + + # + # Matching on default role + # + if self.default: + try: + if not self._match_regex( + rule.default, + self.default, + self.default_regex): + continue + except RuleUseError: + continue + + # if we get here, we have matched all available criteria + yield rule diff --git a/lib/python2.7/site-packages/setools/rolequery.py b/lib/python2.7/site-packages/setools/rolequery.py new file mode 100644 index 0000000..e95dfa6 --- /dev/null +++ b/lib/python2.7/site-packages/setools/rolequery.py @@ -0,0 +1,77 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaSetDescriptor + + +class RoleQuery(compquery.ComponentQuery): + + """ + Query SELinux policy roles. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The role name to match. + name_regex If true, regular expression matching + will be used on the role names. + types The type to match. + types_equal If true, only roles with type sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + types_regex If true, regular expression matching + will be used on the type names instead + of set logic. + """ + + types = CriteriaSetDescriptor("types_regex", "lookup_type") + types_equal = False + types_regex = False + + def results(self): + """Generator which yields all matching roles.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Types: {0.types!r}, regex: {0.types_regex}, " + "eq: {0.types_equal}".format(self)) + + for r in self.policy.roles(): + if r == "object_r": + # all types are implicitly added to object_r by the compiler. + # technically it is incorrect to skip it, but policy writers + # and analysts don't expect to see it in results, and it + # will confuse, especially for set equality type queries. + continue + + if not self._match_name(r): + continue + + if self.types and not self._match_regex_or_set( + set(r.types()), + self.types, + self.types_equal, + self.types_regex): + continue + + yield r diff --git a/lib/python2.7/site-packages/setools/sensitivityquery.py b/lib/python2.7/site-packages/setools/sensitivityquery.py new file mode 100644 index 0000000..a102836 --- /dev/null +++ b/lib/python2.7/site-packages/setools/sensitivityquery.py @@ -0,0 +1,74 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import mixins +from .descriptors import CriteriaDescriptor + + +class SensitivityQuery(mixins.MatchAlias, compquery.ComponentQuery): + + """ + Query MLS Sensitivities + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the category to match. + name_regex If true, regular expression matching will + be used for matching the name. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + sens The criteria to match the sensitivity by dominance. + sens_dom If true, the criteria will match if it dominates + the sensitivity. + sens_domby If true, the criteria will match if it is dominated + by the sensitivity. + """ + + sens = CriteriaDescriptor(lookup_function="lookup_sensitivity") + sens_dom = False + sens_domby = False + + def results(self): + """Generator which yields all matching sensitivities.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + self.log.debug("Sens: {0.sens!r}, dom: {0.sens_dom}, domby: {0.sens_domby}".format(self)) + + for s in self.policy.sensitivities(): + if not self._match_name(s): + continue + + if not self._match_alias(s): + continue + + if self.sens and not self._match_level( + s, + self.sens, + self.sens_dom, + self.sens_domby, + False): + continue + + yield s diff --git a/lib/python2.7/site-packages/setools/terulequery.py b/lib/python2.7/site-packages/setools/terulequery.py new file mode 100644 index 0000000..7f3eccf --- /dev/null +++ b/lib/python2.7/site-packages/setools/terulequery.py @@ -0,0 +1,178 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor +from .policyrep.exception import RuleUseError, RuleNotConditional + + +class TERuleQuery(mixins.MatchObjClass, mixins.MatchPermission, query.PolicyQuery): + + """ + Query the Type Enforcement rules. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source type/attribute to match. + source_indirect If true, members of an attribute will be + matched rather than the attribute itself. + Default is true. + source_regex If true, regular expression matching will + be used on the source type/attribute. + Obeys the source_indirect option. + Default is false. + target The name of the target type/attribute to match. + target_indirect If true, members of an attribute will be + matched rather than the attribute itself. + Default is true. + target_regex If true, regular expression matching will + be used on the target type/attribute. + Obeys target_indirect option. + Default is false. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + Default is false. + perms The set of permission(s) to match. + perms_equal If true, the permission set of the rule + must exactly match the permissions + criteria. If false, any set intersection + will match. + Default is false. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + default The name of the default type to match. + default_regex If true, regular expression matching will be + used on the default type. + Default is false. + boolean The set of boolean(s) to match. + boolean_regex If true, regular expression matching will be + used on the booleans. + Default is false. + boolean_equal If true, the booleans in the conditional + expression of the rule must exactly match the + criteria. If false, any set intersection + will match. Default is false. + """ + + ruletype = RuletypeDescriptor("validate_te_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_type_or_attr") + source_regex = False + source_indirect = True + target = CriteriaDescriptor("target_regex", "lookup_type_or_attr") + target_regex = False + target_indirect = True + default = CriteriaDescriptor("default_regex", "lookup_type") + default_regex = False + boolean = CriteriaSetDescriptor("boolean_regex", "lookup_boolean") + boolean_regex = False + boolean_equal = False + + def results(self): + """Generator which yields all matching TE rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Source: {0.source!r}, indirect: {0.source_indirect}, " + "regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, indirect: {0.target_indirect}, " + "regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + self.log.debug("Default: {0.default!r}, regex: {0.default_regex}".format(self)) + self.log.debug("Boolean: {0.boolean!r}, eq: {0.boolean_equal}, " + "regex: {0.boolean_regex}".format(self)) + + for rule in self.policy.terules(): + # + # Matching on rule type + # + if self.ruletype: + if rule.ruletype not in self.ruletype: + continue + + # + # Matching on source type + # + if self.source and not self._match_indirect_regex( + rule.source, + self.source, + self.source_indirect, + self.source_regex): + continue + + # + # Matching on target type + # + if self.target and not self._match_indirect_regex( + rule.target, + self.target, + self.target_indirect, + self.target_regex): + continue + + # + # Matching on object class + # + if not self._match_object_class(rule): + continue + + # + # Matching on permission set + # + try: + if not self._match_perms(rule): + continue + except RuleUseError: + continue + + # + # Matching on default type + # + if self.default: + try: + if not self._match_regex( + rule.default, + self.default, + self.default_regex): + continue + except RuleUseError: + continue + + # + # Match on Boolean in conditional expression + # + if self.boolean: + try: + if not self._match_regex_or_set( + rule.conditional.booleans, + self.boolean, + self.boolean_equal, + self.boolean_regex): + continue + except RuleNotConditional: + continue + + # if we get here, we have matched all available criteria + yield rule diff --git a/lib/python2.7/site-packages/setools/typeattrquery.py b/lib/python2.7/site-packages/setools/typeattrquery.py new file mode 100644 index 0000000..a91026c --- /dev/null +++ b/lib/python2.7/site-packages/setools/typeattrquery.py @@ -0,0 +1,70 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaSetDescriptor + + +class TypeAttributeQuery(compquery.ComponentQuery): + + """ + Query SELinux policy type attributes. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The type name to match. + name_regex If true, regular expression matching + will be used on the type names. + types The type to match. + types_equal If true, only attributes with type sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + types_regex If true, regular expression matching + will be used on the type names instead + of set logic. + """ + + types = CriteriaSetDescriptor("types_regex", "lookup_type") + types_equal = False + types_regex = False + + def results(self): + """Generator which yields all matching types.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Types: {0.types!r}, regex: {0.types_regex}, " + "eq: {0.types_equal}".format(self)) + + for attr in self.policy.typeattributes(): + if not self._match_name(attr): + continue + + if self.types and not self._match_regex_or_set( + set(attr.expand()), + self.types, + self.types_equal, + self.types_regex): + continue + + yield attr diff --git a/lib/python2.7/site-packages/setools/typequery.py b/lib/python2.7/site-packages/setools/typequery.py new file mode 100644 index 0000000..6634f76 --- /dev/null +++ b/lib/python2.7/site-packages/setools/typequery.py @@ -0,0 +1,96 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from . import mixins +from .descriptors import CriteriaSetDescriptor + + +class TypeQuery(mixins.MatchAlias, compquery.ComponentQuery): + + """ + Query SELinux policy types. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The type name to match. + name_regex If true, regular expression matching + will be used on the type names. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + attrs The attribute to match. + attrs_equal If true, only types with attribute sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + attrs_regex If true, regular expression matching + will be used on the attribute names instead + of set logic. + permissive The permissive state to match. If this + is None, the state is not matched. + """ + + attrs = CriteriaSetDescriptor("attrs_regex", "lookup_typeattr") + attrs_regex = False + attrs_equal = False + _permissive = None + + @property + def permissive(self): + return self._permissive + + @permissive.setter + def permissive(self, value): + if value is None: + self._permissive = None + else: + self._permissive = bool(value) + + def results(self): + """Generator which yields all matching types.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + self.log.debug("Attrs: {0.attrs!r}, regex: {0.attrs_regex}, " + "eq: {0.attrs_equal}".format(self)) + self.log.debug("Permissive: {0.permissive}".format(self)) + + for t in self.policy.types(): + if not self._match_name(t): + continue + + if not self._match_alias(t): + continue + + if self.attrs and not self._match_regex_or_set( + set(t.attributes()), + self.attrs, + self.attrs_equal, + self.attrs_regex): + continue + + if self.permissive is not None and t.ispermissive != self.permissive: + continue + + yield t diff --git a/lib/python2.7/site-packages/setools/userquery.py b/lib/python2.7/site-packages/setools/userquery.py new file mode 100644 index 0000000..00910cf --- /dev/null +++ b/lib/python2.7/site-packages/setools/userquery.py @@ -0,0 +1,116 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + + +class UserQuery(compquery.ComponentQuery): + + """ + Query SELinux policy users. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The user name to match. + name_regex If true, regular expression matching + will be used on the user names. + roles The attribute to match. + roles_equal If true, only types with role sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + roles_regex If true, regular expression matching + will be used on the role names instead + of set logic. + level The criteria to match the user's default level. + level_dom If true, the criteria will match if it dominates + the user's default level. + level_domby If true, the criteria will match if it is dominated + by the user's default level. + level_incomp If true, the criteria will match if it is incomparable + to the user's default level. + range_ The criteria to match the user's range. + range_subset If true, the criteria will match if it is a subset + of the user's range. + range_overlap If true, the criteria will match if it overlaps + any of the user's range. + range_superset If true, the criteria will match if it is a superset + of the user's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + level = CriteriaDescriptor(lookup_function="lookup_level") + level_dom = False + level_domby = False + level_incomp = False + range_ = CriteriaDescriptor(lookup_function="lookup_range") + range_overlap = False + range_subset = False + range_superset = False + range_proper = False + roles = CriteriaSetDescriptor("roles_regex", "lookup_role") + roles_equal = False + roles_regex = False + + def results(self): + """Generator which yields all matching users.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Roles: {0.roles!r}, regex: {0.roles_regex}, " + "eq: {0.roles_equal}".format(self)) + self.log.debug("Level: {0.level!r}, dom: {0.level_dom}, domby: {0.level_domby}, " + "incomp: {0.level_incomp}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for user in self.policy.users(): + if not self._match_name(user): + continue + + if self.roles and not self._match_regex_or_set( + user.roles, + self.roles, + self.roles_equal, + self.roles_regex): + continue + + if self.level and not self._match_level( + user.mls_level, + self.level, + self.level_dom, + self.level_domby, + self.level_incomp): + continue + + if self.range_ and not self._match_range( + user.mls_range, + self.range_, + self.range_subset, + self.range_overlap, + self.range_superset, + self.range_proper): + continue + + yield user diff --git a/lib/python2.7/site-packages/setoolsgui/__init__.py b/lib/python2.7/site-packages/setoolsgui/__init__.py new file mode 100644 index 0000000..ea702ec --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +from .apol import ApolMainWindow +from . import widget diff --git a/lib/python2.7/site-packages/setoolsgui/apol/__init__.py b/lib/python2.7/site-packages/setoolsgui/apol/__init__.py new file mode 100644 index 0000000..22c8f40 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/apol/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +from .mainwindow import ApolMainWindow, ChooseAnalysis + +from .models import PermListModel, SEToolsListModel +from .rulemodels import TERuleListModel +from .terulequery import TERuleQueryTab diff --git a/lib/python2.7/site-packages/setoolsgui/apol/mainwindow.py b/lib/python2.7/site-packages/setoolsgui/apol/mainwindow.py new file mode 100644 index 0000000..53b9f87 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/apol/mainwindow.py @@ -0,0 +1,261 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +import logging + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QAction, QDialog, QFileDialog, QLineEdit, QMainWindow, QMenu, \ + QMessageBox, QTreeWidgetItem, QVBoxLayout, QWidget +from setools import PermissionMap, SELinuxPolicy + +from ..widget import SEToolsWidget +from .terulequery import TERuleQueryTab + + +class ApolMainWindow(SEToolsWidget, QMainWindow): + + def __init__(self, filename): + super(ApolMainWindow, self).__init__() + self.log = logging.getLogger(self.__class__.__name__) + + if filename: + self._policy = SELinuxPolicy(filename) + else: + self._policy = None + + try: + # try to load default permission map + self._permmap = PermissionMap() + except (IOError, OSError) as ex: + self.log.info("Failed to load default permission map: {0}".format(ex)) + self._permmap = None + + self.setupUi() + + def setupUi(self): + self.load_ui("apol.ui") + + self.tab_counter = 0 + + self.update_window_title() + + # set up error message dialog + self.error_msg = QMessageBox(self) + self.error_msg.setStandardButtons(QMessageBox.Ok) + + # set up tab name editor + self.tab_editor = QLineEdit(self.AnalysisTabs) + self.tab_editor.setWindowFlags(Qt.Popup) + + # configure tab bar context menu + tabBar = self.AnalysisTabs.tabBar() + self.rename_tab_action = QAction("&Rename active tab", tabBar) + self.close_tab_action = QAction("&Close active tab", tabBar) + tabBar.addAction(self.rename_tab_action) + tabBar.addAction(self.close_tab_action) + tabBar.setContextMenuPolicy(Qt.ActionsContextMenu) + + # connect signals + self.open_policy.triggered.connect(self.select_policy) + self.open_permmap.triggered.connect(self.select_permmap) + self.new_analysis.triggered.connect(self.choose_analysis) + self.AnalysisTabs.tabCloseRequested.connect(self.close_tab) + self.AnalysisTabs.tabBarDoubleClicked.connect(self.tab_name_editor) + self.tab_editor.editingFinished.connect(self.rename_tab) + self.rename_tab_action.triggered.connect(self.rename_active_tab) + self.close_tab_action.triggered.connect(self.close_active_tab) + + self.show() + + def update_window_title(self): + if self._policy: + self.setWindowTitle("{0} - apol".format(self._policy)) + else: + self.setWindowTitle("apol") + + def select_policy(self): + filename = QFileDialog.getOpenFileName(self, "Open policy file", ".")[0] + if filename: + try: + self._policy = SELinuxPolicy(filename) + except Exception as ex: + self.error_msg.critical(self, "Policy loading error", str(ex)) + else: + self.update_window_title() + + if self._permmap: + self._permmap.map_policy(self._policy) + + def select_permmap(self): + filename = QFileDialog.getOpenFileName(self, "Open permission map file", ".")[0] + if filename: + try: + self._permmap = PermissionMap(filename) + except Exception as ex: + self.error_msg.critical(self, "Permission map loading error", str(ex)) + else: + + if self._policy: + self._permmap.map_policy(self._policy) + + def choose_analysis(self): + if not self._policy: + self.error_msg.critical(self, "No open policy", + "Cannot start a new analysis. Please open a policy first.") + + self.select_policy() + + if self._policy: + # this check of self._policy is here in case someone + # tries to start an analysis with no policy open, but then + # cancels out of the policy file chooser or there is an + # error opening the policy file. + chooser = ChooseAnalysis(self) + chooser.show() + + def create_new_analysis(self, tabtitle, tabclass): + self.tab_counter += 1 + counted_name = "{0}: {1}".format(self.tab_counter, tabtitle) + + newtab = QWidget() + newtab.setObjectName(counted_name) + + newanalysis = tabclass(newtab, self._policy) + + # create a vertical layout in the tab, place the analysis ui inside. + tabLayout = QVBoxLayout() + tabLayout.setContentsMargins(0, 0, 0, 0) + tabLayout.addWidget(newanalysis) + newtab.setLayout(tabLayout) + + index = self.AnalysisTabs.addTab(newtab, counted_name) + self.AnalysisTabs.setTabToolTip(index, tabtitle) + + def tab_name_editor(self, index): + if index >= 0: + tab_area = self.AnalysisTabs.tabBar().tabRect(index) + self.tab_editor.move(self.AnalysisTabs.mapToGlobal(tab_area.topLeft())) + self.tab_editor.setText(self.AnalysisTabs.tabText(index)) + self.tab_editor.selectAll() + self.tab_editor.show() + self.tab_editor.setFocus() + + def close_active_tab(self): + index = self.AnalysisTabs.currentIndex() + if index >= 0: + self.close_tab(index) + + def rename_active_tab(self): + index = self.AnalysisTabs.currentIndex() + if index >= 0: + self.tab_name_editor(index) + + def close_tab(self, index): + widget = self.AnalysisTabs.widget(index) + widget.close() + widget.deleteLater() + self.AnalysisTabs.removeTab(index) + + def rename_tab(self): + # this should never be negative since the editor is modal + index = self.AnalysisTabs.currentIndex() + + self.tab_editor.hide() + self.AnalysisTabs.setTabText(index, self.tab_editor.text()) + + +class ChooseAnalysis(SEToolsWidget, QDialog): + + """ + Dialog for choosing a new analysis + + The below class attributes are used for populating + the GUI contents and mapping them to the appropriate + tab widget class for the analysis. + + The item_mapping attribute will be populated to + map the tree list items to the analysis tab widgets. + """ + _components_map = {"Attributes (Type)": TERuleQueryTab, + "Booleans": TERuleQueryTab, + "Categories": TERuleQueryTab, + "Common Permission Sets": TERuleQueryTab, + "Object Classes": TERuleQueryTab, + "Policy Capabilities": TERuleQueryTab, + "Roles": TERuleQueryTab, + "Types": TERuleQueryTab, + "Users": TERuleQueryTab} + + _rule_map = {"TE Rules": TERuleQueryTab, + "RBAC Rules": TERuleQueryTab, + "MLS Rules": TERuleQueryTab, + "Constraints": TERuleQueryTab} + + _analysis_map = {"Domain Transition Analysis": TERuleQueryTab, + "Information Flow Analysis": TERuleQueryTab} + + _labeling_map = {"fs_use Statements": TERuleQueryTab, + "Genfscon Statements": TERuleQueryTab, + "Initial SID Statements": TERuleQueryTab, + "Netifcon Statements": TERuleQueryTab, + "Nodecon Statements": TERuleQueryTab, + "Portcon Statements": TERuleQueryTab} + + _analysis_choices = {"Components": _components_map, + "Rules": _rule_map, + "Analysis": _analysis_map, + "Labeling Statements": _labeling_map} + + def __init__(self, parent): + super(ChooseAnalysis, self).__init__(parent) + self.item_mapping = {} + self.parent = parent + self.setupUi() + + def setupUi(self): + self.load_ui("choose_analysis.ui") + self.buttonBox.accepted.connect(self.ok_clicked) + self.analysisTypes.doubleClicked.connect(self.ok_clicked) + + # populate the item list: + self.analysisTypes.clear() + for groupname, group in self._analysis_choices.items(): + groupitem = QTreeWidgetItem(self.analysisTypes) + groupitem.setText(0, groupname) + groupitem._tab_class = None + for entryname, cls in group.items(): + item = QTreeWidgetItem(groupitem) + item.setText(0, entryname) + item._tab_class = cls + groupitem.addChild(item) + + self.analysisTypes.expandAll() + + def ok_clicked(self): + try: + # .ui is set for single item selection. + item = self.analysisTypes.selectedItems()[0] + title = item.text(0) + self.parent.create_new_analysis(title, item._tab_class) + except (IndexError, TypeError): + # IndexError: nothing is selected + # TypeError: one of the group items was selected. + pass + else: + self.accept() diff --git a/lib/python2.7/site-packages/setoolsgui/apol/models.py b/lib/python2.7/site-packages/setoolsgui/apol/models.py new file mode 100644 index 0000000..2744ad6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/apol/models.py @@ -0,0 +1,103 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +from PyQt5 import QtCore +from PyQt5.QtCore import QAbstractListModel, QModelIndex, QStringListModel, Qt +from setools.policyrep.exception import NoCommon + + +class SEToolsListModel(QAbstractListModel): + + """ + The purpose of this model is to have the + objects return their string representations + for Qt.DisplayRole and return the object + for Qt.UserRole. + """ + + def __init__(self, parent): + super(SEToolsListModel, self).__init__(parent) + self._item_list = None + + @property + def item_list(self): + return self._item_list + + @item_list.setter + def item_list(self, item_list): + self.beginResetModel() + self._item_list = item_list + self.endResetModel() + + def rowCount(self, parent=QModelIndex()): + if self.item_list: + return len(self.item_list) + else: + return 0 + + def columnCount(self, parent=QModelIndex()): + return 1 + + def data(self, index, role): + if self.item_list: + row = index.row() + + if role == Qt.DisplayRole: + return str(self.item_list[row]) + elif role == Qt.UserRole: + return self.item_list[row] + + +class PermListModel(SEToolsListModel): + + """ + A model that will return the intersection of permissions + for the selected classes. If no classes are + set, all permissions in the policy will be returned. + """ + + def __init__(self, parent, policy): + super(PermListModel, self).__init__(parent) + self.policy = policy + self.set_classes() + + def set_classes(self, classes=[]): + permlist = set() + + # start will all permissions. + for cls in self.policy.classes(): + permlist.update(cls.perms) + + try: + permlist.update(cls.common.perms) + except NoCommon: + pass + + # create intersection + for cls in classes: + cls_perms = cls.perms + + try: + cls_perms.update(cls.common.perms) + except NoCommon: + pass + + permlist.intersection_update(cls_perms) + + self.item_list = sorted(permlist) diff --git a/lib/python2.7/site-packages/setoolsgui/apol/rulemodels.py b/lib/python2.7/site-packages/setoolsgui/apol/rulemodels.py new file mode 100644 index 0000000..4367cfb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/apol/rulemodels.py @@ -0,0 +1,116 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex +from setools.policyrep.exception import RuleNotConditional, RuleUseError + + +class RuleResultModel(QAbstractTableModel): + def __init__(self, parent): + super(RuleResultModel, self).__init__(parent) + self.resultlist = None + + def rowCount(self, parent=QModelIndex()): + if self.resultlist: + return len(self.resultlist) + else: + return 0 + + def columnCount(self, parent=QModelIndex()): + return 5 + + def headerData(self, section, orientation, role): + raise NotImplementedError + + def data(self, index, role): + if role == Qt.DisplayRole: + if not self.resultlist: + return None + + row = index.row() + col = index.column() + + if col == 0: + return self.resultlist[row].ruletype + elif col == 1: + return str(self.resultlist[row].source) + elif col == 2: + return str(self.resultlist[row].target) + elif col == 3: + try: + return str(self.resultlist[row].tclass) + except RuleUseError: + # role allow + return None + elif col == 4: + # most common: permissions + try: + return ", ".join(sorted(self.resultlist[row].perms)) + except RuleUseError: + pass + + # next most common: default + # TODO: figure out filename trans + try: + return str(self.resultlist[row].default) + except RuleUseError: + pass + + # least common: nothing (role allow) + return None + elif col == 5: + try: + return str(self.resultlist[row].conditional) + except RuleNotConditional: + return None + else: + raise ValueError("Invalid column number") + elif role == Qt.UserRole: + # get the whole rule for user role + return self.resultlist[row].statement() + + def set_rules(self, result_list): + self.beginResetModel() + self.resultlist = result_list + self.endResetModel() + + +class TERuleListModel(RuleResultModel): + + """Type Enforcement rule model. Represents rules as a column.""" + + def columnCount(self, parent=QModelIndex()): + return 6 + + def headerData(self, section, orientation, role): + if role == Qt.DisplayRole and orientation == Qt.Horizontal: + if section == 0: + return "Rule Type" + elif section == 1: + return "Source" + elif section == 2: + return "Target" + elif section == 3: + return "Object Class" + elif section == 4: + return "Permissons/Default Type" + elif section == 5: + return "Conditional Expression" + else: + raise ValueError("Invalid column number") diff --git a/lib/python2.7/site-packages/setoolsgui/apol/terulequery.py b/lib/python2.7/site-packages/setoolsgui/apol/terulequery.py new file mode 100644 index 0000000..75148fc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/apol/terulequery.py @@ -0,0 +1,271 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +import logging + +from PyQt5.QtCore import Qt, QSortFilterProxyModel, QStringListModel +from PyQt5.QtGui import QPalette, QTextCursor +from PyQt5.QtWidgets import QCompleter, QHeaderView, QScrollArea +from setools import TERuleQuery + +from ..widget import SEToolsWidget +from .rulemodels import TERuleListModel +from .models import PermListModel, SEToolsListModel + + +class TERuleQueryTab(SEToolsWidget, QScrollArea): + def __init__(self, parent, policy): + super(TERuleQueryTab, self).__init__(parent) + self.log = logging.getLogger(self.__class__.__name__) + self.policy = policy + self.query = TERuleQuery(policy) + self.setupUi() + + def setupUi(self): + self.load_ui("terulequery.ui") + + # set up source/target autocompletion + typeattr_completion_list = [str(t) for t in self.policy.types()] + typeattr_completion_list.extend(str(a) for a in self.policy.typeattributes()) + typeattr_completer_model = QStringListModel(self) + typeattr_completer_model.setStringList(sorted(typeattr_completion_list)) + self.typeattr_completion = QCompleter() + self.typeattr_completion.setModel(typeattr_completer_model) + self.source.setCompleter(self.typeattr_completion) + self.target.setCompleter(self.typeattr_completion) + + # set up default autocompletion + type_completion_list = [str(t) for t in self.policy.types()] + type_completer_model = QStringListModel(self) + type_completer_model.setStringList(sorted(type_completion_list)) + self.type_completion = QCompleter() + self.type_completion.setModel(type_completer_model) + self.default_type.setCompleter(self.type_completion) + + # setup indications of errors on source/target/default + self.orig_palette = self.source.palette() + self.error_palette = self.source.palette() + self.error_palette.setColor(QPalette.Base, Qt.red) + self.clear_source_error() + self.clear_target_error() + self.clear_default_error() + + # populate class list + self.class_model = SEToolsListModel(self) + self.class_model.item_list = sorted(self.policy.classes()) + self.tclass.setModel(self.class_model) + + # populate perm list + self.perms_model = PermListModel(self, self.policy) + self.perms.setModel(self.perms_model) + + # populate bool list + self.bool_model = SEToolsListModel(self) + self.bool_model.item_list = sorted(self.policy.bools()) + self.bool_criteria.setModel(self.bool_model) + + # set up results + self.table_results_model = TERuleListModel(self) + self.sort_proxy = QSortFilterProxyModel(self) + self.sort_proxy.setSourceModel(self.table_results_model) + self.table_results.setModel(self.sort_proxy) + + # Ensure settings are consistent with the initial .ui state + self.set_source_regex(self.source_regex.isChecked()) + self.set_target_regex(self.target_regex.isChecked()) + self.set_default_regex(self.default_regex.isChecked()) + self.criteria_frame.setHidden(not self.criteria_expander.isChecked()) + self.results_frame.setHidden(not self.results_expander.isChecked()) + self.notes.setHidden(not self.notes_expander.isChecked()) + + # connect signals + self.buttonBox.clicked.connect(self.run) + self.clear_ruletypes.clicked.connect(self.clear_all_ruletypes) + self.all_ruletypes.clicked.connect(self.set_all_ruletypes) + self.source.textEdited.connect(self.clear_source_error) + self.source.editingFinished.connect(self.set_source) + self.source_regex.toggled.connect(self.set_source_regex) + self.target.textEdited.connect(self.clear_target_error) + self.target.editingFinished.connect(self.set_target) + self.target_regex.toggled.connect(self.set_target_regex) + self.tclass.selectionModel().selectionChanged.connect(self.set_tclass) + self.perms.selectionModel().selectionChanged.connect(self.set_perms) + self.default_type.textEdited.connect(self.clear_default_error) + self.default_type.editingFinished.connect(self.set_default_type) + self.default_regex.toggled.connect(self.set_default_regex) + self.bool_criteria.selectionModel().selectionChanged.connect(self.set_bools) + + # + # Ruletype criteria + # + + def _set_ruletypes(self, value): + self.allow.setChecked(value) + self.auditallow.setChecked(value) + self.neverallow.setChecked(value) + self.dontaudit.setChecked(value) + self.type_transition.setChecked(value) + self.type_member.setChecked(value) + self.type_change.setChecked(value) + + def set_all_ruletypes(self): + self._set_ruletypes(True) + + def clear_all_ruletypes(self): + self._set_ruletypes(False) + + # + # Source criteria + # + + def clear_source_error(self): + self.source.setToolTip("Match the source type/attribute of the rule.") + self.source.setPalette(self.orig_palette) + + def set_source(self): + try: + self.query.source = self.source.text() + except Exception as ex: + self.source.setToolTip("Error: " + str(ex)) + self.source.setPalette(self.error_palette) + + def set_source_regex(self, state): + self.log.debug("Setting source_regex {0}".format(state)) + self.query.source_regex = state + self.clear_source_error() + self.set_source() + + # + # Target criteria + # + + def clear_target_error(self): + self.target.setToolTip("Match the target type/attribute of the rule.") + self.target.setPalette(self.orig_palette) + + def set_target(self): + try: + self.query.target = self.target.text() + except Exception as ex: + self.target.setToolTip("Error: " + str(ex)) + self.target.setPalette(self.error_palette) + + def set_target_regex(self, state): + self.log.debug("Setting target_regex {0}".format(state)) + self.query.target_regex = state + self.clear_target_error() + self.set_target() + + # + # Class criteria + # + + def set_tclass(self): + selected_classes = [] + for index in self.tclass.selectionModel().selectedIndexes(): + selected_classes.append(self.class_model.data(index, Qt.UserRole)) + + self.query.tclass = selected_classes + self.perms_model.set_classes(selected_classes) + + # + # Permissions criteria + # + + def set_perms(self): + selected_perms = [] + for index in self.perms.selectionModel().selectedIndexes(): + selected_perms.append(self.perms_model.data(index, Qt.UserRole)) + + self.query.perms = selected_perms + + # + # Default criteria + # + + def clear_default_error(self): + self.default_type.setToolTip("Match the default type the rule.") + self.default_type.setPalette(self.orig_palette) + + def set_default_type(self): + self.query.default_regex = self.default_regex.isChecked() + + try: + self.query.default = self.default_type.text() + except Exception as ex: + self.default_type.setToolTip("Error: " + str(ex)) + self.default_type.setPalette(self.error_palette) + + def set_default_regex(self, state): + self.log.debug("Setting default_regex {0}".format(state)) + self.query.default_regex = state + self.clear_default_error() + self.set_default_type() + + # + # Boolean criteria + # + + def set_bools(self): + selected_bools = [] + for index in self.bool_criteria.selectionModel().selectedIndexes(): + selected_bools.append(self.bool_model.data(index, Qt.UserRole)) + + self.query.boolean = selected_bools + + # + # Results runner + # + + def run(self, button): + # right now there is only one button. + rule_types = [] + + if self.allow.isChecked(): + rule_types.append("allow") + if self.auditallow.isChecked(): + rule_types.append("auditallow") + if self.neverallow.isChecked(): + rule_types.append("neverallow") + if self.dontaudit.isChecked(): + rule_types.append("dontaudit") + if self.type_transition.isChecked(): + rule_types.append("type_transition") + if self.type_member.isChecked(): + rule_types.append("type_member") + if self.type_change.isChecked(): + rule_types.append("type_change") + + self.query.ruletype = rule_types + self.query.source_indirect = self.source_indirect.isChecked() + self.query.target_indirect = self.target_indirect.isChecked() + self.query.perms_equal = self.perms_equal.isChecked() + self.query.boolean_equal = self.bools_equal.isChecked() + + # update results table + results = list(self.query.results()) + self.table_results_model.set_rules(results) + self.table_results.resizeColumnsToContents() + + # update raw results + self.raw_results.clear() + for line in results: + self.raw_results.appendPlainText(str(line)) + + self.raw_results.moveCursor(QTextCursor.Start) diff --git a/lib/python2.7/site-packages/setoolsgui/libselinux.so.1 b/lib/python2.7/site-packages/setoolsgui/libselinux.so.1 new file mode 100755 index 0000000000000000000000000000000000000000..dc9280d379898d1e4e119270631be36155194c43 GIT binary patch literal 172235 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 literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/__init__.py new file mode 100644 index 0000000..7090794 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/__init__.py @@ -0,0 +1,85 @@ +""" +NetworkX +======== + + NetworkX (NX) is a Python package for the creation, manipulation, and + study of the structure, dynamics, and functions of complex networks. + + https://networkx.lanl.gov/ + +Using +----- + + Just write in Python + + >>> import networkx as nx + >>> G=nx.Graph() + >>> G.add_edge(1,2) + >>> G.add_node(42) + >>> print(sorted(G.nodes())) + [1, 2, 42] + >>> print(sorted(G.edges())) + [(1, 2)] +""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# +# Add platform dependent shared library path to sys.path +# + +from __future__ import absolute_import + +import sys +if sys.version_info[:2] < (2, 6): + m = "Python version 2.6 or later is required for NetworkX (%d.%d detected)." + raise ImportError(m % sys.version_info[:2]) +del sys + +# Release data +from networkx import release + +__author__ = '%s <%s>\n%s <%s>\n%s <%s>' % \ + ( release.authors['Hagberg'] + release.authors['Schult'] + \ + release.authors['Swart'] ) +__license__ = release.license + +__date__ = release.date +__version__ = release.version + +#These are import orderwise +from networkx.exception import * +import networkx.external +import networkx.utils +# these packages work with Python >= 2.6 + +import networkx.classes +from networkx.classes import * + + +import networkx.convert +from networkx.convert import * + +import networkx.relabel +from networkx.relabel import * + +import networkx.generators +from networkx.generators import * + +import networkx.readwrite +from networkx.readwrite import * + +#Need to test with SciPy, when available +import networkx.algorithms +from networkx.algorithms import * +import networkx.linalg + +from networkx.linalg import * +from networkx.tests.test import run as test + +import networkx.drawing +from networkx.drawing import * + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/__init__.py new file mode 100644 index 0000000..6230da3 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/__init__.py @@ -0,0 +1,51 @@ +from networkx.algorithms.assortativity import * +from networkx.algorithms.block import * +from networkx.algorithms.boundary import * +from networkx.algorithms.centrality import * +from networkx.algorithms.cluster import * +from networkx.algorithms.clique import * +from networkx.algorithms.community import * +from networkx.algorithms.components import * +from networkx.algorithms.connectivity import * +from networkx.algorithms.core import * +from networkx.algorithms.cycles import * +from networkx.algorithms.dag import * +from networkx.algorithms.distance_measures import * +from networkx.algorithms.flow import * +from networkx.algorithms.hierarchy import * +from networkx.algorithms.matching import * +from networkx.algorithms.mis import * +from networkx.algorithms.mst import * +from networkx.algorithms.link_analysis import * +from networkx.algorithms.operators import * +from networkx.algorithms.shortest_paths import * +from networkx.algorithms.smetric import * +from networkx.algorithms.traversal import * +from networkx.algorithms.isolate import * +from networkx.algorithms.euler import * +from networkx.algorithms.vitality import * +from networkx.algorithms.chordal import * +from networkx.algorithms.richclub import * +from networkx.algorithms.distance_regular import * +from networkx.algorithms.swap import * +from networkx.algorithms.graphical import * +from networkx.algorithms.simple_paths import * + +import networkx.algorithms.assortativity +import networkx.algorithms.bipartite +import networkx.algorithms.centrality +import networkx.algorithms.cluster +import networkx.algorithms.clique +import networkx.algorithms.components +import networkx.algorithms.connectivity +import networkx.algorithms.flow +import networkx.algorithms.isomorphism +import networkx.algorithms.link_analysis +import networkx.algorithms.shortest_paths +import networkx.algorithms.traversal +import networkx.algorithms.chordal +import networkx.algorithms.operators + +from networkx.algorithms.bipartite import projected_graph,project,is_bipartite +from networkx.algorithms.isomorphism import is_isomorphic,could_be_isomorphic,\ + fast_could_be_isomorphic,faster_could_be_isomorphic diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/__init__.py new file mode 100644 index 0000000..eb797c2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/__init__.py @@ -0,0 +1,6 @@ +from networkx.algorithms.approximation.clique import * +from networkx.algorithms.approximation.dominating_set import * +from networkx.algorithms.approximation.independent_set import * +from networkx.algorithms.approximation.matching import * +from networkx.algorithms.approximation.ramsey import * +from networkx.algorithms.approximation.vertex_cover import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/clique.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/clique.py new file mode 100644 index 0000000..be363f6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/clique.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +""" +Cliques. +""" +# Copyright (C) 2011-2012 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.algorithms.approximation import ramsey +__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)""" +__all__ = ["clique_removal","max_clique"] + +def max_clique(G): + r"""Find the Maximum Clique + + Finds the `O(|V|/(log|V|)^2)` apx of maximum clique/independent set + in the worst case. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + clique : set + The apx-maximum clique of the graph + + Notes + ------ + A clique in an undirected graph G = (V, E) is a subset of the vertex set + `C \subseteq V`, such that for every two vertices in C, there exists an edge + connecting the two. This is equivalent to saying that the subgraph + induced by C is complete (in some cases, the term clique may also refer + to the subgraph). + + A maximum clique is a clique of the largest possible size in a given graph. + The clique number `\omega(G)` of a graph G is the number of + vertices in a maximum clique in G. The intersection number of + G is the smallest number of cliques that together cover all edges of G. + + http://en.wikipedia.org/wiki/Maximum_clique + + References + ---------- + .. [1] Boppana, R., & Halldórsson, M. M. (1992). + Approximating maximum independent sets by excluding subgraphs. + BIT Numerical Mathematics, 32(2), 180–196. Springer. + doi:10.1007/BF01994876 + """ + if G is None: + raise ValueError("Expected NetworkX graph!") + + # finding the maximum clique in a graph is equivalent to finding + # the independent set in the complementary graph + cgraph = nx.complement(G) + iset, _ = clique_removal(cgraph) + return iset + +def clique_removal(G): + """ Repeatedly remove cliques from the graph. + + Results in a `O(|V|/(\log |V|)^2)` approximation of maximum clique + & independent set. Returns the largest independent set found, along + with found maximal cliques. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + max_ind_cliques : (set, list) tuple + Maximal independent set and list of maximal cliques (sets) in the graph. + + References + ---------- + .. [1] Boppana, R., & Halldórsson, M. M. (1992). + Approximating maximum independent sets by excluding subgraphs. + BIT Numerical Mathematics, 32(2), 180–196. Springer. + """ + graph = G.copy() + c_i, i_i = ramsey.ramsey_R2(graph) + cliques = [c_i] + isets = [i_i] + while graph: + graph.remove_nodes_from(c_i) + c_i, i_i = ramsey.ramsey_R2(graph) + if c_i: + cliques.append(c_i) + if i_i: + isets.append(i_i) + + maxiset = max(isets) + return maxiset, cliques diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/dominating_set.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/dominating_set.py new file mode 100644 index 0000000..6a167e2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/dominating_set.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +""" +************************************** +Minimum Vertex and Edge Dominating Set +************************************** + + +A dominating set for a graph G = (V, E) is a subset D of V such that every +vertex not in D is joined to at least one member of D by some edge. The +domination number gamma(G) is the number of vertices in a smallest dominating +set for G. Given a graph G = (V, E) find a minimum weight dominating set V'. + +http://en.wikipedia.org/wiki/Dominating_set + +An edge dominating set for a graph G = (V, E) is a subset D of E such that +every edge not in D is adjacent to at least one edge in D. + +http://en.wikipedia.org/wiki/Edge_dominating_set +""" +# Copyright (C) 2011-2012 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +import networkx as nx +__all__ = ["min_weighted_dominating_set", + "min_edge_dominating_set"] +__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)""" + + +def min_weighted_dominating_set(G, weight=None): + r"""Return minimum weight vertex dominating set. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + weight : None or string, optional (default = None) + If None, every edge has weight/distance/weight 1. If a string, use this + edge attribute as the edge weight. Any edge attribute not present + defaults to 1. + + Returns + ------- + min_weight_dominating_set : set + Returns a set of vertices whose weight sum is no more than log w(V) * OPT + + Notes + ----- + This algorithm computes an approximate minimum weighted dominating set + for the graph G. The upper-bound on the size of the solution is + log w(V) * OPT. Runtime of the algorithm is `O(|E|)`. + + References + ---------- + .. [1] Vazirani, Vijay Approximation Algorithms (2001) + """ + if not G: + raise ValueError("Expected non-empty NetworkX graph!") + + # min cover = min dominating set + dom_set = set([]) + cost_func = dict((node, nd.get(weight, 1)) \ + for node, nd in G.nodes_iter(data=True)) + + vertices = set(G) + sets = dict((node, set([node]) | set(G[node])) for node in G) + + def _cost(subset): + """ Our cost effectiveness function for sets given its weight + """ + cost = sum(cost_func[node] for node in subset) + return cost / float(len(subset - dom_set)) + + while vertices: + # find the most cost effective set, and the vertex that for that set + dom_node, min_set = min(sets.items(), + key=lambda x: (x[0], _cost(x[1]))) + alpha = _cost(min_set) + + # reduce the cost for the rest + for node in min_set - dom_set: + cost_func[node] = alpha + + # add the node to the dominating set and reduce what we must cover + dom_set.add(dom_node) + del sets[dom_node] + vertices = vertices - min_set + + return dom_set + + +def min_edge_dominating_set(G): + r"""Return minimum cardinality edge dominating set. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + min_edge_dominating_set : set + Returns a set of dominating edges whose size is no more than 2 * OPT. + + Notes + ----- + The algorithm computes an approximate solution to the edge dominating set + problem. The result is no more than 2 * OPT in terms of size of the set. + Runtime of the algorithm is `O(|E|)`. + """ + if not G: + raise ValueError("Expected non-empty NetworkX graph!") + return nx.maximal_matching(G) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/independent_set.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/independent_set.py new file mode 100644 index 0000000..3b18ade --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/independent_set.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" +Independent Set + +Independent set or stable set is a set of vertices in a graph, no two of +which are adjacent. That is, it is a set I of vertices such that for every +two vertices in I, there is no edge connecting the two. Equivalently, each +edge in the graph has at most one endpoint in I. The size of an independent +set is the number of vertices it contains. + +A maximum independent set is a largest independent set for a given graph G +and its size is denoted α(G). The problem of finding such a set is called +the maximum independent set problem and is an NP-hard optimization problem. +As such, it is unlikely that there exists an efficient algorithm for finding +a maximum independent set of a graph. + +http://en.wikipedia.org/wiki/Independent_set_(graph_theory) + +Independent set algorithm is based on the following paper: + +`O(|V|/(log|V|)^2)` apx of maximum clique/independent set. + +Boppana, R., & Halldórsson, M. M. (1992). +Approximating maximum independent sets by excluding subgraphs. +BIT Numerical Mathematics, 32(2), 180–196. Springer. +doi:10.1007/BF01994876 + +""" +# Copyright (C) 2011-2012 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +from networkx.algorithms.approximation import clique_removal +__all__ = ["maximum_independent_set"] +__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)""" + + +def maximum_independent_set(G): + """Return an approximate maximum independent set. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + iset : Set + The apx-maximum independent set + + Notes + ----- + Finds the `O(|V|/(log|V|)^2)` apx of independent set in the worst case. + + + References + ---------- + .. [1] Boppana, R., & Halldórsson, M. M. (1992). + Approximating maximum independent sets by excluding subgraphs. + BIT Numerical Mathematics, 32(2), 180–196. Springer. + """ + iset, _ = clique_removal(G) + return iset diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/matching.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/matching.py new file mode 100644 index 0000000..231d501 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/matching.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" +************** +Graph Matching +************** + +Given a graph G = (V,E), a matching M in G is a set of pairwise non-adjacent +edges; that is, no two edges share a common vertex. + +http://en.wikipedia.org/wiki/Matching_(graph_theory) +""" +# Copyright (C) 2011-2012 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +import networkx as nx +__all__ = ["min_maximal_matching"] +__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)""" + +def min_maximal_matching(G): + r"""Returns the minimum maximal matching of G. That is, out of all maximal + matchings of the graph G, the smallest is returned. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + min_maximal_matching : set + Returns a set of edges such that no two edges share a common endpoint + and every edge not in the set shares some common endpoint in the set. + Cardinality will be 2*OPT in the worst case. + + Notes + ----- + The algorithm computes an approximate solution fo the minimum maximal + cardinality matching problem. The solution is no more than 2 * OPT in size. + Runtime is `O(|E|)`. + + References + ---------- + .. [1] Vazirani, Vijay Approximation Algorithms (2001) + """ + return nx.maximal_matching(G) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/ramsey.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/ramsey.py new file mode 100644 index 0000000..03535ce --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/ramsey.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" +Ramsey numbers. +""" +# Copyright (C) 2011 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +import networkx as nx +__all__ = ["ramsey_R2"] +__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)""" + +def ramsey_R2(G): + r"""Approximately computes the Ramsey number `R(2;s,t)` for graph. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + max_pair : (set, set) tuple + Maximum clique, Maximum independent set. + """ + if not G: + return (set([]), set([])) + + node = next(G.nodes_iter()) + nbrs = nx.all_neighbors(G, node) + nnbrs = nx.non_neighbors(G, node) + c_1, i_1 = ramsey_R2(G.subgraph(nbrs)) + c_2, i_2 = ramsey_R2(G.subgraph(nnbrs)) + + c_1.add(node) + i_2.add(node) + return (max([c_1, c_2]), max([i_1, i_2])) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_clique.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_clique.py new file mode 100644 index 0000000..0f384a5 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_clique.py @@ -0,0 +1,41 @@ +from nose.tools import * +import networkx as nx +import networkx.algorithms.approximation as apxa + +def test_clique_removal(): + graph = nx.complete_graph(10) + i, cs = apxa.clique_removal(graph) + idens = nx.density(graph.subgraph(i)) + eq_(idens, 0.0, "i-set not found by clique_removal!") + for clique in cs: + cdens = nx.density(graph.subgraph(clique)) + eq_(cdens, 1.0, "clique not found by clique_removal!") + + graph = nx.trivial_graph(nx.Graph()) + i, cs = apxa.clique_removal(graph) + idens = nx.density(graph.subgraph(i)) + eq_(idens, 0.0, "i-set not found by ramsey!") + # we should only have 1-cliques. Just singleton nodes. + for clique in cs: + cdens = nx.density(graph.subgraph(clique)) + eq_(cdens, 0.0, "clique not found by clique_removal!") + + graph = nx.barbell_graph(10, 5, nx.Graph()) + i, cs = apxa.clique_removal(graph) + idens = nx.density(graph.subgraph(i)) + eq_(idens, 0.0, "i-set not found by ramsey!") + for clique in cs: + cdens = nx.density(graph.subgraph(clique)) + eq_(cdens, 1.0, "clique not found by clique_removal!") + +def test_max_clique_smoke(): + # smoke test + G = nx.Graph() + assert_equal(len(apxa.max_clique(G)),0) + +def test_max_clique(): + # create a complete graph + graph = nx.complete_graph(30) + # this should return the entire graph + mc = apxa.max_clique(graph) + assert_equals(30, len(mc)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_dominating_set.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_dominating_set.py new file mode 100644 index 0000000..0dbc79f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_dominating_set.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +import networkx.algorithms.approximation as apxa + + +class TestMinWeightDominatingSet: + + def test_min_weighted_dominating_set(self): + graph = nx.Graph() + graph.add_edge(1, 2) + graph.add_edge(1, 5) + graph.add_edge(2, 3) + graph.add_edge(2, 5) + graph.add_edge(3, 4) + graph.add_edge(3, 6) + graph.add_edge(5, 6) + + vertices = set([1, 2, 3, 4, 5, 6]) + # due to ties, this might be hard to test tight bounds + dom_set = apxa.min_weighted_dominating_set(graph) + for vertex in vertices - dom_set: + neighbors = set(graph.neighbors(vertex)) + ok_(len(neighbors & dom_set) > 0, "Non dominating set found!") + + def test_min_edge_dominating_set(self): + graph = nx.path_graph(5) + dom_set = apxa.min_edge_dominating_set(graph) + + # this is a crappy way to test, but good enough for now. + for edge in graph.edges_iter(): + if edge in dom_set: + continue + else: + u, v = edge + found = False + for dom_edge in dom_set: + found |= u == dom_edge[0] or u == dom_edge[1] + ok_(found, "Non adjacent edge found!") + + graph = nx.complete_graph(10) + dom_set = apxa.min_edge_dominating_set(graph) + + # this is a crappy way to test, but good enough for now. + for edge in graph.edges_iter(): + if edge in dom_set: + continue + else: + u, v = edge + found = False + for dom_edge in dom_set: + found |= u == dom_edge[0] or u == dom_edge[1] + ok_(found, "Non adjacent edge found!") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_independent_set.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_independent_set.py new file mode 100644 index 0000000..8825ec8 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_independent_set.py @@ -0,0 +1,8 @@ +from nose.tools import * +import networkx as nx +import networkx.algorithms.approximation as a + +def test_independent_set(): + # smoke test + G = nx.Graph() + assert_equal(len(a.maximum_independent_set(G)),0) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_matching.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_matching.py new file mode 100644 index 0000000..b768c39 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_matching.py @@ -0,0 +1,8 @@ +from nose.tools import * +import networkx as nx +import networkx.algorithms.approximation as a + +def test_min_maximal_matching(): + # smoke test + G = nx.Graph() + assert_equal(len(a.min_maximal_matching(G)),0) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_ramsey.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_ramsey.py new file mode 100644 index 0000000..7ab8dac --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_ramsey.py @@ -0,0 +1,27 @@ +from nose.tools import * +import networkx as nx +import networkx.algorithms.approximation as apxa + +def test_ramsey(): + # this should only find the complete graph + graph = nx.complete_graph(10) + c, i = apxa.ramsey_R2(graph) + cdens = nx.density(graph.subgraph(c)) + eq_(cdens, 1.0, "clique not found by ramsey!") + idens = nx.density(graph.subgraph(i)) + eq_(idens, 0.0, "i-set not found by ramsey!") + + # this trival graph has no cliques. should just find i-sets + graph = nx.trivial_graph(nx.Graph()) + c, i = apxa.ramsey_R2(graph) + cdens = nx.density(graph.subgraph(c)) + eq_(cdens, 0.0, "clique not found by ramsey!") + idens = nx.density(graph.subgraph(i)) + eq_(idens, 0.0, "i-set not found by ramsey!") + + graph = nx.barbell_graph(10, 5, nx.Graph()) + c, i = apxa.ramsey_R2(graph) + cdens = nx.density(graph.subgraph(c)) + eq_(cdens, 1.0, "clique not found by ramsey!") + idens = nx.density(graph.subgraph(i)) + eq_(idens, 0.0, "i-set not found by ramsey!") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_vertex_cover.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_vertex_cover.py new file mode 100644 index 0000000..74b3f51 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/tests/test_vertex_cover.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx.algorithms import approximation as a + +class TestMWVC: + + def test_min_vertex_cover(self): + # create a simple star graph + size = 50 + sg = nx.star_graph(size) + cover = a.min_weighted_vertex_cover(sg) + assert_equals(2, len(cover)) + for u, v in sg.edges_iter(): + ok_((u in cover or v in cover), "Node node covered!") + + wg = nx.Graph() + wg.add_node(0, weight=10) + wg.add_node(1, weight=1) + wg.add_node(2, weight=1) + wg.add_node(3, weight=1) + wg.add_node(4, weight=1) + + wg.add_edge(0, 1) + wg.add_edge(0, 2) + wg.add_edge(0, 3) + wg.add_edge(0, 4) + + wg.add_edge(1,2) + wg.add_edge(2,3) + wg.add_edge(3,4) + wg.add_edge(4,1) + + cover = a.min_weighted_vertex_cover(wg, weight="weight") + csum = sum(wg.node[node]["weight"] for node in cover) + assert_equals(4, csum) + + for u, v in wg.edges_iter(): + ok_((u in cover or v in cover), "Node node covered!") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/vertex_cover.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/vertex_cover.py new file mode 100644 index 0000000..c588e18 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/approximation/vertex_cover.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +""" +************ +Vertex Cover +************ + +Given an undirected graph `G = (V, E)` and a function w assigning nonnegative +weights to its vertices, find a minimum weight subset of V such that each edge +in E is incident to at least one vertex in the subset. + +http://en.wikipedia.org/wiki/Vertex_cover +""" +# Copyright (C) 2011-2012 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +from networkx.utils import * +__all__ = ["min_weighted_vertex_cover"] +__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)""" + +@not_implemented_for('directed') +def min_weighted_vertex_cover(G, weight=None): + r"""2-OPT Local Ratio for Minimum Weighted Vertex Cover + + Find an approximate minimum weighted vertex cover of a graph. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + weight : None or string, optional (default = None) + If None, every edge has weight/distance/cost 1. If a string, use this + edge attribute as the edge weight. Any edge attribute not present + defaults to 1. + + Returns + ------- + min_weighted_cover : set + Returns a set of vertices whose weight sum is no more than 2 * OPT. + + Notes + ----- + Local-Ratio algorithm for computing an approximate vertex cover. + Algorithm greedily reduces the costs over edges and iteratively + builds a cover. Worst-case runtime is `O(|E|)`. + + References + ---------- + .. [1] Bar-Yehuda, R., & Even, S. (1985). A local-ratio theorem for + approximating the weighted vertex cover problem. + Annals of Discrete Mathematics, 25, 27–46 + http://www.cs.technion.ac.il/~reuven/PDF/vc_lr.pdf + """ + weight_func = lambda nd: nd.get(weight, 1) + cost = dict((n, weight_func(nd)) for n, nd in G.nodes(data=True)) + + # while there are edges uncovered, continue + for u,v in G.edges_iter(): + # select some uncovered edge + min_cost = min([cost[u], cost[v]]) + cost[u] -= min_cost + cost[v] -= min_cost + + return set(u for u in cost if cost[u] == 0) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/__init__.py new file mode 100644 index 0000000..4d98886 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/__init__.py @@ -0,0 +1,5 @@ +from networkx.algorithms.assortativity.connectivity import * +from networkx.algorithms.assortativity.correlation import * +from networkx.algorithms.assortativity.mixing import * +from networkx.algorithms.assortativity.neighbor_degree import * +from networkx.algorithms.assortativity.pairs import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/connectivity.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/connectivity.py new file mode 100644 index 0000000..17b0265 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/connectivity.py @@ -0,0 +1,123 @@ +#-*- coding: utf-8 -*- +# Copyright (C) 2011 by +# Jordi Torrents +# Aric Hagberg +# All rights reserved. +# BSD license. +from collections import defaultdict +import networkx as nx +__author__ = """\n""".join(['Jordi Torrents ', + 'Aric Hagberg (hagberg@lanl.gov)']) +__all__ = ['average_degree_connectivity', + 'k_nearest_neighbors'] + +def _avg_deg_conn(G, neighbors, source_degree, target_degree, + nodes=None, weight=None): + # "k nearest neighbors, or neighbor_connectivity + dsum = defaultdict(float) + dnorm = defaultdict(float) + for n,k in source_degree(nodes).items(): + nbrdeg = target_degree(neighbors(n)) + if weight is None: + s = float(sum(nbrdeg.values())) + else: # weight nbr degree by weight of (n,nbr) edge + if neighbors == G.neighbors: + s = float(sum((G[n][nbr].get(weight,1)*d + for nbr,d in nbrdeg.items()))) + elif neighbors == G.successors: + s = float(sum((G[n][nbr].get(weight,1)*d + for nbr,d in nbrdeg.items()))) + elif neighbors == G.predecessors: + s = float(sum((G[nbr][n].get(weight,1)*d + for nbr,d in nbrdeg.items()))) + dnorm[k] += source_degree(n, weight=weight) + dsum[k] += s + + # normalize + dc = {} + for k,avg in dsum.items(): + dc[k]=avg + norm = dnorm[k] + if avg > 0 and norm > 0: + dc[k]/=norm + return dc + +def average_degree_connectivity(G, source="in+out", target="in+out", + nodes=None, weight=None): + r"""Compute the average degree connectivity of graph. + + The average degree connectivity is the average nearest neighbor degree of + nodes with degree k. For weighted graphs, an analogous measure can + be computed using the weighted average neighbors degree defined in + [1]_, for a node `i`, as: + + .. math:: + + k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j + + where `s_i` is the weighted degree of node `i`, + `w_{ij}` is the weight of the edge that links `i` and `j`, + and `N(i)` are the neighbors of node `i`. + + Parameters + ---------- + G : NetworkX graph + + source : "in"|"out"|"in+out" (default:"in+out") + Directed graphs only. Use "in"- or "out"-degree for source node. + + target : "in"|"out"|"in+out" (default:"in+out" + Directed graphs only. Use "in"- or "out"-degree for target node. + + nodes: list or iterable (optional) + Compute neighbor connectivity for these nodes. The default is all nodes. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used as a weight. + If None, then each edge has weight 1. + + Returns + ------- + d: dict + A dictionary keyed by degree k with the value of average connectivity. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> G.edge[1][2]['weight'] = 3 + >>> nx.k_nearest_neighbors(G) + {1: 2.0, 2: 1.5} + >>> nx.k_nearest_neighbors(G, weight='weight') + {1: 2.0, 2: 1.75} + + See also + -------- + neighbors_average_degree + + Notes + ----- + This algorithm is sometimes called "k nearest neighbors'. + + References + ---------- + .. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, + "The architecture of complex weighted networks". + PNAS 101 (11): 3747–3752 (2004). + """ + source_degree = G.degree + target_degree = G.degree + neighbors = G.neighbors + if G.is_directed(): + direction = {'out':G.out_degree, + 'in':G.in_degree, + 'in+out': G.degree} + source_degree = direction[source] + target_degree = direction[target] + if source == 'in': + neighbors=G.predecessors + elif source == 'out': + neighbors=G.successors + return _avg_deg_conn(G, neighbors, source_degree, target_degree, + nodes=nodes, weight=weight) + +k_nearest_neighbors=average_degree_connectivity diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/correlation.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/correlation.py new file mode 100644 index 0000000..6d471c9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/correlation.py @@ -0,0 +1,298 @@ +#-*- coding: utf-8 -*- +"""Node assortativity coefficients and correlation measures. +""" +import networkx as nx +from networkx.algorithms.assortativity.mixing import degree_mixing_matrix, \ + attribute_mixing_matrix, numeric_mixing_matrix +from networkx.algorithms.assortativity.pairs import node_degree_xy, \ + node_attribute_xy +__author__ = ' '.join(['Aric Hagberg ', + 'Oleguer Sagarra ']) +__all__ = ['degree_pearson_correlation_coefficient', + 'degree_assortativity_coefficient', + 'attribute_assortativity_coefficient', + 'numeric_assortativity_coefficient'] + +def degree_assortativity_coefficient(G, x='out', y='in', weight=None, + nodes=None): + """Compute degree assortativity of graph. + + Assortativity measures the similarity of connections + in the graph with respect to the node degree. + + Parameters + ---------- + G : NetworkX graph + + x: string ('in','out') + The degree type for source node (directed graphs only). + + y: string ('in','out') + The degree type for target node (directed graphs only). + + weight: string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + nodes: list or iterable (optional) + Compute degree assortativity only for nodes in container. + The default is all nodes. + + Returns + ------- + r : float + Assortativity of graph by degree. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> r=nx.degree_assortativity_coefficient(G) + >>> print("%3.1f"%r) + -0.5 + + See Also + -------- + attribute_assortativity_coefficient + numeric_assortativity_coefficient + neighbor_connectivity + degree_mixing_dict + degree_mixing_matrix + + Notes + ----- + This computes Eq. (21) in Ref. [1]_ , where e is the joint + probability distribution (mixing matrix) of the degrees. If G is + directed than the matrix e is the joint probability of the + user-specified degree type for the source and target. + + References + ---------- + .. [1] M. E. J. Newman, Mixing patterns in networks, + Physical Review E, 67 026126, 2003 + .. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M. + Edge direction and the structure of networks, PNAS 107, 10815-20 (2010). + """ + M = degree_mixing_matrix(G, x=x, y=y, nodes=nodes, weight=weight) + return numeric_ac(M) + + +def degree_pearson_correlation_coefficient(G, x='out', y='in', + weight=None, nodes=None): + """Compute degree assortativity of graph. + + Assortativity measures the similarity of connections + in the graph with respect to the node degree. + + This is the same as degree_assortativity_coefficient but uses the + potentially faster scipy.stats.pearsonr function. + + Parameters + ---------- + G : NetworkX graph + + x: string ('in','out') + The degree type for source node (directed graphs only). + + y: string ('in','out') + The degree type for target node (directed graphs only). + + weight: string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + nodes: list or iterable (optional) + Compute pearson correlation of degrees only for specified nodes. + The default is all nodes. + + Returns + ------- + r : float + Assortativity of graph by degree. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> r=nx.degree_pearson_correlation_coefficient(G) + >>> r + -0.5 + + Notes + ----- + This calls scipy.stats.pearsonr. + + References + ---------- + .. [1] M. E. J. Newman, Mixing patterns in networks + Physical Review E, 67 026126, 2003 + .. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M. + Edge direction and the structure of networks, PNAS 107, 10815-20 (2010). + """ + try: + import scipy.stats as stats + except ImportError: + raise ImportError( + "Assortativity requires SciPy: http://scipy.org/ ") + xy=node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight) + x,y=zip(*xy) + return stats.pearsonr(x,y)[0] + + +def attribute_assortativity_coefficient(G,attribute,nodes=None): + """Compute assortativity for node attributes. + + Assortativity measures the similarity of connections + in the graph with respect to the given attribute. + + Parameters + ---------- + G : NetworkX graph + + attribute : string + Node attribute key + + nodes: list or iterable (optional) + Compute attribute assortativity for nodes in container. + The default is all nodes. + + Returns + ------- + r: float + Assortativity of graph for given attribute + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_nodes_from([0,1],color='red') + >>> G.add_nodes_from([2,3],color='blue') + >>> G.add_edges_from([(0,1),(2,3)]) + >>> print(nx.attribute_assortativity_coefficient(G,'color')) + 1.0 + + Notes + ----- + This computes Eq. (2) in Ref. [1]_ , trace(M)-sum(M))/(1-sum(M), + where M is the joint probability distribution (mixing matrix) + of the specified attribute. + + References + ---------- + .. [1] M. E. J. Newman, Mixing patterns in networks, + Physical Review E, 67 026126, 2003 + """ + M = attribute_mixing_matrix(G,attribute,nodes) + return attribute_ac(M) + + +def numeric_assortativity_coefficient(G, attribute, nodes=None): + """Compute assortativity for numerical node attributes. + + Assortativity measures the similarity of connections + in the graph with respect to the given numeric attribute. + + Parameters + ---------- + G : NetworkX graph + + attribute : string + Node attribute key + + nodes: list or iterable (optional) + Compute numeric assortativity only for attributes of nodes in + container. The default is all nodes. + + Returns + ------- + r: float + Assortativity of graph for given attribute + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_nodes_from([0,1],size=2) + >>> G.add_nodes_from([2,3],size=3) + >>> G.add_edges_from([(0,1),(2,3)]) + >>> print(nx.numeric_assortativity_coefficient(G,'size')) + 1.0 + + Notes + ----- + This computes Eq. (21) in Ref. [1]_ , for the mixing matrix of + of the specified attribute. + + References + ---------- + .. [1] M. E. J. Newman, Mixing patterns in networks + Physical Review E, 67 026126, 2003 + """ + a = numeric_mixing_matrix(G,attribute,nodes) + return numeric_ac(a) + + +def attribute_ac(M): + """Compute assortativity for attribute matrix M. + + Parameters + ---------- + M : numpy array or matrix + Attribute mixing matrix. + + Notes + ----- + This computes Eq. (2) in Ref. [1]_ , (trace(e)-sum(e))/(1-sum(e)), + where e is the joint probability distribution (mixing matrix) + of the specified attribute. + + References + ---------- + .. [1] M. E. J. Newman, Mixing patterns in networks, + Physical Review E, 67 026126, 2003 + """ + try: + import numpy + except ImportError: + raise ImportError( + "attribute_assortativity requires NumPy: http://scipy.org/ ") + if M.sum() != 1.0: + M=M/float(M.sum()) + M=numpy.asmatrix(M) + s=(M*M).sum() + t=M.trace() + r=(t-s)/(1-s) + return float(r) + + +def numeric_ac(M): + # M is a numpy matrix or array + # numeric assortativity coefficient, pearsonr + try: + import numpy + except ImportError: + raise ImportError('numeric_assortativity requires ', + 'NumPy: http://scipy.org/') + if M.sum() != 1.0: + M=M/float(M.sum()) + nx,ny=M.shape # nx=ny + x=numpy.arange(nx) + y=numpy.arange(ny) + a=M.sum(axis=0) + b=M.sum(axis=1) + vara=(a*x**2).sum()-((a*x).sum())**2 + varb=(b*x**2).sum()-((b*x).sum())**2 + xy=numpy.outer(x,y) + ab=numpy.outer(a,b) + return (xy*(M-ab)).sum()/numpy.sqrt(vara*varb) + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/mixing.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/mixing.py new file mode 100644 index 0000000..2c0e4f0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/mixing.py @@ -0,0 +1,248 @@ +#-*- coding: utf-8 -*- +""" +Mixing matrices for node attributes and degree. +""" +import networkx as nx +from networkx.utils import dict_to_numpy_array +from networkx.algorithms.assortativity.pairs import node_degree_xy, \ + node_attribute_xy +__author__ = ' '.join(['Aric Hagberg ']) +__all__ = ['attribute_mixing_matrix', + 'attribute_mixing_dict', + 'degree_mixing_matrix', + 'degree_mixing_dict', + 'numeric_mixing_matrix', + 'mixing_dict'] + +def attribute_mixing_dict(G,attribute,nodes=None,normalized=False): + """Return dictionary representation of mixing matrix for attribute. + + Parameters + ---------- + G : graph + NetworkX graph object. + + attribute : string + Node attribute key. + + nodes: list or iterable (optional) + Unse nodes in container to build the dict. The default is all nodes. + + normalized : bool (default=False) + Return counts if False or probabilities if True. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_nodes_from([0,1],color='red') + >>> G.add_nodes_from([2,3],color='blue') + >>> G.add_edge(1,3) + >>> d=nx.attribute_mixing_dict(G,'color') + >>> print(d['red']['blue']) + 1 + >>> print(d['blue']['red']) # d symmetric for undirected graphs + 1 + + Returns + ------- + d : dictionary + Counts or joint probability of occurrence of attribute pairs. + """ + xy_iter=node_attribute_xy(G,attribute,nodes) + return mixing_dict(xy_iter,normalized=normalized) + + +def attribute_mixing_matrix(G,attribute,nodes=None,mapping=None, + normalized=True): + """Return mixing matrix for attribute. + + Parameters + ---------- + G : graph + NetworkX graph object. + + attribute : string + Node attribute key. + + nodes: list or iterable (optional) + Use only nodes in container to build the matrix. The default is + all nodes. + + mapping : dictionary, optional + Mapping from node attribute to integer index in matrix. + If not specified, an arbitrary ordering will be used. + + normalized : bool (default=False) + Return counts if False or probabilities if True. + + Returns + ------- + m: numpy array + Counts or joint probability of occurrence of attribute pairs. + """ + d=attribute_mixing_dict(G,attribute,nodes) + a=dict_to_numpy_array(d,mapping=mapping) + if normalized: + a=a/a.sum() + return a + + +def degree_mixing_dict(G, x='out', y='in', weight=None, + nodes=None, normalized=False): + """Return dictionary representation of mixing matrix for degree. + + Parameters + ---------- + G : graph + NetworkX graph object. + + x: string ('in','out') + The degree type for source node (directed graphs only). + + y: string ('in','out') + The degree type for target node (directed graphs only). + + weight: string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + normalized : bool (default=False) + Return counts if False or probabilities if True. + + Returns + ------- + d: dictionary + Counts or joint probability of occurrence of degree pairs. + """ + xy_iter=node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight) + return mixing_dict(xy_iter,normalized=normalized) + + + +def degree_mixing_matrix(G, x='out', y='in', weight=None, + nodes=None, normalized=True): + """Return mixing matrix for attribute. + + Parameters + ---------- + G : graph + NetworkX graph object. + + x: string ('in','out') + The degree type for source node (directed graphs only). + + y: string ('in','out') + The degree type for target node (directed graphs only). + + nodes: list or iterable (optional) + Build the matrix using only nodes in container. + The default is all nodes. + + weight: string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + normalized : bool (default=False) + Return counts if False or probabilities if True. + + Returns + ------- + m: numpy array + Counts, or joint probability, of occurrence of node degree. + """ + d=degree_mixing_dict(G, x=x, y=y, nodes=nodes, weight=weight) + s=set(d.keys()) + for k,v in d.items(): + s.update(v.keys()) + m=max(s) + mapping=dict(zip(range(m+1),range(m+1))) + a=dict_to_numpy_array(d,mapping=mapping) + if normalized: + a=a/a.sum() + return a + +def numeric_mixing_matrix(G,attribute,nodes=None,normalized=True): + """Return numeric mixing matrix for attribute. + + Parameters + ---------- + G : graph + NetworkX graph object. + + attribute : string + Node attribute key. + + nodes: list or iterable (optional) + Build the matrix only with nodes in container. The default is all nodes. + + normalized : bool (default=False) + Return counts if False or probabilities if True. + + Returns + ------- + m: numpy array + Counts, or joint, probability of occurrence of node attribute pairs. + """ + d=attribute_mixing_dict(G,attribute,nodes) + s=set(d.keys()) + for k,v in d.items(): + s.update(v.keys()) + m=max(s) + mapping=dict(zip(range(m+1),range(m+1))) + a=dict_to_numpy_array(d,mapping=mapping) + if normalized: + a=a/a.sum() + return a + +def mixing_dict(xy,normalized=False): + """Return a dictionary representation of mixing matrix. + + Parameters + ---------- + xy : list or container of two-tuples + Pairs of (x,y) items. + + attribute : string + Node attribute key + + normalized : bool (default=False) + Return counts if False or probabilities if True. + + Returns + ------- + d: dictionary + Counts or Joint probability of occurrence of values in xy. + """ + d={} + psum=0.0 + for x,y in xy: + if x not in d: + d[x]={} + if y not in d: + d[y]={} + v = d[x].get(y,0) + d[x][y] = v+1 + psum+=1 + + + if normalized: + for k,jdict in d.items(): + for j in jdict: + jdict[j]/=psum + return d + + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/neighbor_degree.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/neighbor_degree.py new file mode 100644 index 0000000..9257954 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/neighbor_degree.py @@ -0,0 +1,133 @@ +#-*- coding: utf-8 -*- +# Copyright (C) 2011 by +# Jordi Torrents +# Aric Hagberg +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Jordi Torrents ', + 'Aric Hagberg (hagberg@lanl.gov)']) +__all__ = ["average_neighbor_degree"] + + +def _average_nbr_deg(G, source_degree, target_degree, nodes=None, weight=None): + # average degree of neighbors + avg = {} + for n,deg in source_degree(nodes,weight=weight).items(): + # normalize but not by zero degree + if deg == 0: + deg = 1 + nbrdeg = target_degree(G[n]) + if weight is None: + avg[n] = sum(nbrdeg.values())/float(deg) + else: + avg[n] = sum((G[n][nbr].get(weight,1)*d + for nbr,d in nbrdeg.items()))/float(deg) + return avg + +def average_neighbor_degree(G, source='out', target='out', + nodes=None, weight=None): + r"""Returns the average degree of the neighborhood of each node. + + The average degree of a node `i` is + + .. math:: + + k_{nn,i} = \frac{1}{|N(i)|} \sum_{j \in N(i)} k_j + + where `N(i)` are the neighbors of node `i` and `k_j` is + the degree of node `j` which belongs to `N(i)`. For weighted + graphs, an analogous measure can be defined [1]_, + + .. math:: + + k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j + + where `s_i` is the weighted degree of node `i`, `w_{ij}` + is the weight of the edge that links `i` and `j` and + `N(i)` are the neighbors of node `i`. + + + Parameters + ---------- + G : NetworkX graph + + source : string ("in"|"out") + Directed graphs only. + Use "in"- or "out"-degree for source node. + + target : string ("in"|"out") + Directed graphs only. + Use "in"- or "out"-degree for target node. + + nodes : list or iterable, optional + Compute neighbor degree for specified nodes. The default is + all nodes in the graph. + + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used as a weight. + If None, then each edge has weight 1. + + Returns + ------- + d: dict + A dictionary keyed by node with average neighbors degree value. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> G.edge[0][1]['weight'] = 5 + >>> G.edge[2][3]['weight'] = 3 + + >>> nx.average_neighbor_degree(G) + {0: 2.0, 1: 1.5, 2: 1.5, 3: 2.0} + >>> nx.average_neighbor_degree(G, weight='weight') + {0: 2.0, 1: 1.1666666666666667, 2: 1.25, 3: 2.0} + + >>> G=nx.DiGraph() + >>> G.add_path([0,1,2,3]) + >>> nx.average_neighbor_degree(G, source='in', target='in') + {0: 1.0, 1: 1.0, 2: 1.0, 3: 0.0} + + >>> nx.average_neighbor_degree(G, source='out', target='out') + {0: 1.0, 1: 1.0, 2: 0.0, 3: 0.0} + + Notes + ----- + For directed graphs you can also specify in-degree or out-degree + by passing keyword arguments. + + See Also + -------- + average_degree_connectivity + + References + ---------- + .. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, + "The architecture of complex weighted networks". + PNAS 101 (11): 3747–3752 (2004). + """ + source_degree = G.degree + target_degree = G.degree + if G.is_directed(): + direction = {'out':G.out_degree, + 'in':G.in_degree} + source_degree = direction[source] + target_degree = direction[target] + return _average_nbr_deg(G, source_degree, target_degree, + nodes=nodes, weight=weight) + +# obsolete +# def average_neighbor_in_degree(G, nodes=None, weight=None): +# if not G.is_directed(): +# raise nx.NetworkXError("Not defined for undirected graphs.") +# return _average_nbr_deg(G, G.in_degree, G.in_degree, nodes, weight) +# average_neighbor_in_degree.__doc__=average_neighbor_degree.__doc__ + +# def average_neighbor_out_degree(G, nodes=None, weight=None): +# if not G.is_directed(): +# raise nx.NetworkXError("Not defined for undirected graphs.") +# return _average_nbr_deg(G, G.out_degree, G.out_degree, nodes, weight) +# average_neighbor_out_degree.__doc__=average_neighbor_degree.__doc__ + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/pairs.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/pairs.py new file mode 100644 index 0000000..0ed0fa9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/pairs.py @@ -0,0 +1,134 @@ +#-*- coding: utf-8 -*- +"""Generators of x-y pairs of node data.""" +import networkx as nx +from networkx.utils import dict_to_numpy_array +__author__ = ' '.join(['Aric Hagberg ']) +__all__ = ['node_attribute_xy', + 'node_degree_xy'] + +def node_attribute_xy(G, attribute, nodes=None): + """Return iterator of node-attribute pairs for all edges in G. + + Parameters + ---------- + G: NetworkX graph + + attribute: key + The node attribute key. + + nodes: list or iterable (optional) + Use only edges that are adjacency to specified nodes. + The default is all nodes. + + Returns + ------- + (x,y): 2-tuple + Generates 2-tuple of (attribute,attribute) values. + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_node(1,color='red') + >>> G.add_node(2,color='blue') + >>> G.add_edge(1,2) + >>> list(nx.node_attribute_xy(G,'color')) + [('red', 'blue')] + + Notes + ----- + For undirected graphs each edge is produced twice, once for each edge + representation (u,v) and (v,u), with the exception of self-loop edges + which only appear once. + """ + if nodes is None: + nodes = set(G) + else: + nodes = set(nodes) + node = G.node + for u,nbrsdict in G.adjacency_iter(): + if u not in nodes: + continue + uattr = node[u].get(attribute,None) + if G.is_multigraph(): + for v,keys in nbrsdict.items(): + vattr = node[v].get(attribute,None) + for k,d in keys.items(): + yield (uattr,vattr) + else: + for v,eattr in nbrsdict.items(): + vattr = node[v].get(attribute,None) + yield (uattr,vattr) + + +def node_degree_xy(G, x='out', y='in', weight=None, nodes=None): + """Generate node degree-degree pairs for edges in G. + + Parameters + ---------- + G: NetworkX graph + + x: string ('in','out') + The degree type for source node (directed graphs only). + + y: string ('in','out') + The degree type for target node (directed graphs only). + + weight: string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + nodes: list or iterable (optional) + Use only edges that are adjacency to specified nodes. + The default is all nodes. + + Returns + ------- + (x,y): 2-tuple + Generates 2-tuple of (degree,degree) values. + + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_edge(1,2) + >>> list(nx.node_degree_xy(G,x='out',y='in')) + [(1, 1)] + >>> list(nx.node_degree_xy(G,x='in',y='out')) + [(0, 0)] + + Notes + ----- + For undirected graphs each edge is produced twice, once for each edge + representation (u,v) and (v,u), with the exception of self-loop edges + which only appear once. + """ + if nodes is None: + nodes = set(G) + else: + nodes = set(nodes) + xdeg = G.degree_iter + ydeg = G.degree_iter + if G.is_directed(): + direction = {'out':G.out_degree_iter, + 'in':G.in_degree_iter} + xdeg = direction[x] + ydeg = direction[y] + + for u,degu in xdeg(nodes, weight=weight): + neighbors = (nbr for _,nbr in G.edges_iter(u) if nbr in nodes) + for v,degv in ydeg(neighbors, weight=weight): + yield degu,degv + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/base_test.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/base_test.py new file mode 100644 index 0000000..2e16544 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/base_test.py @@ -0,0 +1,50 @@ +import networkx as nx + +class BaseTestAttributeMixing(object): + + def setUp(self): + G=nx.Graph() + G.add_nodes_from([0,1],fish='one') + G.add_nodes_from([2,3],fish='two') + G.add_nodes_from([4],fish='red') + G.add_nodes_from([5],fish='blue') + G.add_edges_from([(0,1),(2,3),(0,4),(2,5)]) + self.G=G + + D=nx.DiGraph() + D.add_nodes_from([0,1],fish='one') + D.add_nodes_from([2,3],fish='two') + D.add_nodes_from([4],fish='red') + D.add_nodes_from([5],fish='blue') + D.add_edges_from([(0,1),(2,3),(0,4),(2,5)]) + self.D=D + + M=nx.MultiGraph() + M.add_nodes_from([0,1],fish='one') + M.add_nodes_from([2,3],fish='two') + M.add_nodes_from([4],fish='red') + M.add_nodes_from([5],fish='blue') + M.add_edges_from([(0,1),(0,1),(2,3)]) + self.M=M + + S=nx.Graph() + S.add_nodes_from([0,1],fish='one') + S.add_nodes_from([2,3],fish='two') + S.add_nodes_from([4],fish='red') + S.add_nodes_from([5],fish='blue') + S.add_edge(0,0) + S.add_edge(2,2) + self.S=S + +class BaseTestDegreeMixing(object): + + def setUp(self): + self.P4=nx.path_graph(4) + self.D=nx.DiGraph() + self.D.add_edges_from([(0, 2), (0, 3), (1, 3), (2, 3)]) + self.M=nx.MultiGraph() + self.M.add_path(list(range(4))) + self.M.add_edge(0,1) + self.S=nx.Graph() + self.S.add_edges_from([(0,0),(1,1)]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_connectivity.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_connectivity.py new file mode 100644 index 0000000..5091161 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_connectivity.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestNeighborConnectivity(object): + + def test_degree_p4(self): + G=nx.path_graph(4) + answer={1:2.0,2:1.5} + nd = nx.average_degree_connectivity(G) + assert_equal(nd,answer) + + D=G.to_directed() + answer={2:2.0,4:1.5} + nd = nx.average_degree_connectivity(D) + assert_equal(nd,answer) + + answer={1:2.0,2:1.5} + D=G.to_directed() + nd = nx.average_degree_connectivity(D, source='in', target='in') + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_degree_connectivity(D, source='in', target='in') + assert_equal(nd,answer) + + def test_degree_p4_weighted(self): + G=nx.path_graph(4) + G[1][2]['weight']=4 + answer={1:2.0,2:1.8} + nd = nx.average_degree_connectivity(G,weight='weight') + assert_equal(nd,answer) + answer={1:2.0,2:1.5} + nd = nx.average_degree_connectivity(G) + assert_equal(nd,answer) + + D=G.to_directed() + answer={2:2.0,4:1.8} + nd = nx.average_degree_connectivity(D,weight='weight') + assert_equal(nd,answer) + + answer={1:2.0,2:1.8} + D=G.to_directed() + nd = nx.average_degree_connectivity(D,weight='weight', source='in', + target='in') + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_degree_connectivity(D,source='in',target='out', + weight='weight') + assert_equal(nd,answer) + + def test_weight_keyword(self): + G=nx.path_graph(4) + G[1][2]['other']=4 + answer={1:2.0,2:1.8} + nd = nx.average_degree_connectivity(G,weight='other') + assert_equal(nd,answer) + answer={1:2.0,2:1.5} + nd = nx.average_degree_connectivity(G,weight=None) + assert_equal(nd,answer) + + D=G.to_directed() + answer={2:2.0,4:1.8} + nd = nx.average_degree_connectivity(D,weight='other') + assert_equal(nd,answer) + + answer={1:2.0,2:1.8} + D=G.to_directed() + nd = nx.average_degree_connectivity(D,weight='other', source='in', + target='in') + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_degree_connectivity(D,weight='other',source='in', + target='in') + assert_equal(nd,answer) + + def test_degree_barrat(self): + G=nx.star_graph(5) + G.add_edges_from([(5,6),(5,7),(5,8),(5,9)]) + G[0][5]['weight']=5 + nd = nx.average_degree_connectivity(G)[5] + assert_equal(nd,1.8) + nd = nx.average_degree_connectivity(G,weight='weight')[5] + assert_almost_equal(nd,3.222222,places=5) + nd = nx.k_nearest_neighbors(G,weight='weight')[5] + assert_almost_equal(nd,3.222222,places=5) + + def test_zero_deg(self): + G=nx.DiGraph() + G.add_edge(1,2) + G.add_edge(1,3) + G.add_edge(1,4) + c = nx.average_degree_connectivity(G) + assert_equal(c,{1:0,3:1}) + c = nx.average_degree_connectivity(G, source='in', target='in') + assert_equal(c,{0:0,1:0}) + c = nx.average_degree_connectivity(G, source='in', target='out') + assert_equal(c,{0:0,1:3}) + c = nx.average_degree_connectivity(G, source='in', target='in+out') + assert_equal(c,{0:0,1:3}) + c = nx.average_degree_connectivity(G, source='out', target='out') + assert_equal(c,{0:0,3:0}) + c = nx.average_degree_connectivity(G, source='out', target='in') + assert_equal(c,{0:0,3:1}) + c = nx.average_degree_connectivity(G, source='out', target='in+out') + assert_equal(c,{0:0,3:1}) + + + def test_in_out_weight(self): + from itertools import permutations + G=nx.DiGraph() + G.add_edge(1,2,weight=1) + G.add_edge(1,3,weight=1) + G.add_edge(3,1,weight=1) + for s,t in permutations(['in','out','in+out'],2): + c = nx.average_degree_connectivity(G, source=s, target=t) + cw = nx.average_degree_connectivity(G,source=s, target=t, + weight='weight') + assert_equal(c,cw) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_correlation.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_correlation.py new file mode 100644 index 0000000..fbb2d51 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_correlation.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx as nx +from base_test import BaseTestAttributeMixing,BaseTestDegreeMixing +from networkx.algorithms.assortativity.correlation import attribute_ac + + +class TestDegreeMixingCorrelation(BaseTestDegreeMixing): + @classmethod + def setupClass(cls): + global np + global npt + try: + import numpy as np + import numpy.testing as npt + except ImportError: + raise SkipTest('NumPy not available.') + try: + import scipy + import scipy.stats + except ImportError: + raise SkipTest('SciPy not available.') + + + + def test_degree_assortativity_undirected(self): + r=nx.degree_assortativity_coefficient(self.P4) + npt.assert_almost_equal(r,-1.0/2,decimal=4) + + def test_degree_assortativity_directed(self): + r=nx.degree_assortativity_coefficient(self.D) + npt.assert_almost_equal(r,-0.57735,decimal=4) + + def test_degree_assortativity_multigraph(self): + r=nx.degree_assortativity_coefficient(self.M) + npt.assert_almost_equal(r,-1.0/7.0,decimal=4) + + + def test_degree_assortativity_undirected(self): + r=nx.degree_pearson_correlation_coefficient(self.P4) + npt.assert_almost_equal(r,-1.0/2,decimal=4) + + def test_degree_assortativity_directed(self): + r=nx.degree_pearson_correlation_coefficient(self.D) + npt.assert_almost_equal(r,-0.57735,decimal=4) + + def test_degree_assortativity_multigraph(self): + r=nx.degree_pearson_correlation_coefficient(self.M) + npt.assert_almost_equal(r,-1.0/7.0,decimal=4) + + + +class TestAttributeMixingCorrelation(BaseTestAttributeMixing): + @classmethod + def setupClass(cls): + global np + global npt + try: + import numpy as np + import numpy.testing as npt + + except ImportError: + raise SkipTest('NumPy not available.') + + + def test_attribute_assortativity_undirected(self): + r=nx.attribute_assortativity_coefficient(self.G,'fish') + assert_equal(r,6.0/22.0) + + def test_attribute_assortativity_directed(self): + r=nx.attribute_assortativity_coefficient(self.D,'fish') + assert_equal(r,1.0/3.0) + + def test_attribute_assortativity_multigraph(self): + r=nx.attribute_assortativity_coefficient(self.M,'fish') + assert_equal(r,1.0) + + def test_attribute_assortativity_coefficient(self): + # from "Mixing patterns in networks" + a=np.array([[0.258,0.016,0.035,0.013], + [0.012,0.157,0.058,0.019], + [0.013,0.023,0.306,0.035], + [0.005,0.007,0.024,0.016]]) + r=attribute_ac(a) + npt.assert_almost_equal(r,0.623,decimal=3) + + def test_attribute_assortativity_coefficient2(self): + a=np.array([[0.18,0.02,0.01,0.03], + [0.02,0.20,0.03,0.02], + [0.01,0.03,0.16,0.01], + [0.03,0.02,0.01,0.22]]) + + r=attribute_ac(a) + npt.assert_almost_equal(r,0.68,decimal=2) + + def test_attribute_assortativity(self): + a=np.array([[50,50,0],[50,50,0],[0,0,2]]) + r=attribute_ac(a) + npt.assert_almost_equal(r,0.029,decimal=3) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_mixing.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_mixing.py new file mode 100644 index 0000000..ce60a94 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_mixing.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx as nx +from base_test import BaseTestAttributeMixing,BaseTestDegreeMixing + + +class TestDegreeMixingDict(BaseTestDegreeMixing): + + + def test_degree_mixing_dict_undirected(self): + d=nx.degree_mixing_dict(self.P4) + d_result={1:{2:2}, + 2:{1:2,2:2}, + } + assert_equal(d,d_result) + + def test_degree_mixing_dict_undirected_normalized(self): + d=nx.degree_mixing_dict(self.P4, normalized=True) + d_result={1:{2:1.0/3}, + 2:{1:1.0/3,2:1.0/3}, + } + assert_equal(d,d_result) + + def test_degree_mixing_dict_directed(self): + d=nx.degree_mixing_dict(self.D) + print(d) + d_result={1:{3:2}, + 2:{1:1,3:1}, + 3:{} + } + assert_equal(d,d_result) + + def test_degree_mixing_dict_multigraph(self): + d=nx.degree_mixing_dict(self.M) + d_result={1:{2:1}, + 2:{1:1,3:3}, + 3:{2:3} + } + assert_equal(d,d_result) + + +class TestDegreeMixingMatrix(BaseTestDegreeMixing): + + @classmethod + def setupClass(cls): + global np + global npt + try: + import numpy as np + import numpy.testing as npt + + except ImportError: + raise SkipTest('NumPy not available.') + + def test_degree_mixing_matrix_undirected(self): + a_result=np.array([[0,0,0], + [0,0,2], + [0,2,2]] + ) + a=nx.degree_mixing_matrix(self.P4,normalized=False) + npt.assert_equal(a,a_result) + a=nx.degree_mixing_matrix(self.P4) + npt.assert_equal(a,a_result/float(a_result.sum())) + + def test_degree_mixing_matrix_directed(self): + a_result=np.array([[0,0,0,0], + [0,0,0,2], + [0,1,0,1], + [0,0,0,0]] + ) + a=nx.degree_mixing_matrix(self.D,normalized=False) + npt.assert_equal(a,a_result) + a=nx.degree_mixing_matrix(self.D) + npt.assert_equal(a,a_result/float(a_result.sum())) + + def test_degree_mixing_matrix_multigraph(self): + a_result=np.array([[0,0,0,0], + [0,0,1,0], + [0,1,0,3], + [0,0,3,0]] + ) + a=nx.degree_mixing_matrix(self.M,normalized=False) + npt.assert_equal(a,a_result) + a=nx.degree_mixing_matrix(self.M) + npt.assert_equal(a,a_result/float(a_result.sum())) + + + def test_degree_mixing_matrix_selfloop(self): + a_result=np.array([[0,0,0], + [0,0,0], + [0,0,2]] + ) + a=nx.degree_mixing_matrix(self.S,normalized=False) + npt.assert_equal(a,a_result) + a=nx.degree_mixing_matrix(self.S) + npt.assert_equal(a,a_result/float(a_result.sum())) + + +class TestAttributeMixingDict(BaseTestAttributeMixing): + + def test_attribute_mixing_dict_undirected(self): + d=nx.attribute_mixing_dict(self.G,'fish') + d_result={'one':{'one':2,'red':1}, + 'two':{'two':2,'blue':1}, + 'red':{'one':1}, + 'blue':{'two':1} + } + assert_equal(d,d_result) + + def test_attribute_mixing_dict_directed(self): + d=nx.attribute_mixing_dict(self.D,'fish') + d_result={'one':{'one':1,'red':1}, + 'two':{'two':1,'blue':1}, + 'red':{}, + 'blue':{} + } + assert_equal(d,d_result) + + + def test_attribute_mixing_dict_multigraph(self): + d=nx.attribute_mixing_dict(self.M,'fish') + d_result={'one':{'one':4}, + 'two':{'two':2}, + } + assert_equal(d,d_result) + + + +class TestAttributeMixingMatrix(BaseTestAttributeMixing): + @classmethod + def setupClass(cls): + global np + global npt + try: + import numpy as np + import numpy.testing as npt + + except ImportError: + raise SkipTest('NumPy not available.') + + def test_attribute_mixing_matrix_undirected(self): + mapping={'one':0,'two':1,'red':2,'blue':3} + a_result=np.array([[2,0,1,0], + [0,2,0,1], + [1,0,0,0], + [0,1,0,0]] + ) + a=nx.attribute_mixing_matrix(self.G,'fish', + mapping=mapping, + normalized=False) + npt.assert_equal(a,a_result) + a=nx.attribute_mixing_matrix(self.G,'fish', + mapping=mapping) + npt.assert_equal(a,a_result/float(a_result.sum())) + + def test_attribute_mixing_matrix_directed(self): + mapping={'one':0,'two':1,'red':2,'blue':3} + a_result=np.array([[1,0,1,0], + [0,1,0,1], + [0,0,0,0], + [0,0,0,0]] + ) + a=nx.attribute_mixing_matrix(self.D,'fish', + mapping=mapping, + normalized=False) + npt.assert_equal(a,a_result) + a=nx.attribute_mixing_matrix(self.D,'fish', + mapping=mapping) + npt.assert_equal(a,a_result/float(a_result.sum())) + + def test_attribute_mixing_matrix_multigraph(self): + mapping={'one':0,'two':1,'red':2,'blue':3} + a_result=np.array([[4,0,0,0], + [0,2,0,0], + [0,0,0,0], + [0,0,0,0]] + ) + a=nx.attribute_mixing_matrix(self.M,'fish', + mapping=mapping, + normalized=False) + npt.assert_equal(a,a_result) + a=nx.attribute_mixing_matrix(self.M,'fish', + mapping=mapping) + npt.assert_equal(a,a_result/float(a_result.sum())) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_neighbor_degree.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_neighbor_degree.py new file mode 100644 index 0000000..7ab99fb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_neighbor_degree.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestAverageNeighbor(object): + + def test_degree_p4(self): + G=nx.path_graph(4) + answer={0:2,1:1.5,2:1.5,3:2} + nd = nx.average_neighbor_degree(G) + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D) + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D) + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D, source='in', target='in') + assert_equal(nd,answer) + + def test_degree_p4_weighted(self): + G=nx.path_graph(4) + G[1][2]['weight']=4 + answer={0:2,1:1.8,2:1.8,3:2} + nd = nx.average_neighbor_degree(G,weight='weight') + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D,weight='weight') + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D,weight='weight') + assert_equal(nd,answer) + nd = nx.average_neighbor_degree(D,source='out',target='out', + weight='weight') + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D,source='in',target='in', + weight='weight') + assert_equal(nd,answer) + + + def test_degree_k4(self): + G=nx.complete_graph(4) + answer={0:3,1:3,2:3,3:3} + nd = nx.average_neighbor_degree(G) + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D) + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D) + assert_equal(nd,answer) + + D=G.to_directed() + nd = nx.average_neighbor_degree(D,source='in',target='in') + assert_equal(nd,answer) + + def test_degree_k4_nodes(self): + G=nx.complete_graph(4) + answer={1:3.0,2:3.0} + nd = nx.average_neighbor_degree(G,nodes=[1,2]) + assert_equal(nd,answer) + + def test_degree_barrat(self): + G=nx.star_graph(5) + G.add_edges_from([(5,6),(5,7),(5,8),(5,9)]) + G[0][5]['weight']=5 + nd = nx.average_neighbor_degree(G)[5] + assert_equal(nd,1.8) + nd = nx.average_neighbor_degree(G,weight='weight')[5] + assert_almost_equal(nd,3.222222,places=5) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_pairs.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_pairs.py new file mode 100644 index 0000000..fa67a45 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/assortativity/tests/test_pairs.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from base_test import BaseTestAttributeMixing,BaseTestDegreeMixing + +class TestAttributeMixingXY(BaseTestAttributeMixing): + + def test_node_attribute_xy_undirected(self): + attrxy=sorted(nx.node_attribute_xy(self.G,'fish')) + attrxy_result=sorted([('one','one'), + ('one','one'), + ('two','two'), + ('two','two'), + ('one','red'), + ('red','one'), + ('blue','two'), + ('two','blue') + ]) + assert_equal(attrxy,attrxy_result) + + def test_node_attribute_xy_undirected_nodes(self): + attrxy=sorted(nx.node_attribute_xy(self.G,'fish', + nodes=['one','yellow'])) + attrxy_result=sorted( [ + ]) + assert_equal(attrxy,attrxy_result) + + + def test_node_attribute_xy_directed(self): + attrxy=sorted(nx.node_attribute_xy(self.D,'fish')) + attrxy_result=sorted([('one','one'), + ('two','two'), + ('one','red'), + ('two','blue') + ]) + assert_equal(attrxy,attrxy_result) + + def test_node_attribute_xy_multigraph(self): + attrxy=sorted(nx.node_attribute_xy(self.M,'fish')) + attrxy_result=[('one','one'), + ('one','one'), + ('one','one'), + ('one','one'), + ('two','two'), + ('two','two') + ] + assert_equal(attrxy,attrxy_result) + + def test_node_attribute_xy_selfloop(self): + attrxy=sorted(nx.node_attribute_xy(self.S,'fish')) + attrxy_result=[('one','one'), + ('two','two') + ] + assert_equal(attrxy,attrxy_result) + + +class TestDegreeMixingXY(BaseTestDegreeMixing): + + def test_node_degree_xy_undirected(self): + xy=sorted(nx.node_degree_xy(self.P4)) + xy_result=sorted([(1,2), + (2,1), + (2,2), + (2,2), + (1,2), + (2,1)]) + assert_equal(xy,xy_result) + + def test_node_degree_xy_undirected_nodes(self): + xy=sorted(nx.node_degree_xy(self.P4,nodes=[0,1,-1])) + xy_result=sorted([(1,2), + (2,1),]) + assert_equal(xy,xy_result) + + + def test_node_degree_xy_directed(self): + xy=sorted(nx.node_degree_xy(self.D)) + xy_result=sorted([(2,1), + (2,3), + (1,3), + (1,3)]) + assert_equal(xy,xy_result) + + def test_node_degree_xy_multigraph(self): + xy=sorted(nx.node_degree_xy(self.M)) + xy_result=sorted([(2,3), + (2,3), + (3,2), + (3,2), + (2,3), + (3,2), + (1,2), + (2,1)]) + assert_equal(xy,xy_result) + + + def test_node_degree_xy_selfloop(self): + xy=sorted(nx.node_degree_xy(self.S)) + xy_result=sorted([(2,2), + (2,2)]) + assert_equal(xy,xy_result) + + def test_node_degree_xy_weighted(self): + G = nx.Graph() + G.add_edge(1,2,weight=7) + G.add_edge(2,3,weight=10) + xy=sorted(nx.node_degree_xy(G,weight='weight')) + xy_result=sorted([(7,17), + (17,10), + (17,7), + (10,17)]) + assert_equal(xy,xy_result) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/__init__.py new file mode 100644 index 0000000..53ba9d3 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/__init__.py @@ -0,0 +1,93 @@ +r""" This module provides functions and operations for bipartite +graphs. Bipartite graphs `B = (U, V, E)` have two node sets `U,V` and edges in +`E` that only connect nodes from opposite sets. It is common in the literature +to use an spatial analogy referring to the two node sets as top and bottom nodes. + +The bipartite algorithms are not imported into the networkx namespace +at the top level so the easiest way to use them is with: + +>>> import networkx as nx +>>> from networkx.algorithms import bipartite + +NetworkX does not have a custom bipartite graph class but the Graph() +or DiGraph() classes can be used to represent bipartite graphs. However, +you have to keep track of which set each node belongs to, and make +sure that there is no edge between nodes of the same set. The convention used +in NetworkX is to use a node attribute named "bipartite" with values 0 or 1 to +identify the sets each node belongs to. + +For example: + +>>> B = nx.Graph() +>>> B.add_nodes_from([1,2,3,4], bipartite=0) # Add the node attribute "bipartite" +>>> B.add_nodes_from(['a','b','c'], bipartite=1) +>>> B.add_edges_from([(1,'a'), (1,'b'), (2,'b'), (2,'c'), (3,'c'), (4,'a')]) + +Many algorithms of the bipartite module of NetworkX require, as an argument, a +container with all the nodes that belong to one set, in addition to the bipartite +graph `B`. If `B` is connected, you can find the node sets using a two-coloring +algorithm: + +>>> nx.is_connected(B) +True +>>> bottom_nodes, top_nodes = bipartite.sets(B) + +list(top_nodes) +[1, 2, 3, 4] +list(bottom_nodes) +['a', 'c', 'b'] + +However, if the input graph is not connected, there are more than one possible +colorations. Thus, the following result is correct: + +>>> B.remove_edge(2,'c') +>>> nx.is_connected(B) +False +>>> bottom_nodes, top_nodes = bipartite.sets(B) + +list(top_nodes) +[1, 2, 4, 'c'] +list(bottom_nodes) +['a', 3, 'b'] + +Using the "bipartite" node attribute, you can easily get the two node sets: + +>>> top_nodes = set(n for n,d in B.nodes(data=True) if d['bipartite']==0) +>>> bottom_nodes = set(B) - top_nodes + +list(top_nodes) +[1, 2, 3, 4] +list(bottom_nodes) +['a', 'c', 'b'] + +So you can easily use the bipartite algorithms that require, as an argument, a +container with all nodes that belong to one node set: + +>>> print(round(bipartite.density(B, bottom_nodes),2)) +0.42 +>>> G = bipartite.projected_graph(B, top_nodes) +>>> G.edges() +[(1, 2), (1, 4)] + +All bipartite graph generators in NetworkX build bipartite graphs with the +"bipartite" node attribute. Thus, you can use the same approach: + +>>> RB = nx.bipartite_random_graph(5, 7, 0.2) +>>> RB_top = set(n for n,d in RB.nodes(data=True) if d['bipartite']==0) +>>> RB_bottom = set(RB) - RB_top +>>> list(RB_top) +[0, 1, 2, 3, 4] +>>> list(RB_bottom) +[5, 6, 7, 8, 9, 10, 11] + +For other bipartite graph generators see the bipartite section of +:doc:`generators`. + +""" + +from networkx.algorithms.bipartite.basic import * +from networkx.algorithms.bipartite.centrality import * +from networkx.algorithms.bipartite.cluster import * +from networkx.algorithms.bipartite.projection import * +from networkx.algorithms.bipartite.redundancy import * +from networkx.algorithms.bipartite.spectral import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/basic.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/basic.py new file mode 100644 index 0000000..e902889 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/basic.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +""" +========================== +Bipartite Graph Algorithms +========================== +""" +# Copyright (C) 2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from itertools import count +__author__ = """\n""".join(['Jordi Torrents ', + 'Aric Hagberg ']) +__all__ = [ 'is_bipartite', + 'is_bipartite_node_set', + 'color', + 'sets', + 'density', + 'degrees', + 'biadjacency_matrix'] + +def biadjacency_matrix(G, row_order, column_order=None, + weight='weight', dtype=None): + r"""Return the biadjacency matrix of the bipartite graph G. + + Let `G = (U, V, E)` be a bipartite graph with node sets + `U = u_{1},...,u_{r}` and `V = v_{1},...,v_{s}`. The biadjacency + matrix [1] is the `r` x `s` matrix `B` in which `b_{i,j} = 1` + if, and only if, `(u_i, v_j) \in E`. If the parameter `weight` is + not `None` and matches the name of an edge attribute, its value is + used instead of 1. + + Parameters + ---------- + G : graph + A NetworkX graph + + row_order : list of nodes + The rows of the matrix are ordered according to the list of nodes. + + column_order : list, optional + The columns of the matrix are ordered according to the list of nodes. + If column_order is None, then the ordering of columns is arbitrary. + + weight : string or None, optional (default='weight') + The edge data key used to provide each value in the matrix. + If None, then each edge has weight 1. + + dtype : NumPy data type, optional + A valid single NumPy data type used to initialize the array. + This must be a simple type such as int or numpy.float64 and + not a compound data type (see to_numpy_recarray) + If None, then the NumPy default is used. + + Returns + ------- + B : numpy matrix + Biadjacency matrix representation of the bipartite graph G. + + Notes + ----- + No attempt is made to check that the input graph is bipartite. + + For directed bipartite graphs only successors are considered as neighbors. + To obtain an adjacency matrix with ones (or weight values) for both + predecessors and successors you have to generate two biadjacency matrices + where the rows of one of them are the columns of the other, and then add + one to the transpose of the other. + + See Also + -------- + to_numpy_matrix + adjacency_matrix + + References + ---------- + [1] http://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph + """ + try: + import numpy as np + except ImportError: + raise ImportError('adjacency_matrix() requires numpy ', + 'http://scipy.org/') + if column_order is None: + column_order = list(set(G) - set(row_order)) + row = dict(zip(row_order,count())) + col = dict(zip(column_order,count())) + M = np.zeros((len(row),len(col)), dtype=dtype) + for u in row_order: + for v, d in G[u].items(): + M[row[u],col[v]] = d.get(weight, 1) + return np.asmatrix(M) + +def color(G): + """Returns a two-coloring of the graph. + + Raises an exception if the graph is not bipartite. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + color : dictionary + A dictionary keyed by node with a 1 or 0 as data for each node color. + + Raises + ------ + NetworkXError if the graph is not two-colorable. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> c = bipartite.color(G) + >>> print(c) + {0: 1, 1: 0, 2: 1, 3: 0} + + You can use this to set a node attribute indicating the biparite set: + + >>> nx.set_node_attributes(G, 'bipartite', c) + >>> print(G.node[0]['bipartite']) + 1 + >>> print(G.node[1]['bipartite']) + 0 + """ + if G.is_directed(): + import itertools + def neighbors(v): + return itertools.chain.from_iterable([G.predecessors_iter(v), + G.successors_iter(v)]) + else: + neighbors=G.neighbors_iter + + color = {} + for n in G: # handle disconnected graphs + if n in color or len(G[n])==0: # skip isolates + continue + queue = [n] + color[n] = 1 # nodes seen with color (1 or 0) + while queue: + v = queue.pop() + c = 1 - color[v] # opposite color of node v + for w in neighbors(v): + if w in color: + if color[w] == color[v]: + raise nx.NetworkXError("Graph is not bipartite.") + else: + color[w] = c + queue.append(w) + # color isolates with 0 + color.update(dict.fromkeys(nx.isolates(G),0)) + return color + +def is_bipartite(G): + """ Returns True if graph G is bipartite, False if not. + + Parameters + ---------- + G : NetworkX graph + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> print(bipartite.is_bipartite(G)) + True + + See Also + -------- + color, is_bipartite_node_set + """ + try: + color(G) + return True + except nx.NetworkXError: + return False + +def is_bipartite_node_set(G,nodes): + """Returns True if nodes and G/nodes are a bipartition of G. + + Parameters + ---------- + G : NetworkX graph + + nodes: list or container + Check if nodes are a one of a bipartite set. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> X = set([1,3]) + >>> bipartite.is_bipartite_node_set(G,X) + True + + Notes + ----- + For connected graphs the bipartite sets are unique. This function handles + disconnected graphs. + """ + S=set(nodes) + for CC in nx.connected_component_subgraphs(G): + X,Y=sets(CC) + if not ( (X.issubset(S) and Y.isdisjoint(S)) or + (Y.issubset(S) and X.isdisjoint(S)) ): + return False + return True + + +def sets(G): + """Returns bipartite node sets of graph G. + + Raises an exception if the graph is not bipartite. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + (X,Y) : two-tuple of sets + One set of nodes for each part of the bipartite graph. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> X, Y = bipartite.sets(G) + >>> list(X) + [0, 2] + >>> list(Y) + [1, 3] + + See Also + -------- + color + """ + c = color(G) + X = set(n for n in c if c[n]) # c[n] == 1 + Y = set(n for n in c if not c[n]) # c[n] == 0 + return (X, Y) + +def density(B, nodes): + """Return density of bipartite graph B. + + Parameters + ---------- + G : NetworkX graph + + nodes: list or container + Nodes in one set of the bipartite graph. + + Returns + ------- + d : float + The bipartite density + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.complete_bipartite_graph(3,2) + >>> X=set([0,1,2]) + >>> bipartite.density(G,X) + 1.0 + >>> Y=set([3,4]) + >>> bipartite.density(G,Y) + 1.0 + + See Also + -------- + color + """ + n=len(B) + m=nx.number_of_edges(B) + nb=len(nodes) + nt=n-nb + if m==0: # includes cases n==0 and n==1 + d=0.0 + else: + if B.is_directed(): + d=m/(2.0*float(nb*nt)) + else: + d= m/float(nb*nt) + return d + +def degrees(B, nodes, weight=None): + """Return the degrees of the two node sets in the bipartite graph B. + + Parameters + ---------- + G : NetworkX graph + + nodes: list or container + Nodes in one set of the bipartite graph. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used as a weight. + If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + (degX,degY) : tuple of dictionaries + The degrees of the two bipartite sets as dictionaries keyed by node. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.complete_bipartite_graph(3,2) + >>> Y=set([3,4]) + >>> degX,degY=bipartite.degrees(G,Y) + >>> degX + {0: 2, 1: 2, 2: 2} + + See Also + -------- + color, density + """ + bottom=set(nodes) + top=set(B)-bottom + return (B.degree(top,weight),B.degree(bottom,weight)) + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/centrality.py new file mode 100644 index 0000000..0b885a7 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/centrality.py @@ -0,0 +1,266 @@ +#-*- coding: utf-8 -*- +# Copyright (C) 2011 by +# Jordi Torrents +# Aric Hagberg +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Jordi Torrents ', + 'Aric Hagberg (hagberg@lanl.gov)']) +__all__=['degree_centrality', + 'betweenness_centrality', + 'closeness_centrality'] + +def degree_centrality(G, nodes): + r"""Compute the degree centrality for nodes in a bipartite network. + + The degree centrality for a node `v` is the fraction of nodes + connected to it. + + Parameters + ---------- + G : graph + A bipartite network + + nodes : list or container + Container with all nodes in one bipartite node set. + + Returns + ------- + centrality : dictionary + Dictionary keyed by node with bipartite degree centrality as the value. + + See Also + -------- + betweenness_centrality, + closeness_centrality, + sets, + is_bipartite + + Notes + ----- + The nodes input parameter must conatin all nodes in one bipartite node set, + but the dictionary returned contains all nodes from both bipartite node + sets. + + For unipartite networks, the degree centrality values are + normalized by dividing by the maximum possible degree (which is + `n-1` where `n` is the number of nodes in G). + + In the bipartite case, the maximum possible degree of a node in a + bipartite node set is the number of nodes in the opposite node set + [1]_. The degree centrality for a node `v` in the bipartite + sets `U` with `n` nodes and `V` with `m` nodes is + + .. math:: + + d_{v} = \frac{deg(v)}{m}, \mbox{for} v \in U , + + d_{v} = \frac{deg(v)}{n}, \mbox{for} v \in V , + + + where `deg(v)` is the degree of node `v`. + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + http://www.steveborgatti.com/papers/bhaffiliations.pdf + """ + top = set(nodes) + bottom = set(G) - top + s = 1.0/len(bottom) + centrality = dict((n,d*s) for n,d in G.degree_iter(top)) + s = 1.0/len(top) + centrality.update(dict((n,d*s) for n,d in G.degree_iter(bottom))) + return centrality + + +def betweenness_centrality(G, nodes): + r"""Compute betweenness centrality for nodes in a bipartite network. + + Betweenness centrality of a node `v` is the sum of the + fraction of all-pairs shortest paths that pass through `v`. + + Values of betweenness are normalized by the maximum possible + value which for bipartite graphs is limited by the relative size + of the two node sets [1]_. + + Let `n` be the number of nodes in the node set `U` and + `m` be the number of nodes in the node set `V`, then + nodes in `U` are normalized by dividing by + + .. math:: + + \frac{1}{2} [m^2 (s + 1)^2 + m (s + 1)(2t - s - 1) - t (2s - t + 3)] , + + where + + .. math:: + + s = (n - 1) \div m , t = (n - 1) \mod m , + + and nodes in `V` are normalized by dividing by + + .. math:: + + \frac{1}{2} [n^2 (p + 1)^2 + n (p + 1)(2r - p - 1) - r (2p - r + 3)] , + + where, + + .. math:: + + p = (m - 1) \div n , r = (m - 1) \mod n . + + Parameters + ---------- + G : graph + A bipartite graph + + nodes : list or container + Container with all nodes in one bipartite node set. + + Returns + ------- + betweenness : dictionary + Dictionary keyed by node with bipartite betweenness centrality + as the value. + + See Also + -------- + degree_centrality, + closeness_centrality, + sets, + is_bipartite + + Notes + ----- + The nodes input parameter must contain all nodes in one bipartite node set, + but the dictionary returned contains all nodes from both node sets. + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + http://www.steveborgatti.com/papers/bhaffiliations.pdf + """ + top = set(nodes) + bottom = set(G) - top + n = float(len(top)) + m = float(len(bottom)) + s = (n-1) // m + t = (n-1) % m + bet_max_top = (((m**2)*((s+1)**2))+ + (m*(s+1)*(2*t-s-1))- + (t*((2*s)-t+3)))/2.0 + p = (m-1) // n + r = (m-1) % n + bet_max_bot = (((n**2)*((p+1)**2))+ + (n*(p+1)*(2*r-p-1))- + (r*((2*p)-r+3)))/2.0 + betweenness = nx.betweenness_centrality(G, normalized=False, + weight=None) + for node in top: + betweenness[node]/=bet_max_top + for node in bottom: + betweenness[node]/=bet_max_bot + return betweenness + +def closeness_centrality(G, nodes, normalized=True): + r"""Compute the closeness centrality for nodes in a bipartite network. + + The closeness of a node is the distance to all other nodes in the + graph or in the case that the graph is not connected to all other nodes + in the connected component containing that node. + + Parameters + ---------- + G : graph + A bipartite network + + nodes : list or container + Container with all nodes in one bipartite node set. + + normalized : bool, optional + If True (default) normalize by connected component size. + + Returns + ------- + closeness : dictionary + Dictionary keyed by node with bipartite closeness centrality + as the value. + + See Also + -------- + betweenness_centrality, + degree_centrality + sets, + is_bipartite + + Notes + ----- + The nodes input parameter must conatin all nodes in one bipartite node set, + but the dictionary returned contains all nodes from both node sets. + + Closeness centrality is normalized by the minimum distance possible. + In the bipartite case the minimum distance for a node in one bipartite + node set is 1 from all nodes in the other node set and 2 from all + other nodes in its own set [1]_. Thus the closeness centrality + for node `v` in the two bipartite sets `U` with + `n` nodes and `V` with `m` nodes is + + .. math:: + + c_{v} = \frac{m + 2(n - 1)}{d}, \mbox{for} v \in U, + + c_{v} = \frac{n + 2(m - 1)}{d}, \mbox{for} v \in V, + + where `d` is the sum of the distances from `v` to all + other nodes. + + Higher values of closeness indicate higher centrality. + + As in the unipartite case, setting normalized=True causes the + values to normalized further to n-1 / size(G)-1 where n is the + number of nodes in the connected part of graph containing the + node. If the graph is not completely connected, this algorithm + computes the closeness centrality for each connected part + separately. + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + http://www.steveborgatti.com/papers/bhaffiliations.pdf + """ + closeness={} + path_length=nx.single_source_shortest_path_length + top = set(nodes) + bottom = set(G) - top + n = float(len(top)) + m = float(len(bottom)) + for node in top: + sp=path_length(G,node) + totsp=sum(sp.values()) + if totsp > 0.0 and len(G) > 1: + closeness[node]= (m + 2*(n-1)) / totsp + if normalized: + s=(len(sp)-1.0) / ( len(G) - 1 ) + closeness[node] *= s + else: + closeness[n]=0.0 + for node in bottom: + sp=path_length(G,node) + totsp=sum(sp.values()) + if totsp > 0.0 and len(G) > 1: + closeness[node]= (n + 2*(m-1)) / totsp + if normalized: + s=(len(sp)-1.0) / ( len(G) - 1 ) + closeness[node] *= s + else: + closeness[n]=0.0 + return closeness + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/cluster.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/cluster.py new file mode 100644 index 0000000..8adf92d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/cluster.py @@ -0,0 +1,266 @@ +#-*- coding: utf-8 -*- +# Copyright (C) 2011 by +# Jordi Torrents +# Aric Hagberg +# All rights reserved. +# BSD license. +import itertools +import networkx as nx +__author__ = """\n""".join(['Jordi Torrents ', + 'Aric Hagberg (hagberg@lanl.gov)']) +__all__ = [ 'clustering', + 'average_clustering', + 'latapy_clustering', + 'robins_alexander_clustering'] + +# functions for computing clustering of pairs +def cc_dot(nu,nv): + return float(len(nu & nv))/len(nu | nv) + +def cc_max(nu,nv): + return float(len(nu & nv))/max(len(nu),len(nv)) + +def cc_min(nu,nv): + return float(len(nu & nv))/min(len(nu),len(nv)) + +modes={'dot':cc_dot, + 'min':cc_min, + 'max':cc_max} + +def latapy_clustering(G, nodes=None, mode='dot'): + r"""Compute a bipartite clustering coefficient for nodes. + + The bipartie clustering coefficient is a measure of local density + of connections defined as [1]_: + + .. math:: + + c_u = \frac{\sum_{v \in N(N(v))} c_{uv} }{|N(N(u))|} + + where `N(N(u))` are the second order neighbors of `u` in `G` excluding `u`, + and `c_{uv}` is the pairwise clustering coefficient between nodes + `u` and `v`. + + The mode selects the function for `c_{uv}` which can be: + + `dot`: + + .. math:: + + c_{uv}=\frac{|N(u)\cap N(v)|}{|N(u) \cup N(v)|} + + `min`: + + .. math:: + + c_{uv}=\frac{|N(u)\cap N(v)|}{min(|N(u)|,|N(v)|)} + + `max`: + + .. math:: + + c_{uv}=\frac{|N(u)\cap N(v)|}{max(|N(u)|,|N(v)|)} + + + Parameters + ---------- + G : graph + A bipartite graph + + nodes : list or iterable (optional) + Compute bipartite clustering for these nodes. The default + is all nodes in G. + + mode : string + The pariwise bipartite clustering method to be used in the computation. + It must be "dot", "max", or "min". + + Returns + ------- + clustering : dictionary + A dictionary keyed by node with the clustering coefficient value. + + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) # path graphs are bipartite + >>> c = bipartite.clustering(G) + >>> c[0] + 0.5 + >>> c = bipartite.clustering(G,mode='min') + >>> c[0] + 1.0 + + See Also + -------- + robins_alexander_clustering + square_clustering + average_clustering + + References + ---------- + .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). + Basic notions for the analysis of large two-mode networks. + Social Networks 30(1), 31--48. + """ + if not nx.algorithms.bipartite.is_bipartite(G): + raise nx.NetworkXError("Graph is not bipartite") + + try: + cc_func = modes[mode] + except KeyError: + raise nx.NetworkXError(\ + "Mode for bipartite clustering must be: dot, min or max") + + if nodes is None: + nodes = G + ccs = {} + for v in nodes: + cc = 0.0 + nbrs2=set([u for nbr in G[v] for u in G[nbr]])-set([v]) + for u in nbrs2: + cc += cc_func(set(G[u]),set(G[v])) + if cc > 0.0: # len(nbrs2)>0 + cc /= len(nbrs2) + ccs[v] = cc + return ccs + +clustering = latapy_clustering + +def average_clustering(G, nodes=None, mode='dot'): + r"""Compute the average bipartite clustering coefficient. + + A clustering coefficient for the whole graph is the average, + + .. math:: + + C = \frac{1}{n}\sum_{v \in G} c_v, + + where `n` is the number of nodes in `G`. + + Similar measures for the two bipartite sets can be defined [1]_ + + .. math:: + + C_X = \frac{1}{|X|}\sum_{v \in X} c_v, + + where `X` is a bipartite set of `G`. + + Parameters + ---------- + G : graph + a bipartite graph + + nodes : list or iterable, optional + A container of nodes to use in computing the average. + The nodes should be either the entire graph (the default) or one of the + bipartite sets. + + mode : string + The pariwise bipartite clustering method. + It must be "dot", "max", or "min" + + Returns + ------- + clustering : float + The average bipartite clustering for the given set of nodes or the + entire graph if no nodes are specified. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G=nx.star_graph(3) # star graphs are bipartite + >>> bipartite.average_clustering(G) + 0.75 + >>> X,Y=bipartite.sets(G) + >>> bipartite.average_clustering(G,X) + 0.0 + >>> bipartite.average_clustering(G,Y) + 1.0 + + See Also + -------- + clustering + + Notes + ----- + The container of nodes passed to this function must contain all of the nodes + in one of the bipartite sets ("top" or "bottom") in order to compute + the correct average bipartite clustering coefficients. + + References + ---------- + .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). + Basic notions for the analysis of large two-mode networks. + Social Networks 30(1), 31--48. + """ + if nodes is None: + nodes=G + ccs=latapy_clustering(G, nodes=nodes, mode=mode) + return float(sum(ccs[v] for v in nodes))/len(nodes) + +def robins_alexander_clustering(G): + r"""Compute the bipartite clustering of G. + + Robins and Alexander [1]_ defined bipartite clustering coefficient as + four times the number of four cycles `C_4` divided by the number of + three paths `L_3` in a bipartite graph: + + .. math:: + + CC_4 = \frac{4 * C_4}{L_3} + + Parameters + ---------- + G : graph + a bipartite graph + + Returns + ------- + clustering : float + The Robins and Alexander bipartite clustering for the input graph. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.davis_southern_women_graph() + >>> print(round(bipartite.robins_alexander_clustering(G), 3)) + 0.468 + + See Also + -------- + latapy_clustering + square_clustering + + References + ---------- + .. [1] Robins, G. and M. Alexander (2004). Small worlds among interlocking + directors: Network structure and distance in bipartite graphs. + Computational & Mathematical Organization Theory 10(1), 69–94. + + """ + if G.order() < 4 or G.size() < 3: + return 0 + L_3 = _threepaths(G) + if L_3 == 0: + return 0 + C_4 = _four_cycles(G) + return (4. * C_4) / L_3 + +def _four_cycles(G): + cycles = 0 + for v in G: + for u, w in itertools.combinations(G[v], 2): + cycles += len((set(G[u]) & set(G[w])) - set([v])) + return cycles / 4 + +def _threepaths(G): + paths = 0 + for v in G: + for u in G[v]: + for w in set(G[u]) - set([v]): + paths += len(set(G[w]) - set([v, u])) + # Divide by two because we count each three path twice + # one for each possible starting point + return paths / 2 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/projection.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/projection.py new file mode 100644 index 0000000..7f08244 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/projection.py @@ -0,0 +1,497 @@ +# -*- coding: utf-8 -*- +"""One-mode (unipartite) projections of bipartite graphs. +""" +import networkx as nx +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """\n""".join(['Aric Hagberg ', + 'Jordi Torrents ']) +__all__ = ['project', + 'projected_graph', + 'weighted_projected_graph', + 'collaboration_weighted_projected_graph', + 'overlap_weighted_projected_graph', + 'generic_weighted_projected_graph'] + +def projected_graph(B, nodes, multigraph=False): + r"""Returns the projection of B onto one of its node sets. + + Returns the graph G that is the projection of the bipartite graph B + onto the specified nodes. They retain their attributes and are connected + in G if they have a common neighbor in B. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + multigraph: bool (default=False) + If True return a multigraph where the multiple edges represent multiple + shared neighbors. They edge key in the multigraph is assigned to the + label of the neighbor. + + Returns + ------- + Graph : NetworkX graph or multigraph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(4) + >>> G = bipartite.projected_graph(B, [1,3]) + >>> print(G.nodes()) + [1, 3] + >>> print(G.edges()) + [(1, 3)] + + If nodes `a`, and `b` are connected through both nodes 1 and 2 then + building a multigraph results in two edges in the projection onto + [`a`,`b`]: + + >>> B = nx.Graph() + >>> B.add_edges_from([('a', 1), ('b', 1), ('a', 2), ('b', 2)]) + >>> G = bipartite.projected_graph(B, ['a', 'b'], multigraph=True) + >>> print([sorted((u,v)) for u,v in G.edges()]) + [['a', 'b'], ['a', 'b']] + + Notes + ------ + No attempt is made to verify that the input graph B is bipartite. + Returns a simple graph that is the projection of the bipartite graph B + onto the set of nodes given in list nodes. If multigraph=True then + a multigraph is returned with an edge for every shared neighbor. + + Directed graphs are allowed as input. The output will also then + be a directed graph with edges if there is a directed path between + the nodes. + + The graph and node properties are (shallow) copied to the projected graph. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + collaboration_weighted_projected_graph, + overlap_weighted_projected_graph, + generic_weighted_projected_graph + """ + if B.is_multigraph(): + raise nx.NetworkXError("not defined for multigraphs") + if B.is_directed(): + directed=True + if multigraph: + G=nx.MultiDiGraph() + else: + G=nx.DiGraph() + else: + directed=False + if multigraph: + G=nx.MultiGraph() + else: + G=nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n,B.node[n]) for n in nodes) + for u in nodes: + nbrs2=set((v for nbr in B[u] for v in B[nbr])) -set([u]) + if multigraph: + for n in nbrs2: + if directed: + links=set(B[u]) & set(B.pred[n]) + else: + links=set(B[u]) & set(B[n]) + for l in links: + if not G.has_edge(u,n,l): + G.add_edge(u,n,key=l) + else: + G.add_edges_from((u,n) for n in nbrs2) + return G + +def weighted_projected_graph(B, nodes, ratio=False): + r"""Returns a weighted projection of B onto one of its node sets. + + The weighted projected graph is the projection of the bipartite + network B onto the specified nodes with weights representing the + number of shared neighbors or the ratio between actual shared + neighbors and possible shared neighbors if ratio=True [1]_. The + nodes retain their attributes and are connected in the resulting graph + if they have an edge to a common node in the original graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + ratio: Bool (default=False) + If True, edge weight is the ratio between actual shared neighbors + and possible shared neighbors. If False, edges weight is the number + of shared neighbors. + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(4) + >>> G = bipartite.weighted_projected_graph(B, [1,3]) + >>> print(G.nodes()) + [1, 3] + >>> print(G.edges(data=True)) + [(1, 3, {'weight': 1})] + >>> G = bipartite.weighted_projected_graph(B, [1,3], ratio=True) + >>> print(G.edges(data=True)) + [(1, 3, {'weight': 0.5})] + + Notes + ------ + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + collaboration_weighted_projected_graph, + overlap_weighted_projected_graph, + generic_weighted_projected_graph + projected_graph + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + """ + if B.is_multigraph(): + raise nx.NetworkXError("not defined for multigraphs") + if B.is_directed(): + pred=B.pred + G=nx.DiGraph() + else: + pred=B.adj + G=nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n,B.node[n]) for n in nodes) + n_top = float(len(B) - len(nodes)) + for u in nodes: + unbrs = set(B[u]) + nbrs2 = set((n for nbr in unbrs for n in B[nbr])) - set([u]) + for v in nbrs2: + vnbrs = set(pred[v]) + common = unbrs & vnbrs + if not ratio: + weight = len(common) + else: + weight = len(common) / n_top + G.add_edge(u,v,weight=weight) + return G + +def collaboration_weighted_projected_graph(B, nodes): + r"""Newman's weighted projection of B onto one of its node sets. + + The collaboration weighted projection is the projection of the + bipartite network B onto the specified nodes with weights assigned + using Newman's collaboration model [1]_: + + .. math:: + + w_{v,u} = \sum_k \frac{\delta_{v}^{w} \delta_{w}^{k}}{k_w - 1} + + where `v` and `u` are nodes from the same bipartite node set, + and `w` is a node of the opposite node set. + The value `k_w` is the degree of node `w` in the bipartite + network and `\delta_{v}^{w}` is 1 if node `v` is + linked to node `w` in the original bipartite graph or 0 otherwise. + + The nodes retain their attributes and are connected in the resulting + graph if have an edge to a common node in the original bipartite + graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(5) + >>> B.add_edge(1,5) + >>> G = bipartite.collaboration_weighted_projected_graph(B, [0, 2, 4, 5]) + >>> print(G.nodes()) + [0, 2, 4, 5] + >>> for edge in G.edges(data=True): print(edge) + ... + (0, 2, {'weight': 0.5}) + (0, 5, {'weight': 0.5}) + (2, 4, {'weight': 1.0}) + (2, 5, {'weight': 0.5}) + + Notes + ------ + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + overlap_weighted_projected_graph, + generic_weighted_projected_graph, + projected_graph + + References + ---------- + .. [1] Scientific collaboration networks: II. + Shortest paths, weighted networks, and centrality, + M. E. J. Newman, Phys. Rev. E 64, 016132 (2001). + """ + if B.is_multigraph(): + raise nx.NetworkXError("not defined for multigraphs") + if B.is_directed(): + pred=B.pred + G=nx.DiGraph() + else: + pred=B.adj + G=nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n,B.node[n]) for n in nodes) + for u in nodes: + unbrs = set(B[u]) + nbrs2 = set((n for nbr in unbrs for n in B[nbr])) - set([u]) + for v in nbrs2: + vnbrs = set(pred[v]) + common = unbrs & vnbrs + weight = sum([1.0/(len(B[n]) - 1) for n in common if len(B[n])>1]) + G.add_edge(u,v,weight=weight) + return G + +def overlap_weighted_projected_graph(B, nodes, jaccard=True): + r"""Overlap weighted projection of B onto one of its node sets. + + The overlap weighted projection is the projection of the bipartite + network B onto the specified nodes with weights representing + the Jaccard index between the neighborhoods of the two nodes in the + original bipartite network [1]_: + + .. math:: + + w_{v,u} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|} + + or if the parameter 'jaccard' is False, the fraction of common + neighbors by minimum of both nodes degree in the original + bipartite graph [1]_: + + .. math:: + + w_{v,u} = \frac{|N(u) \cap N(v)|}{min(|N(u)|,|N(v)|)} + + The nodes retain their attributes and are connected in the resulting + graph if have an edge to a common node in the original bipartite graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + jaccard: Bool (default=True) + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(5) + >>> G = bipartite.overlap_weighted_projected_graph(B, [0, 2, 4]) + >>> print(G.nodes()) + [0, 2, 4] + >>> print(G.edges(data=True)) + [(0, 2, {'weight': 0.5}), (2, 4, {'weight': 0.5})] + >>> G = bipartite.overlap_weighted_projected_graph(B, [0, 2, 4], jaccard=False) + >>> print(G.edges(data=True)) + [(0, 2, {'weight': 1.0}), (2, 4, {'weight': 1.0})] + + Notes + ------ + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + collaboration_weighted_projected_graph, + generic_weighted_projected_graph, + projected_graph + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. Analyzing Affiliation + Networks. In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + + """ + if B.is_multigraph(): + raise nx.NetworkXError("not defined for multigraphs") + if B.is_directed(): + pred=B.pred + G=nx.DiGraph() + else: + pred=B.adj + G=nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n,B.node[n]) for n in nodes) + for u in nodes: + unbrs = set(B[u]) + nbrs2 = set((n for nbr in unbrs for n in B[nbr])) - set([u]) + for v in nbrs2: + vnbrs = set(pred[v]) + if jaccard: + weight = float(len(unbrs & vnbrs)) / len(unbrs | vnbrs) + else: + weight = float(len(unbrs & vnbrs)) / min(len(unbrs),len(vnbrs)) + G.add_edge(u,v,weight=weight) + return G + +def generic_weighted_projected_graph(B, nodes, weight_function=None): + r"""Weighted projection of B with a user-specified weight function. + + The bipartite network B is projected on to the specified nodes + with weights computed by a user-specified function. This function + must accept as a parameter the neighborhood sets of two nodes and + return an integer or a float. + + The nodes retain their attributes and are connected in the resulting graph + if they have an edge to a common node in the original graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + weight_function: function + This function must accept as parameters the same input graph + that this function, and two nodes; and return an integer or a float. + The default function computes the number of shared neighbors. + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> # Define some custom weight functions + >>> def jaccard(G, u, v): + ... unbrs = set(G[u]) + ... vnbrs = set(G[v]) + ... return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs) + ... + >>> def my_weight(G, u, v, weight='weight'): + ... w = 0 + ... for nbr in set(G[u]) & set(G[v]): + ... w += G.edge[u][nbr].get(weight, 1) + G.edge[v][nbr].get(weight, 1) + ... return w + ... + >>> # A complete bipartite graph with 4 nodes and 4 edges + >>> B = nx.complete_bipartite_graph(2,2) + >>> # Add some arbitrary weight to the edges + >>> for i,(u,v) in enumerate(B.edges()): + ... B.edge[u][v]['weight'] = i + 1 + ... + >>> for edge in B.edges(data=True): + ... print(edge) + ... + (0, 2, {'weight': 1}) + (0, 3, {'weight': 2}) + (1, 2, {'weight': 3}) + (1, 3, {'weight': 4}) + >>> # Without specifying a function, the weight is equal to # shared partners + >>> G = bipartite.generic_weighted_projected_graph(B, [0, 1]) + >>> print(G.edges(data=True)) + [(0, 1, {'weight': 2})] + >>> # To specify a custom weight function use the weight_function parameter + >>> G = bipartite.generic_weighted_projected_graph(B, [0, 1], weight_function=jaccard) + >>> print(G.edges(data=True)) + [(0, 1, {'weight': 1.0})] + >>> G = bipartite.generic_weighted_projected_graph(B, [0, 1], weight_function=my_weight) + >>> print(G.edges(data=True)) + [(0, 1, {'weight': 10})] + + Notes + ------ + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + collaboration_weighted_projected_graph, + overlap_weighted_projected_graph, + projected_graph + + """ + if B.is_multigraph(): + raise nx.NetworkXError("not defined for multigraphs") + if B.is_directed(): + pred=B.pred + G=nx.DiGraph() + else: + pred=B.adj + G=nx.Graph() + if weight_function is None: + def weight_function(G, u, v): + # Notice that we use set(pred[v]) for handling the directed case. + return len(set(G[u]) & set(pred[v])) + G.graph.update(B.graph) + G.add_nodes_from((n,B.node[n]) for n in nodes) + for u in nodes: + nbrs2 = set((n for nbr in set(B[u]) for n in B[nbr])) - set([u]) + for v in nbrs2: + weight = weight_function(B, u, v) + G.add_edge(u,v,weight=weight) + return G + +def project(B, nodes, create_using=None): + return projected_graph(B, nodes) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/redundancy.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/redundancy.py new file mode 100644 index 0000000..055fdcb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/redundancy.py @@ -0,0 +1,84 @@ +#-*- coding: utf-8 -*- +"""Node redundancy for bipartite graphs.""" +# Copyright (C) 2011 by +# Jordi Torrents +# Aric Hagberg +# All rights reserved. +# BSD license. +from itertools import combinations +import networkx as nx + +__author__ = """\n""".join(['Jordi Torrents ', + 'Aric Hagberg (hagberg@lanl.gov)']) +__all__ = ['node_redundancy'] + +def node_redundancy(G, nodes=None): + r"""Compute bipartite node redundancy coefficient. + + The redundancy coefficient of a node `v` is the fraction of pairs of + neighbors of `v` that are both linked to other nodes. In a one-mode + projection these nodes would be linked together even if `v` were + not there. + + .. math:: + + rc(v) = \frac{|\{\{u,w\} \subseteq N(v), + \: \exists v' \neq v,\: (v',u) \in E\: + \mathrm{and}\: (v',w) \in E\}|}{ \frac{|N(v)|(|N(v)|-1)}{2}} + + where `N(v)` are the neighbors of `v` in `G`. + + Parameters + ---------- + G : graph + A bipartite graph + + nodes : list or iterable (optional) + Compute redundancy for these nodes. The default is all nodes in G. + + Returns + ------- + redundancy : dictionary + A dictionary keyed by node with the node redundancy value. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.cycle_graph(4) + >>> rc = bipartite.node_redundancy(G) + >>> rc[0] + 1.0 + + Compute the average redundancy for the graph: + + >>> sum(rc.values())/len(G) + 1.0 + + Compute the average redundancy for a set of nodes: + + >>> nodes = [0, 2] + >>> sum(rc[n] for n in nodes)/len(nodes) + 1.0 + + References + ---------- + .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). + Basic notions for the analysis of large two-mode networks. + Social Networks 30(1), 31--48. + """ + if nodes is None: + nodes = G + rc = {} + for v in nodes: + overlap = 0.0 + for u, w in combinations(G[v], 2): + if len((set(G[u]) & set(G[w])) - set([v])) > 0: + overlap += 1 + if overlap > 0: + n = len(G[v]) + norm = 2.0/(n*(n-1)) + else: + norm = 1.0 + rc[v] = overlap*norm + return rc + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/spectral.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/spectral.py new file mode 100644 index 0000000..d0ebdd4 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/spectral.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +""" +Spectral bipartivity measure. +""" +import networkx as nx +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__all__ = ['spectral_bipartivity'] + +def spectral_bipartivity(G, nodes=None, weight='weight'): + """Returns the spectral bipartivity. + + Parameters + ---------- + G : NetworkX graph + + nodes : list or container optional(default is all nodes) + Nodes to return value of spectral bipartivity contribution. + + weight : string or None optional (default = 'weight') + Edge data key to use for edge weights. If None, weights set to 1. + + Returns + ------- + sb : float or dict + A single number if the keyword nodes is not specified, or + a dictionary keyed by node with the spectral bipartivity contribution + of that node as the value. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> bipartite.spectral_bipartivity(G) + 1.0 + + Notes + ----- + This implementation uses Numpy (dense) matrices which are not efficient + for storing large sparse graphs. + + See Also + -------- + color + + References + ---------- + .. [1] E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of + bipartivity in complex networks", PhysRev E 72, 046105 (2005) + """ + try: + import scipy.linalg + except ImportError: + raise ImportError('spectral_bipartivity() requires SciPy: ', + 'http://scipy.org/') + nodelist = G.nodes() # ordering of nodes in matrix + A = nx.to_numpy_matrix(G, nodelist, weight=weight) + expA = scipy.linalg.expm(A) + expmA = scipy.linalg.expm(-A) + coshA = 0.5 * (expA + expmA) + if nodes is None: + # return single number for entire graph + return coshA.diagonal().sum() / expA.diagonal().sum() + else: + # contribution for individual nodes + index = dict(zip(nodelist, range(len(nodelist)))) + sb = {} + for n in nodes: + i = index[n] + sb[n] = coshA[i, i] / expA[i, i] + return sb + +def setup_module(module): + """Fixture for nose tests.""" + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_basic.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_basic.py new file mode 100644 index 0000000..5b22e9a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_basic.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +from nose.plugins.attrib import attr +import networkx as nx +from networkx.algorithms import bipartite +class TestBipartiteBasic: + + def test_is_bipartite(self): + assert_true(bipartite.is_bipartite(nx.path_graph(4))) + assert_true(bipartite.is_bipartite(nx.DiGraph([(1,0)]))) + assert_false(bipartite.is_bipartite(nx.complete_graph(3))) + + + def test_bipartite_color(self): + G=nx.path_graph(4) + c=bipartite.color(G) + assert_equal(c,{0: 1, 1: 0, 2: 1, 3: 0}) + + @raises(nx.NetworkXError) + def test_not_bipartite_color(self): + c=bipartite.color(nx.complete_graph(4)) + + + def test_bipartite_directed(self): + G = nx.bipartite_random_graph(10, 10, 0.1, directed=True) + assert_true(bipartite.is_bipartite(G)) + + def test_bipartite_sets(self): + G=nx.path_graph(4) + X,Y=bipartite.sets(G) + assert_equal(X,set([0,2])) + assert_equal(Y,set([1,3])) + + def test_is_bipartite_node_set(self): + G=nx.path_graph(4) + assert_true(bipartite.is_bipartite_node_set(G,[0,2])) + assert_true(bipartite.is_bipartite_node_set(G,[1,3])) + assert_false(bipartite.is_bipartite_node_set(G,[1,2])) + G.add_path([10,20]) + assert_true(bipartite.is_bipartite_node_set(G,[0,2,10])) + assert_true(bipartite.is_bipartite_node_set(G,[0,2,20])) + assert_true(bipartite.is_bipartite_node_set(G,[1,3,10])) + assert_true(bipartite.is_bipartite_node_set(G,[1,3,20])) + + def test_bipartite_density(self): + G=nx.path_graph(5) + X,Y=bipartite.sets(G) + density=float(len(G.edges()))/(len(X)*len(Y)) + assert_equal(bipartite.density(G,X),density) + D = nx.DiGraph(G.edges()) + assert_equal(bipartite.density(D,X),density/2.0) + assert_equal(bipartite.density(nx.Graph(),{}),0.0) + + def test_bipartite_degrees(self): + G=nx.path_graph(5) + X=set([1,3]) + Y=set([0,2,4]) + u,d=bipartite.degrees(G,Y) + assert_equal(u,{1:2,3:2}) + assert_equal(d,{0:1,2:2,4:1}) + + def test_bipartite_weighted_degrees(self): + G=nx.path_graph(5) + G.add_edge(0,1,weight=0.1,other=0.2) + X=set([1,3]) + Y=set([0,2,4]) + u,d=bipartite.degrees(G,Y,weight='weight') + assert_equal(u,{1:1.1,3:2}) + assert_equal(d,{0:0.1,2:2,4:1}) + u,d=bipartite.degrees(G,Y,weight='other') + assert_equal(u,{1:1.2,3:2}) + assert_equal(d,{0:0.2,2:2,4:1}) + + + @attr('numpy') + def test_biadjacency_matrix_weight(self): + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + G=nx.path_graph(5) + G.add_edge(0,1,weight=2,other=4) + X=[1,3] + Y=[0,2,4] + M = bipartite.biadjacency_matrix(G,X,weight='weight') + assert_equal(M[0,0], 2) + M = bipartite.biadjacency_matrix(G, X, weight='other') + assert_equal(M[0,0], 4) + + @attr('numpy') + def test_biadjacency_matrix(self): + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + tops = [2,5,10] + bots = [5,10,15] + for i in range(len(tops)): + G = nx.bipartite_random_graph(tops[i], bots[i], 0.2) + top = [n for n,d in G.nodes(data=True) if d['bipartite']==0] + M = bipartite.biadjacency_matrix(G, top) + assert_equal(M.shape[0],tops[i]) + assert_equal(M.shape[1],bots[i]) + + @attr('numpy') + def test_biadjacency_matrix_order(self): + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + G=nx.path_graph(5) + G.add_edge(0,1,weight=2) + X=[3,1] + Y=[4,2,0] + M = bipartite.biadjacency_matrix(G,X,Y,weight='weight') + assert_equal(M[1,2], 2) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_centrality.py new file mode 100644 index 0000000..992d643 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_centrality.py @@ -0,0 +1,169 @@ +from nose.tools import * +import networkx as nx +from networkx.algorithms import bipartite + +class TestBipartiteCentrality(object): + + def setUp(self): + self.P4 = nx.path_graph(4) + self.K3 = nx.complete_bipartite_graph(3,3) + self.C4 = nx.cycle_graph(4) + self.davis = nx.davis_southern_women_graph() + self.top_nodes = [n for n,d in self.davis.nodes(data=True) + if d['bipartite']==0] + + def test_degree_centrality(self): + d = bipartite.degree_centrality(self.P4, [1,3]) + answer = {0: 0.5, 1: 1.0, 2: 1.0, 3: 0.5} + assert_equal(d, answer) + d = bipartite.degree_centrality(self.K3, [0,1,2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0} + assert_equal(d, answer) + d = bipartite.degree_centrality(self.C4, [0,2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0} + assert_equal(d,answer) + + def test_betweenness_centrality(self): + c = bipartite.betweenness_centrality(self.P4, [1,3]) + answer = {0: 0.0, 1: 1.0, 2: 1.0, 3: 0.0} + assert_equal(c, answer) + c = bipartite.betweenness_centrality(self.K3, [0,1,2]) + answer = {0: 0.125, 1: 0.125, 2: 0.125, 3: 0.125, 4: 0.125, 5: 0.125} + assert_equal(c, answer) + c = bipartite.betweenness_centrality(self.C4, [0,2]) + answer = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} + assert_equal(c, answer) + + def test_closeness_centrality(self): + c = bipartite.closeness_centrality(self.P4, [1,3]) + answer = {0: 2.0/3, 1: 1.0, 2: 1.0, 3:2.0/3} + assert_equal(c, answer) + c = bipartite.closeness_centrality(self.K3, [0,1,2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0} + assert_equal(c, answer) + c = bipartite.closeness_centrality(self.C4, [0,2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0} + assert_equal(c, answer) + G = nx.Graph() + G.add_node(0) + G.add_node(1) + c = bipartite.closeness_centrality(G, [0]) + assert_equal(c, {1: 0.0}) + c = bipartite.closeness_centrality(G, [1]) + assert_equal(c, {1: 0.0}) + + def test_davis_degree_centrality(self): + G = self.davis + deg = bipartite.degree_centrality(G, self.top_nodes) + answer = {'E8':0.78, + 'E9':0.67, + 'E7':0.56, + 'Nora Fayette':0.57, + 'Evelyn Jefferson':0.57, + 'Theresa Anderson':0.57, + 'E6':0.44, + 'Sylvia Avondale':0.50, + 'Laura Mandeville':0.50, + 'Brenda Rogers':0.50, + 'Katherina Rogers':0.43, + 'E5':0.44, + 'Helen Lloyd':0.36, + 'E3':0.33, + 'Ruth DeSand':0.29, + 'Verne Sanderson':0.29, + 'E12':0.33, + 'Myra Liddel':0.29, + 'E11':0.22, + 'Eleanor Nye':0.29, + 'Frances Anderson':0.29, + 'Pearl Oglethorpe':0.21, + 'E4':0.22, + 'Charlotte McDowd':0.29, + 'E10':0.28, + 'Olivia Carleton':0.14, + 'Flora Price':0.14, + 'E2':0.17, + 'E1':0.17, + 'Dorothy Murchison':0.14, + 'E13':0.17, + 'E14':0.17} + for node, value in answer.items(): + assert_almost_equal(value, deg[node], places=2) + + def test_davis_betweenness_centrality(self): + G = self.davis + bet = bipartite.betweenness_centrality(G, self.top_nodes) + answer = {'E8':0.24, + 'E9':0.23, + 'E7':0.13, + 'Nora Fayette':0.11, + 'Evelyn Jefferson':0.10, + 'Theresa Anderson':0.09, + 'E6':0.07, + 'Sylvia Avondale':0.07, + 'Laura Mandeville':0.05, + 'Brenda Rogers':0.05, + 'Katherina Rogers':0.05, + 'E5':0.04, + 'Helen Lloyd':0.04, + 'E3':0.02, + 'Ruth DeSand':0.02, + 'Verne Sanderson':0.02, + 'E12':0.02, + 'Myra Liddel':0.02, + 'E11':0.02, + 'Eleanor Nye':0.01, + 'Frances Anderson':0.01, + 'Pearl Oglethorpe':0.01, + 'E4':0.01, + 'Charlotte McDowd':0.01, + 'E10':0.01, + 'Olivia Carleton':0.01, + 'Flora Price':0.01, + 'E2':0.00, + 'E1':0.00, + 'Dorothy Murchison':0.00, + 'E13':0.00, + 'E14':0.00} + for node, value in answer.items(): + assert_almost_equal(value, bet[node], places=2) + + def test_davis_closeness_centrality(self): + G = self.davis + clos = bipartite.closeness_centrality(G, self.top_nodes) + answer = {'E8':0.85, + 'E9':0.79, + 'E7':0.73, + 'Nora Fayette':0.80, + 'Evelyn Jefferson':0.80, + 'Theresa Anderson':0.80, + 'E6':0.69, + 'Sylvia Avondale':0.77, + 'Laura Mandeville':0.73, + 'Brenda Rogers':0.73, + 'Katherina Rogers':0.73, + 'E5':0.59, + 'Helen Lloyd':0.73, + 'E3':0.56, + 'Ruth DeSand':0.71, + 'Verne Sanderson':0.71, + 'E12':0.56, + 'Myra Liddel':0.69, + 'E11':0.54, + 'Eleanor Nye':0.67, + 'Frances Anderson':0.67, + 'Pearl Oglethorpe':0.67, + 'E4':0.54, + 'Charlotte McDowd':0.60, + 'E10':0.55, + 'Olivia Carleton':0.59, + 'Flora Price':0.59, + 'E2':0.52, + 'E1':0.52, + 'Dorothy Murchison':0.65, + 'E13':0.52, + 'E14':0.52} + for node, value in answer.items(): + assert_almost_equal(value, clos[node], places=2) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_cluster.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_cluster.py new file mode 100644 index 0000000..aa158f9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_cluster.py @@ -0,0 +1,70 @@ +import networkx as nx +from nose.tools import * +from networkx.algorithms.bipartite.cluster import cc_dot,cc_min,cc_max +import networkx.algorithms.bipartite as bipartite + +def test_pairwise_bipartite_cc_functions(): + # Test functions for different kinds of bipartite clustering coefficients + # between pairs of nodes using 3 example graphs from figure 5 p. 40 + # Latapy et al (2008) + G1 = nx.Graph([(0,2),(0,3),(0,4),(0,5),(0,6),(1,5),(1,6),(1,7)]) + G2 = nx.Graph([(0,2),(0,3),(0,4),(1,3),(1,4),(1,5)]) + G3 = nx.Graph([(0,2),(0,3),(0,4),(0,5),(0,6),(1,5),(1,6),(1,7),(1,8),(1,9)]) + result = {0:[1/3.0, 2/3.0, 2/5.0], + 1:[1/2.0, 2/3.0, 2/3.0], + 2:[2/8.0, 2/5.0, 2/5.0]} + for i, G in enumerate([G1, G2, G3]): + assert(bipartite.is_bipartite(G)) + assert(cc_dot(set(G[0]), set(G[1])) == result[i][0]) + assert(cc_min(set(G[0]), set(G[1])) == result[i][1]) + assert(cc_max(set(G[0]), set(G[1])) == result[i][2]) + +def test_star_graph(): + G=nx.star_graph(3) + # all modes are the same + answer={0:0,1:1,2:1,3:1} + assert_equal(bipartite.clustering(G,mode='dot'),answer) + assert_equal(bipartite.clustering(G,mode='min'),answer) + assert_equal(bipartite.clustering(G,mode='max'),answer) + +@raises(nx.NetworkXError) +def test_not_bipartite(): + bipartite.clustering(nx.complete_graph(4)) + +@raises(nx.NetworkXError) +def test_bad_mode(): + bipartite.clustering(nx.path_graph(4),mode='foo') + +def test_path_graph(): + G=nx.path_graph(4) + answer={0:0.5,1:0.5,2:0.5,3:0.5} + assert_equal(bipartite.clustering(G,mode='dot'),answer) + assert_equal(bipartite.clustering(G,mode='max'),answer) + answer={0:1,1:1,2:1,3:1} + assert_equal(bipartite.clustering(G,mode='min'),answer) + +def test_average_path_graph(): + G=nx.path_graph(4) + assert_equal(bipartite.average_clustering(G,mode='dot'),0.5) + assert_equal(bipartite.average_clustering(G,mode='max'),0.5) + assert_equal(bipartite.average_clustering(G,mode='min'),1) + +def test_ra_clustering_davis(): + G = nx.davis_southern_women_graph() + cc4 = round(bipartite.robins_alexander_clustering(G), 3) + assert_equal(cc4, 0.468) + +def test_ra_clustering_square(): + G = nx.path_graph(4) + G.add_edge(0, 3) + assert_equal(bipartite.robins_alexander_clustering(G), 1.0) + +def test_ra_clustering_zero(): + G = nx.Graph() + assert_equal(bipartite.robins_alexander_clustering(G), 0) + G.add_nodes_from(range(4)) + assert_equal(bipartite.robins_alexander_clustering(G), 0) + G.add_edges_from([(0,1),(2,3),(3,4)]) + assert_equal(bipartite.robins_alexander_clustering(G), 0) + G.add_edge(1,2) + assert_equal(bipartite.robins_alexander_clustering(G), 0) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_project.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_project.py new file mode 100644 index 0000000..52a93b2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_project.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx.algorithms import bipartite +from networkx.testing import * + +class TestBipartiteProject: + + def test_path_projected_graph(self): + G=nx.path_graph(4) + P=bipartite.projected_graph(G,[1,3]) + assert_equal(sorted(P.nodes()),[1,3]) + assert_equal(sorted(P.edges()),[(1,3)]) + P=bipartite.projected_graph(G,[0,2]) + assert_equal(sorted(P.nodes()),[0,2]) + assert_equal(sorted(P.edges()),[(0,2)]) + + def test_path_projected_properties_graph(self): + G=nx.path_graph(4) + G.add_node(1,name='one') + G.add_node(2,name='two') + P=bipartite.projected_graph(G,[1,3]) + assert_equal(sorted(P.nodes()),[1,3]) + assert_equal(sorted(P.edges()),[(1,3)]) + assert_equal(P.node[1]['name'],G.node[1]['name']) + P=bipartite.projected_graph(G,[0,2]) + assert_equal(sorted(P.nodes()),[0,2]) + assert_equal(sorted(P.edges()),[(0,2)]) + assert_equal(P.node[2]['name'],G.node[2]['name']) + + def test_path_collaboration_projected_graph(self): + G=nx.path_graph(4) + P=bipartite.collaboration_weighted_projected_graph(G,[1,3]) + assert_equal(sorted(P.nodes()),[1,3]) + assert_equal(sorted(P.edges()),[(1,3)]) + P[1][3]['weight']=1 + P=bipartite.collaboration_weighted_projected_graph(G,[0,2]) + assert_equal(sorted(P.nodes()),[0,2]) + assert_equal(sorted(P.edges()),[(0,2)]) + P[0][2]['weight']=1 + + def test_directed_path_collaboration_projected_graph(self): + G=nx.DiGraph() + G.add_path(list(range(4))) + P=bipartite.collaboration_weighted_projected_graph(G,[1,3]) + assert_equal(sorted(P.nodes()),[1,3]) + assert_equal(sorted(P.edges()),[(1,3)]) + P[1][3]['weight']=1 + P=bipartite.collaboration_weighted_projected_graph(G,[0,2]) + assert_equal(sorted(P.nodes()),[0,2]) + assert_equal(sorted(P.edges()),[(0,2)]) + P[0][2]['weight']=1 + + def test_path_weighted_projected_graph(self): + G=nx.path_graph(4) + P=bipartite.weighted_projected_graph(G,[1,3]) + assert_equal(sorted(P.nodes()),[1,3]) + assert_equal(sorted(P.edges()),[(1,3)]) + P[1][3]['weight']=1 + P=bipartite.weighted_projected_graph(G,[0,2]) + assert_equal(sorted(P.nodes()),[0,2]) + assert_equal(sorted(P.edges()),[(0,2)]) + P[0][2]['weight']=1 + + def test_path_weighted_projected_directed_graph(self): + G=nx.DiGraph() + G.add_path(list(range(4))) + P=bipartite.weighted_projected_graph(G,[1,3]) + assert_equal(sorted(P.nodes()),[1,3]) + assert_equal(sorted(P.edges()),[(1,3)]) + P[1][3]['weight']=1 + P=bipartite.weighted_projected_graph(G,[0,2]) + assert_equal(sorted(P.nodes()),[0,2]) + assert_equal(sorted(P.edges()),[(0,2)]) + P[0][2]['weight']=1 + + + def test_star_projected_graph(self): + G=nx.star_graph(3) + P=bipartite.projected_graph(G,[1,2,3]) + assert_equal(sorted(P.nodes()),[1,2,3]) + assert_equal(sorted(P.edges()),[(1,2),(1,3),(2,3)]) + P=bipartite.weighted_projected_graph(G,[1,2,3]) + assert_equal(sorted(P.nodes()),[1,2,3]) + assert_equal(sorted(P.edges()),[(1,2),(1,3),(2,3)]) + + P=bipartite.projected_graph(G,[0]) + assert_equal(sorted(P.nodes()),[0]) + assert_equal(sorted(P.edges()),[]) + + def test_project_multigraph(self): + G=nx.Graph() + G.add_edge('a',1) + G.add_edge('b',1) + G.add_edge('a',2) + G.add_edge('b',2) + P=bipartite.projected_graph(G,'ab') + assert_edges_equal(P.edges(),[('a','b')]) + P=bipartite.weighted_projected_graph(G,'ab') + assert_edges_equal(P.edges(),[('a','b')]) + P=bipartite.projected_graph(G,'ab',multigraph=True) + assert_edges_equal(P.edges(),[('a','b'),('a','b')]) + + def test_project_collaboration(self): + G=nx.Graph() + G.add_edge('a',1) + G.add_edge('b',1) + G.add_edge('b',2) + G.add_edge('c',2) + G.add_edge('c',3) + G.add_edge('c',4) + G.add_edge('b',4) + P=bipartite.collaboration_weighted_projected_graph(G,'abc') + assert_equal(P['a']['b']['weight'],1) + assert_equal(P['b']['c']['weight'],2) + + def test_directed_projection(self): + G=nx.DiGraph() + G.add_edge('A',1) + G.add_edge(1,'B') + G.add_edge('A',2) + G.add_edge('B',2) + P=bipartite.projected_graph(G,'AB') + assert_equal(sorted(P.edges()),[('A','B')]) + P=bipartite.weighted_projected_graph(G,'AB') + assert_equal(sorted(P.edges()),[('A','B')]) + assert_equal(P['A']['B']['weight'],1) + + P=bipartite.projected_graph(G,'AB',multigraph=True) + assert_equal(sorted(P.edges()),[('A','B')]) + + G=nx.DiGraph() + G.add_edge('A',1) + G.add_edge(1,'B') + G.add_edge('A',2) + G.add_edge(2,'B') + P=bipartite.projected_graph(G,'AB') + assert_equal(sorted(P.edges()),[('A','B')]) + P=bipartite.weighted_projected_graph(G,'AB') + assert_equal(sorted(P.edges()),[('A','B')]) + assert_equal(P['A']['B']['weight'],2) + + P=bipartite.projected_graph(G,'AB',multigraph=True) + assert_equal(sorted(P.edges()),[('A','B'),('A','B')]) + + +class TestBipartiteWeightedProjection: + + def setUp(self): + # Tore Opsahl's example + # http://toreopsahl.com/2009/05/01/projecting-two-mode-networks-onto-weighted-one-mode-networks/ + self.G=nx.Graph() + self.G.add_edge('A',1) + self.G.add_edge('A',2) + self.G.add_edge('B',1) + self.G.add_edge('B',2) + self.G.add_edge('B',3) + self.G.add_edge('B',4) + self.G.add_edge('B',5) + self.G.add_edge('C',1) + self.G.add_edge('D',3) + self.G.add_edge('E',4) + self.G.add_edge('E',5) + self.G.add_edge('E',6) + self.G.add_edge('F',6) + # Graph based on figure 6 from Newman (2001) + self.N=nx.Graph() + self.N.add_edge('A',1) + self.N.add_edge('A',2) + self.N.add_edge('A',3) + self.N.add_edge('B',1) + self.N.add_edge('B',2) + self.N.add_edge('B',3) + self.N.add_edge('C',1) + self.N.add_edge('D',1) + self.N.add_edge('E',3) + + def test_project_weighted_shared(self): + edges=[('A','B',2), + ('A','C',1), + ('B','C',1), + ('B','D',1), + ('B','E',2), + ('E','F',1)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.weighted_projected_graph(self.G,'ABCDEF') + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + edges=[('A','B',3), + ('A','E',1), + ('A','C',1), + ('A','D',1), + ('B','E',1), + ('B','C',1), + ('B','D',1), + ('C','D',1)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.weighted_projected_graph(self.N,'ABCDE') + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + def test_project_weighted_newman(self): + edges=[('A','B',1.5), + ('A','C',0.5), + ('B','C',0.5), + ('B','D',1), + ('B','E',2), + ('E','F',1)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.collaboration_weighted_projected_graph(self.G,'ABCDEF') + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + edges=[('A','B',11/6.0), + ('A','E',1/2.0), + ('A','C',1/3.0), + ('A','D',1/3.0), + ('B','E',1/2.0), + ('B','C',1/3.0), + ('B','D',1/3.0), + ('C','D',1/3.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.collaboration_weighted_projected_graph(self.N,'ABCDE') + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + def test_project_weighted_ratio(self): + edges=[('A','B',2/6.0), + ('A','C',1/6.0), + ('B','C',1/6.0), + ('B','D',1/6.0), + ('B','E',2/6.0), + ('E','F',1/6.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.weighted_projected_graph(self.G, 'ABCDEF', ratio=True) + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + edges=[('A','B',3/3.0), + ('A','E',1/3.0), + ('A','C',1/3.0), + ('A','D',1/3.0), + ('B','E',1/3.0), + ('B','C',1/3.0), + ('B','D',1/3.0), + ('C','D',1/3.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.weighted_projected_graph(self.N, 'ABCDE', ratio=True) + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + def test_project_weighted_overlap(self): + edges=[('A','B',2/2.0), + ('A','C',1/1.0), + ('B','C',1/1.0), + ('B','D',1/1.0), + ('B','E',2/3.0), + ('E','F',1/1.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.overlap_weighted_projected_graph(self.G,'ABCDEF', jaccard=False) + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + edges=[('A','B',3/3.0), + ('A','E',1/1.0), + ('A','C',1/1.0), + ('A','D',1/1.0), + ('B','E',1/1.0), + ('B','C',1/1.0), + ('B','D',1/1.0), + ('C','D',1/1.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.overlap_weighted_projected_graph(self.N,'ABCDE', jaccard=False) + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + def test_project_weighted_jaccard(self): + edges=[('A','B',2/5.0), + ('A','C',1/2.0), + ('B','C',1/5.0), + ('B','D',1/5.0), + ('B','E',2/6.0), + ('E','F',1/3.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.overlap_weighted_projected_graph(self.G,'ABCDEF') + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + edges=[('A','B',3/3.0), + ('A','E',1/3.0), + ('A','C',1/3.0), + ('A','D',1/3.0), + ('B','E',1/3.0), + ('B','C',1/3.0), + ('B','D',1/3.0), + ('C','D',1/1.0)] + Panswer=nx.Graph() + Panswer.add_weighted_edges_from(edges) + P=bipartite.overlap_weighted_projected_graph(self.N,'ABCDE') + assert_equal(P.edges(),Panswer.edges()) + for u,v in P.edges(): + assert_equal(P[u][v]['weight'],Panswer[u][v]['weight']) + + def test_generic_weighted_projected_graph_simple(self): + def shared(G, u, v): + return len(set(G[u]) & set(G[v])) + B = nx.path_graph(5) + G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4], weight_function=shared) + assert_equal(sorted(G.nodes()), [0, 2, 4]) + assert_equal(G.edges(data=True), + [(0, 2, {'weight': 1}), (2, 4, {'weight': 1})] ) + + G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4]) + assert_equal(sorted(G.nodes()), [0, 2, 4]) + assert_equal(G.edges(data=True), + [(0, 2, {'weight': 1}), (2, 4, {'weight': 1})] ) + B = nx.DiGraph() + B.add_path(list(range(5))) + G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4]) + assert_equal(sorted(G.nodes()), [0, 2, 4]) + assert_equal(G.edges(data=True), + [(0, 2, {'weight': 1}), (2, 4, {'weight': 1})] ) + + def test_generic_weighted_projected_graph_custom(self): + def jaccard(G, u, v): + unbrs = set(G[u]) + vnbrs = set(G[v]) + return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs) + def my_weight(G, u, v, weight='weight'): + w = 0 + for nbr in set(G[u]) & set(G[v]): + w += G.edge[u][nbr].get(weight, 1) + G.edge[v][nbr].get(weight, 1) + return w + B = nx.complete_bipartite_graph(2,2) + for i,(u,v) in enumerate(B.edges()): + B.edge[u][v]['weight'] = i + 1 + G = bipartite.generic_weighted_projected_graph(B, [0, 1], + weight_function=jaccard) + assert_equal(G.edges(data=True), [(0, 1, {'weight': 1.0})]) + G = bipartite.generic_weighted_projected_graph(B, [0, 1], + weight_function=my_weight) + assert_equal(G.edges(data=True), [(0, 1, {'weight': 10})]) + G = bipartite.generic_weighted_projected_graph(B, [0, 1]) + assert_equal(G.edges(data=True), [(0, 1, {'weight': 2})]) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py new file mode 100644 index 0000000..e244a42 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from nose import SkipTest +from nose.tools import * +import networkx as nx +from networkx.algorithms.bipartite import spectral_bipartivity as sb + +# Examples from Figure 1 +# E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of +# bipartivity in complex networks", PhysRev E 72, 046105 (2005) + +class TestSpectralBipartivity(object): + @classmethod + def setupClass(cls): + global scipy + global assert_equal + global assert_almost_equal + try: + import scipy.linalg + except ImportError: + raise SkipTest('SciPy not available.') + + + def test_star_like(self): + # star-like + + G=nx.star_graph(2) + G.add_edge(1,2) + assert_almost_equal(sb(G),0.843,places=3) + + G=nx.star_graph(3) + G.add_edge(1,2) + assert_almost_equal(sb(G),0.871,places=3) + + G=nx.star_graph(4) + G.add_edge(1,2) + assert_almost_equal(sb(G),0.890,places=3) + + + def k23_like(self): + # K2,3-like + G=nx.complete_bipartite_graph(2,3) + G.add_edge(0,1) + assert_almost_equal(sb(G),0.769,places=3) + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(2,4) + assert_almost_equal(sb(G),0.829,places=3) + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(2,4) + G.add_edge(3,4) + assert_almost_equal(sb(G),0.731,places=3) + + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(0,1) + G.add_edge(2,4) + assert_almost_equal(sb(G),0.692,places=3) + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(2,4) + G.add_edge(3,4) + G.add_edge(0,1) + assert_almost_equal(sb(G),0.645,places=3) + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(2,4) + G.add_edge(3,4) + G.add_edge(2,3) + assert_almost_equal(sb(G),0.645,places=3) + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(2,4) + G.add_edge(3,4) + G.add_edge(2,3) + G.add_edge(0,1) + assert_almost_equal(sb(G),0.597,places=3) + + def test_single_nodes(self): + + # single nodes + G=nx.complete_bipartite_graph(2,3) + G.add_edge(2,4) + sbn=sb(G,nodes=[1,2]) + assert_almost_equal(sbn[1],0.85,places=2) + assert_almost_equal(sbn[2],0.77,places=2) + + G=nx.complete_bipartite_graph(2,3) + G.add_edge(0,1) + sbn=sb(G,nodes=[1,2]) + assert_almost_equal(sbn[1],0.73,places=2) + assert_almost_equal(sbn[2],0.82,places=2) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/block.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/block.py new file mode 100644 index 0000000..cc2ff1d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/block.py @@ -0,0 +1,115 @@ +# encoding: utf-8 +""" +Functions for creating network blockmodels from node partitions. + +Created by Drew Conway +Copyright (c) 2010. All rights reserved. +""" +__author__ = """\n""".join(['Drew Conway ', + 'Aric Hagberg ']) +__all__=['blockmodel'] + +import networkx as nx + +def blockmodel(G,partitions,multigraph=False): + """Returns a reduced graph constructed using the generalized block modeling + technique. + + The blockmodel technique collapses nodes into blocks based on a + given partitioning of the node set. Each partition of nodes + (block) is represented as a single node in the reduced graph. + + Edges between nodes in the block graph are added according to the + edges in the original graph. If the parameter multigraph is False + (the default) a single edge is added with a weight equal to the + sum of the edge weights between nodes in the original graph + The default is a weight of 1 if weights are not specified. If the + parameter multigraph is True then multiple edges are added each + with the edge data from the original graph. + + Parameters + ---------- + G : graph + A networkx Graph or DiGraph + partitions : list of lists, or list of sets + The partition of the nodes. Must be non-overlapping. + multigraph : bool, optional + If True return a MultiGraph with the edge data of the original + graph applied to each corresponding edge in the new graph. + If False return a Graph with the sum of the edge weights, or a + count of the edges if the original graph is unweighted. + + Returns + ------- + blockmodel : a Networkx graph object + + Examples + -------- + >>> G=nx.path_graph(6) + >>> partition=[[0,1],[2,3],[4,5]] + >>> M=nx.blockmodel(G,partition) + + References + ---------- + .. [1] Patrick Doreian, Vladimir Batagelj, and Anuska Ferligoj + "Generalized Blockmodeling",Cambridge University Press, 2004. + """ + # Create sets of node partitions + part=list(map(set,partitions)) + + # Check for overlapping node partitions + u=set() + for p1,p2 in zip(part[:-1],part[1:]): + u.update(p1) + #if not u.isdisjoint(p2): # Python 2.6 required + if len (u.intersection(p2))>0: + raise nx.NetworkXException("Overlapping node partitions.") + + # Initialize blockmodel graph + if multigraph: + if G.is_directed(): + M=nx.MultiDiGraph() + else: + M=nx.MultiGraph() + else: + if G.is_directed(): + M=nx.DiGraph() + else: + M=nx.Graph() + + # Add nodes and properties to blockmodel + # The blockmodel nodes are node-induced subgraphs of G + # Label them with integers starting at 0 + for i,p in zip(range(len(part)),part): + M.add_node(i) + # The node-induced subgraph is stored as the node 'graph' attribute + SG=G.subgraph(p) + M.node[i]['graph']=SG + M.node[i]['nnodes']=SG.number_of_nodes() + M.node[i]['nedges']=SG.number_of_edges() + M.node[i]['density']=nx.density(SG) + + # Create mapping between original node labels and new blockmodel node labels + block_mapping={} + for n in M: + nodes_in_block=M.node[n]['graph'].nodes() + block_mapping.update(dict.fromkeys(nodes_in_block,n)) + + # Add edges to block graph + for u,v,d in G.edges(data=True): + bmu=block_mapping[u] + bmv=block_mapping[v] + if bmu==bmv: # no self loops + continue + if multigraph: + # For multigraphs add an edge for each edge in original graph + M.add_edge(bmu,bmv,attr_dict=d) + else: + # For graphs and digraphs add single weighted edge + weight=d.get('weight',1.0) # default to 1 if no weight specified + if M.has_edge(bmu,bmv): + M[bmu][bmv]['weight']+=weight + else: + M.add_edge(bmu,bmv,weight=weight) + return M + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/boundary.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/boundary.py new file mode 100644 index 0000000..ec7b11c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/boundary.py @@ -0,0 +1,102 @@ +""" +Routines to find the boundary of a set of nodes. + +Edge boundaries are edges that have only one end +in the set of nodes. + +Node boundaries are nodes outside the set of nodes +that have an edge to a node in the set. + +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)\nDan Schult (dschult@colgate.edu)""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__=['edge_boundary','node_boundary'] + +def edge_boundary(G, nbunch1, nbunch2=None): + """Return the edge boundary. + + Edge boundaries are edges that have only one end + in the given set of nodes. + + Parameters + ----------- + G : graph + A networkx graph + + nbunch1 : list, container + Interior node set + + nbunch2 : list, container + Exterior node set. If None then it is set to all of the + nodes in G not in nbunch1. + + Returns + ------- + elist : list + List of edges + + Notes + ------ + Nodes in nbunch1 and nbunch2 that are not in G are ignored. + + nbunch1 and nbunch2 are usually meant to be disjoint, + but in the interest of speed and generality, that is + not required here. + + """ + if nbunch2 is None: # Then nbunch2 is complement of nbunch1 + nset1=set((n for n in nbunch1 if n in G)) + return [(n1,n2) for n1 in nset1 for n2 in G[n1] \ + if n2 not in nset1] + + nset2=set(nbunch2) + return [(n1,n2) for n1 in nbunch1 if n1 in G for n2 in G[n1] \ + if n2 in nset2] + +def node_boundary(G, nbunch1, nbunch2=None): + """Return the node boundary. + + The node boundary is all nodes in the edge boundary of a given + set of nodes that are in the set. + + Parameters + ----------- + G : graph + A networkx graph + + nbunch1 : list, container + Interior node set + + nbunch2 : list, container + Exterior node set. If None then it is set to all of the + nodes in G not in nbunch1. + + Returns + ------- + nlist : list + List of nodes. + + Notes + ------ + Nodes in nbunch1 and nbunch2 that are not in G are ignored. + + nbunch1 and nbunch2 are usually meant to be disjoint, + but in the interest of speed and generality, that is + not required here. + + """ + nset1=set(n for n in nbunch1 if n in G) + bdy=set() + for n1 in nset1: + bdy.update(G[n1]) + bdy -= nset1 + if nbunch2 is not None: # else nbunch2 is complement of nbunch1 + bdy &= set(nbunch2) + return list(bdy) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/__init__.py new file mode 100644 index 0000000..d60154b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/__init__.py @@ -0,0 +1,20 @@ +from networkx.algorithms.centrality.betweenness import * +from networkx.algorithms.centrality.betweenness_subset import * +from networkx.algorithms.centrality.closeness import * +from networkx.algorithms.centrality.current_flow_closeness import * +from networkx.algorithms.centrality.current_flow_betweenness import * +from networkx.algorithms.centrality.current_flow_betweenness_subset import * +from networkx.algorithms.centrality.degree_alg import * +from networkx.algorithms.centrality.eigenvector import * +from networkx.algorithms.centrality.katz import * +from networkx.algorithms.centrality.load import * +from networkx.algorithms.centrality.communicability_alg import * +import networkx.algorithms.centrality.betweenness +import networkx.algorithms.centrality.closeness +import networkx.algorithms.centrality.current_flow_betweenness +import networkx.algorithms.centrality.current_flow_closeness +import networkx.algorithms.centrality.degree_alg +import networkx.algorithms.centrality.eigenvector +import networkx.algorithms.centrality.load +import networkx.algorithms.centrality.communicability_alg +import networkx.algorithms.centrality.katz diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness.py new file mode 100644 index 0000000..af2cf6a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness.py @@ -0,0 +1,334 @@ +""" +Betweenness centrality measures. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import heapq +import networkx as nx +import random +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" + +__all__ = ['betweenness_centrality', + 'edge_betweenness_centrality', + 'edge_betweenness'] + +def betweenness_centrality(G, k=None, normalized=True, weight=None, + endpoints=False, + seed=None): + r"""Compute the shortest-path betweenness centrality for nodes. + + Betweenness centrality of a node `v` is the sum of the + fraction of all-pairs shortest paths that pass through `v`: + + .. math:: + + c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)} + + where `V` is the set of nodes, `\sigma(s, t)` is the number of + shortest `(s, t)`-paths, and `\sigma(s, t|v)` is the number of those + paths passing through some node `v` other than `s, t`. + If `s = t`, `\sigma(s, t) = 1`, and if `v \in {s, t}`, + `\sigma(s, t|v) = 0` [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + k : int, optional (default=None) + If k is not None use k node samples to estimate betweenness. + The value of k <= n where n is the number of nodes in the graph. + Higher values give better approximation. + + normalized : bool, optional + If True the betweenness values are normalized by `2/((n-1)(n-2))` + for graphs, and `1/((n-1)(n-2))` for directed graphs where `n` + is the number of nodes in G. + + weight : None or string, optional + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + + endpoints : bool, optional + If True include the endpoints in the shortest path counts. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + edge_betweenness_centrality + load_centrality + + Notes + ----- + The algorithm is from Ulrik Brandes [1]_. + See [2]_ for details on algorithms for variations and related metrics. + + For approximate betweenness calculations set k=#samples to use + k nodes ("pivots") to estimate the betweenness values. For an estimate + of the number of pivots needed see [3]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + References + ---------- + .. [1] A Faster Algorithm for Betweenness Centrality. + Ulrik Brandes, + Journal of Mathematical Sociology 25(2):163-177, 2001. + http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf + .. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf + .. [3] Ulrik Brandes and Christian Pich: + Centrality Estimation in Large Networks. + International Journal of Bifurcation and Chaos 17(7):2303-2318, 2007. + http://www.inf.uni-konstanz.de/algo/publications/bp-celn-06.pdf + """ + betweenness=dict.fromkeys(G,0.0) # b[v]=0 for v in G + if k is None: + nodes = G + else: + random.seed(seed) + nodes = random.sample(G.nodes(), k) + for s in nodes: + # single source shortest paths + if weight is None: # use BFS + S,P,sigma=_single_source_shortest_path_basic(G,s) + else: # use Dijkstra's algorithm + S,P,sigma=_single_source_dijkstra_path_basic(G,s,weight) + # accumulation + if endpoints: + betweenness=_accumulate_endpoints(betweenness,S,P,sigma,s) + else: + betweenness=_accumulate_basic(betweenness,S,P,sigma,s) + # rescaling + betweenness=_rescale(betweenness, len(G), + normalized=normalized, + directed=G.is_directed(), + k=k) + return betweenness + + +def edge_betweenness_centrality(G,normalized=True,weight=None): + r"""Compute betweenness centrality for edges. + + Betweenness centrality of an edge `e` is the sum of the + fraction of all-pairs shortest paths that pass through `e`: + + .. math:: + + c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|e)}{\sigma(s, t)} + + where `V` is the set of nodes,`\sigma(s, t)` is the number of + shortest `(s, t)`-paths, and `\sigma(s, t|e)` is the number of + those paths passing through edge `e` [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional + If True the betweenness values are normalized by `2/(n(n-1))` + for graphs, and `1/(n(n-1))` for directed graphs where `n` + is the number of nodes in G. + + weight : None or string, optional + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + + Returns + ------- + edges : dictionary + Dictionary of edges with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_load + + Notes + ----- + The algorithm is from Ulrik Brandes [1]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + References + ---------- + .. [1] A Faster Algorithm for Betweenness Centrality. Ulrik Brandes, + Journal of Mathematical Sociology 25(2):163-177, 2001. + http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf + .. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf + """ + betweenness=dict.fromkeys(G,0.0) # b[v]=0 for v in G + # b[e]=0 for e in G.edges() + betweenness.update(dict.fromkeys(G.edges(),0.0)) + for s in G: + # single source shortest paths + if weight is None: # use BFS + S,P,sigma=_single_source_shortest_path_basic(G,s) + else: # use Dijkstra's algorithm + S,P,sigma=_single_source_dijkstra_path_basic(G,s,weight) + # accumulation + betweenness=_accumulate_edges(betweenness,S,P,sigma,s) + # rescaling + for n in G: # remove nodes to only return edges + del betweenness[n] + betweenness=_rescale_e(betweenness, len(G), + normalized=normalized, + directed=G.is_directed()) + return betweenness + +# obsolete name +def edge_betweenness(G,normalized=True,weight=None): + return edge_betweenness_centrality(G,normalized,weight) + + +# helpers for betweenness centrality + +def _single_source_shortest_path_basic(G,s): + S=[] + P={} + for v in G: + P[v]=[] + sigma=dict.fromkeys(G,0.0) # sigma[v]=0 for v in G + D={} + sigma[s]=1.0 + D[s]=0 + Q=[s] + while Q: # use BFS to find shortest paths + v=Q.pop(0) + S.append(v) + Dv=D[v] + sigmav=sigma[v] + for w in G[v]: + if w not in D: + Q.append(w) + D[w]=Dv+1 + if D[w]==Dv+1: # this is a shortest path, count paths + sigma[w] += sigmav + P[w].append(v) # predecessors + return S,P,sigma + + + +def _single_source_dijkstra_path_basic(G,s,weight='weight'): + # modified from Eppstein + S=[] + P={} + for v in G: + P[v]=[] + sigma=dict.fromkeys(G,0.0) # sigma[v]=0 for v in G + D={} + sigma[s]=1.0 + push=heapq.heappush + pop=heapq.heappop + seen = {s:0} + Q=[] # use Q as heap with (distance,node id) tuples + push(Q,(0,s,s)) + while Q: + (dist,pred,v)=pop(Q) + if v in D: + continue # already searched this node. + sigma[v] += sigma[pred] # count paths + S.append(v) + D[v] = dist + for w,edgedata in G[v].items(): + vw_dist = dist + edgedata.get(weight,1) + if w not in D and (w not in seen or vw_dist < seen[w]): + seen[w] = vw_dist + push(Q,(vw_dist,v,w)) + sigma[w]=0.0 + P[w]=[v] + elif vw_dist==seen[w]: # handle equal paths + sigma[w] += sigma[v] + P[w].append(v) + return S,P,sigma + +def _accumulate_basic(betweenness,S,P,sigma,s): + delta=dict.fromkeys(S,0) + while S: + w=S.pop() + coeff=(1.0+delta[w])/sigma[w] + for v in P[w]: + delta[v] += sigma[v]*coeff + if w != s: + betweenness[w]+=delta[w] + return betweenness + +def _accumulate_endpoints(betweenness,S,P,sigma,s): + betweenness[s]+=len(S)-1 + delta=dict.fromkeys(S,0) + while S: + w=S.pop() + coeff=(1.0+delta[w])/sigma[w] + for v in P[w]: + delta[v] += sigma[v]*coeff + if w != s: + betweenness[w] += delta[w]+1 + return betweenness + +def _accumulate_edges(betweenness,S,P,sigma,s): + delta=dict.fromkeys(S,0) + while S: + w=S.pop() + coeff=(1.0+delta[w])/sigma[w] + for v in P[w]: + c=sigma[v]*coeff + if (v,w) not in betweenness: + betweenness[(w,v)]+=c + else: + betweenness[(v,w)]+=c + delta[v]+=c + if w != s: + betweenness[w]+=delta[w] + return betweenness + +def _rescale(betweenness,n,normalized,directed=False,k=None): + if normalized is True: + if n <=2: + scale=None # no normalization b=0 for all nodes + else: + scale=1.0/((n-1)*(n-2)) + else: # rescale by 2 for undirected graphs + if not directed: + scale=1.0/2.0 + else: + scale=None + if scale is not None: + if k is not None: + scale=scale*n/k + for v in betweenness: + betweenness[v] *= scale + return betweenness + +def _rescale_e(betweenness,n,normalized,directed=False): + if normalized is True: + if n <=1: + scale=None # no normalization b=0 for all nodes + else: + scale=1.0/(n*(n-1)) + else: # rescale by 2 for undirected graphs + if not directed: + scale=1.0/2.0 + else: + scale=None + if scale is not None: + for v in betweenness: + betweenness[v] *= scale + return betweenness diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness_subset.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness_subset.py new file mode 100644 index 0000000..cd4f8a2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/betweenness_subset.py @@ -0,0 +1,263 @@ +""" +Betweenness centrality measures for subsets of nodes. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" + +__all__ = ['betweenness_centrality_subset', + 'edge_betweenness_centrality_subset', + 'betweenness_centrality_source'] + +import networkx as nx + +from networkx.algorithms.centrality.betweenness import\ + _single_source_dijkstra_path_basic as dijkstra +from networkx.algorithms.centrality.betweenness import\ + _single_source_shortest_path_basic as shortest_path + + +def betweenness_centrality_subset(G,sources,targets, + normalized=False, + weight=None): + """Compute betweenness centrality for a subset of nodes. + + .. math:: + + c_B(v) =\sum_{s\in S, t \in T} \frac{\sigma(s, t|v)}{\sigma(s, t)} + + where `S` is the set of sources, `T` is the set of targets, + `\sigma(s, t)` is the number of shortest `(s, t)`-paths, + and `\sigma(s, t|v)` is the number of those paths + passing through some node `v` other than `s, t`. + If `s = t`, `\sigma(s, t) = 1`, + and if `v \in {s, t}`, `\sigma(s, t|v) = 0` [2]_. + + + Parameters + ---------- + G : graph + + sources: list of nodes + Nodes to use as sources for shortest paths in betweenness + + targets: list of nodes + Nodes to use as targets for shortest paths in betweenness + + normalized : bool, optional + If True the betweenness values are normalized by `2/((n-1)(n-2))` + for graphs, and `1/((n-1)(n-2))` for directed graphs where `n` + is the number of nodes in G. + + weight : None or string, optional + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + edge_betweenness_centrality + load_centrality + + Notes + ----- + The basic algorithm is from [1]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + The normalization might seem a little strange but it is the same + as in betweenness_centrality() and is designed to make + betweenness_centrality(G) be the same as + betweenness_centrality_subset(G,sources=G.nodes(),targets=G.nodes()). + + + References + ---------- + .. [1] Ulrik Brandes, A Faster Algorithm for Betweenness Centrality. + Journal of Mathematical Sociology 25(2):163-177, 2001. + http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf + .. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf + """ + b=dict.fromkeys(G,0.0) # b[v]=0 for v in G + for s in sources: + # single source shortest paths + if weight is None: # use BFS + S,P,sigma=shortest_path(G,s) + else: # use Dijkstra's algorithm + S,P,sigma=dijkstra(G,s,weight) + b=_accumulate_subset(b,S,P,sigma,s,targets) + b=_rescale(b,len(G),normalized=normalized,directed=G.is_directed()) + return b + + +def edge_betweenness_centrality_subset(G,sources,targets, + normalized=False, + weight=None): + """Compute betweenness centrality for edges for a subset of nodes. + + .. math:: + + c_B(v) =\sum_{s\in S,t \in T} \frac{\sigma(s, t|e)}{\sigma(s, t)} + + where `S` is the set of sources, `T` is the set of targets, + `\sigma(s, t)` is the number of shortest `(s, t)`-paths, + and `\sigma(s, t|e)` is the number of those paths + passing through edge `e` [2]_. + + Parameters + ---------- + G : graph + A networkx graph + + sources: list of nodes + Nodes to use as sources for shortest paths in betweenness + + targets: list of nodes + Nodes to use as targets for shortest paths in betweenness + + normalized : bool, optional + If True the betweenness values are normalized by `2/(n(n-1))` + for graphs, and `1/(n(n-1))` for directed graphs where `n` + is the number of nodes in G. + + weight : None or string, optional + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + + Returns + ------- + edges : dictionary + Dictionary of edges with Betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_load + + Notes + ----- + The basic algorithm is from [1]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + The normalization might seem a little strange but it is the same + as in edge_betweenness_centrality() and is designed to make + edge_betweenness_centrality(G) be the same as + edge_betweenness_centrality_subset(G,sources=G.nodes(),targets=G.nodes()). + + References + ---------- + .. [1] Ulrik Brandes, A Faster Algorithm for Betweenness Centrality. + Journal of Mathematical Sociology 25(2):163-177, 2001. + http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf + .. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + http://www.inf.uni-konstanz.de/algo/publications/b-vspbc-08.pdf + + """ + + b=dict.fromkeys(G,0.0) # b[v]=0 for v in G + b.update(dict.fromkeys(G.edges(),0.0)) # b[e] for e in G.edges() + for s in sources: + # single source shortest paths + if weight is None: # use BFS + S,P,sigma=shortest_path(G,s) + else: # use Dijkstra's algorithm + S,P,sigma=dijkstra(G,s,weight) + b=_accumulate_edges_subset(b,S,P,sigma,s,targets) + for n in G: # remove nodes to only return edges + del b[n] + b=_rescale_e(b,len(G),normalized=normalized,directed=G.is_directed()) + return b + +# obsolete name +def betweenness_centrality_source(G,normalized=True,weight=None,sources=None): + if sources is None: + sources=G.nodes() + targets=G.nodes() + return betweenness_centrality_subset(G,sources,targets,normalized,weight) + + +def _accumulate_subset(betweenness,S,P,sigma,s,targets): + delta=dict.fromkeys(S,0) + target_set=set(targets) + while S: + w=S.pop() + for v in P[w]: + if w in target_set: + delta[v]+=(sigma[v]/sigma[w])*(1.0+delta[w]) + else: + delta[v]+=delta[w]/len(P[w]) + if w != s: + betweenness[w]+=delta[w] + return betweenness + +def _accumulate_edges_subset(betweenness,S,P,sigma,s,targets): + delta=dict.fromkeys(S,0) + target_set=set(targets) + while S: + w=S.pop() + for v in P[w]: + if w in target_set: + c=(sigma[v]/sigma[w])*(1.0+delta[w]) + else: + c=delta[w]/len(P[w]) + if (v,w) not in betweenness: + betweenness[(w,v)]+=c + else: + betweenness[(v,w)]+=c + delta[v]+=c + if w != s: + betweenness[w]+=delta[w] + return betweenness + + + + +def _rescale(betweenness,n,normalized,directed=False): + if normalized is True: + if n <=2: + scale=None # no normalization b=0 for all nodes + else: + scale=1.0/((n-1)*(n-2)) + else: # rescale by 2 for undirected graphs + if not directed: + scale=1.0/2.0 + else: + scale=None + if scale is not None: + for v in betweenness: + betweenness[v] *= scale + return betweenness + +def _rescale_e(betweenness,n,normalized,directed=False): + if normalized is True: + if n <=1: + scale=None # no normalization b=0 for all nodes + else: + scale=1.0/(n*(n-1)) + else: # rescale by 2 for undirected graphs + if not directed: + scale=1.0/2.0 + else: + scale=None + if scale is not None: + for v in betweenness: + betweenness[v] *= scale + return betweenness diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/closeness.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/closeness.py new file mode 100644 index 0000000..67d8089 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/closeness.py @@ -0,0 +1,103 @@ +""" +Closeness centrality measures. +""" +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import functools +import networkx as nx +__author__ = "\n".join(['Aric Hagberg ', + 'Pieter Swart (swart@lanl.gov)', + 'Sasha Gutfraind (ag362@cornell.edu)']) +__all__ = ['closeness_centrality'] + + +def closeness_centrality(G, u=None, distance=None, normalized=True): + r"""Compute closeness centrality for nodes. + + Closeness centrality [1]_ of a node `u` is the reciprocal of the + sum of the shortest path distances from `u` to all `n-1` other nodes. + Since the sum of distances depends on the number of nodes in the + graph, closeness is normalized by the sum of minimum possible + distances `n-1`. + + .. math:: + + C(u) = \frac{n - 1}{\sum_{v=1}^{n} d(v, u)}, + + where `d(v, u)` is the shortest-path distance between `v` and `u`, + and `n` is the number of nodes in the graph. + + Notice that higher values of closeness indicate higher centrality. + + Parameters + ---------- + G : graph + A NetworkX graph + u : node, optional + Return only the value for node u + distance : edge attribute key, optional (default=None) + Use the specified edge attribute as the edge distance in shortest + path calculations + normalized : bool, optional + If True (default) normalize by the number of nodes in the connected + part of the graph. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with closeness centrality as the value. + + See Also + -------- + betweenness_centrality, load_centrality, eigenvector_centrality, + degree_centrality + + Notes + ----- + The closeness centrality is normalized to `(n-1)/(|G|-1)` where + `n` is the number of nodes in the connected part of graph + containing the node. If the graph is not completely connected, + this algorithm computes the closeness centrality for each + connected part separately. + + If the 'distance' keyword is set to an edge attribute key then the + shortest-path length will be computed using Dijkstra's algorithm with + that edge attribute as the edge weight. + + References + ---------- + .. [1] Freeman, L.C., 1979. Centrality in networks: I. + Conceptual clarification. Social Networks 1, 215--239. + http://www.soc.ucsb.edu/faculty/friedkin/Syllabi/Soc146/Freeman78.PDF + """ + if distance is not None: + # use Dijkstra's algorithm with specified attribute as edge weight + path_length = functools.partial(nx.single_source_dijkstra_path_length, + weight=distance) + else: + path_length = nx.single_source_shortest_path_length + + if u is None: + nodes = G.nodes() + else: + nodes = [u] + closeness_centrality = {} + for n in nodes: + sp = path_length(G,n) + totsp = sum(sp.values()) + if totsp > 0.0 and len(G) > 1: + closeness_centrality[n] = (len(sp)-1.0) / totsp + # normalize to number of nodes-1 in connected part + if normalized: + s = (len(sp)-1.0) / ( len(G) - 1 ) + closeness_centrality[n] *= s + else: + closeness_centrality[n] = 0.0 + if u is not None: + return closeness_centrality[u] + else: + return closeness_centrality diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/communicability_alg.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/communicability_alg.py new file mode 100644 index 0000000..3c8fc6b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/communicability_alg.py @@ -0,0 +1,495 @@ +""" +Communicability and centrality measures. +""" +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import * +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Franck Kalala (franckkalala@yahoo.fr']) +__all__ = ['communicability_centrality_exp', + 'communicability_centrality', + 'communicability_betweenness_centrality', + 'communicability', + 'communicability_exp', + 'estrada_index', + ] + +@require('scipy') +@not_implemented_for('directed') +@not_implemented_for('multigraph') +def communicability_centrality_exp(G): + r"""Return the communicability centrality for each node of G + + Communicability centrality, also called subgraph centrality, of a node `n` + is the sum of closed walks of all lengths starting and ending at node `n`. + + Parameters + ---------- + G: graph + + Returns + ------- + nodes:dictionary + Dictionary of nodes with communicability centrality as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + communicability: + Communicability between all pairs of nodes in G. + communicability_centrality: + Communicability centrality for each node of G. + + Notes + ----- + This version of the algorithm exponentiates the adjacency matrix. + The communicability centrality of a node `u` in G can be found using + the matrix exponential of the adjacency matrix of G [1]_ [2]_, + + .. math:: + + SC(u)=(e^A)_{uu} . + + References + ---------- + .. [1] Ernesto Estrada, Juan A. Rodriguez-Velazquez, + "Subgraph centrality in complex networks", + Physical Review E 71, 056103 (2005). + http://arxiv.org/abs/cond-mat/0504730 + + .. [2] Ernesto Estrada, Naomichi Hatano, + "Communicability in complex networks", + Phys. Rev. E 77, 036111 (2008). + http://arxiv.org/abs/0707.0756 + + Examples + -------- + >>> G = nx.Graph([(0,1),(1,2),(1,5),(5,4),(2,4),(2,3),(4,3),(3,6)]) + >>> sc = nx.communicability_centrality_exp(G) + """ + # alternative implementation that calculates the matrix exponential + import scipy.linalg + nodelist = G.nodes() # ordering of nodes in matrix + A = nx.to_numpy_matrix(G,nodelist) + # convert to 0-1 matrix + A[A!=0.0] = 1 + expA = scipy.linalg.expm(A) + # convert diagonal to dictionary keyed by node + sc = dict(zip(nodelist,map(float,expA.diagonal()))) + return sc + +@require('numpy') +@not_implemented_for('directed') +@not_implemented_for('multigraph') +def communicability_centrality(G): + r"""Return communicability centrality for each node in G. + + Communicability centrality, also called subgraph centrality, of a node `n` + is the sum of closed walks of all lengths starting and ending at node `n`. + + Parameters + ---------- + G: graph + + Returns + ------- + nodes: dictionary + Dictionary of nodes with communicability centrality as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + communicability: + Communicability between all pairs of nodes in G. + communicability_centrality: + Communicability centrality for each node of G. + + Notes + ----- + This version of the algorithm computes eigenvalues and eigenvectors + of the adjacency matrix. + + Communicability centrality of a node `u` in G can be found using + a spectral decomposition of the adjacency matrix [1]_ [2]_, + + .. math:: + + SC(u)=\sum_{j=1}^{N}(v_{j}^{u})^2 e^{\lambda_{j}}, + + where `v_j` is an eigenvector of the adjacency matrix `A` of G + corresponding corresponding to the eigenvalue `\lambda_j`. + + Examples + -------- + >>> G = nx.Graph([(0,1),(1,2),(1,5),(5,4),(2,4),(2,3),(4,3),(3,6)]) + >>> sc = nx.communicability_centrality(G) + + References + ---------- + .. [1] Ernesto Estrada, Juan A. Rodriguez-Velazquez, + "Subgraph centrality in complex networks", + Physical Review E 71, 056103 (2005). + http://arxiv.org/abs/cond-mat/0504730 + .. [2] Ernesto Estrada, Naomichi Hatano, + "Communicability in complex networks", + Phys. Rev. E 77, 036111 (2008). + http://arxiv.org/abs/0707.0756 + """ + import numpy + import numpy.linalg + nodelist = G.nodes() # ordering of nodes in matrix + A = nx.to_numpy_matrix(G,nodelist) + # convert to 0-1 matrix + A[A!=0.0] = 1 + w,v = numpy.linalg.eigh(A) + vsquare = numpy.array(v)**2 + expw = numpy.exp(w) + xg = numpy.dot(vsquare,expw) + # convert vector dictionary keyed by node + sc = dict(zip(nodelist,map(float,xg))) + return sc + +@require('scipy') +@not_implemented_for('directed') +@not_implemented_for('multigraph') +def communicability_betweenness_centrality(G, normalized=True): + r"""Return communicability betweenness for all pairs of nodes in G. + + Communicability betweenness measure makes use of the number of walks + connecting every pair of nodes as the basis of a betweenness centrality + measure. + + Parameters + ---------- + G: graph + + Returns + ------- + nodes:dictionary + Dictionary of nodes with communicability betweenness as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + communicability: + Communicability between all pairs of nodes in G. + communicability_centrality: + Communicability centrality for each node of G using matrix exponential. + communicability_centrality_exp: + Communicability centrality for each node in G using + spectral decomposition. + + Notes + ----- + Let `G=(V,E)` be a simple undirected graph with `n` nodes and `m` edges, + and `A` denote the adjacency matrix of `G`. + + Let `G(r)=(V,E(r))` be the graph resulting from + removing all edges connected to node `r` but not the node itself. + + The adjacency matrix for `G(r)` is `A+E(r)`, where `E(r)` has nonzeros + only in row and column `r`. + + The communicability betweenness of a node `r` is [1]_ + + .. math:: + \omega_{r} = \frac{1}{C}\sum_{p}\sum_{q}\frac{G_{prq}}{G_{pq}}, + p\neq q, q\neq r, + + where + `G_{prq}=(e^{A}_{pq} - (e^{A+E(r)})_{pq}` is the number of walks + involving node r, + `G_{pq}=(e^{A})_{pq}` is the number of closed walks starting + at node `p` and ending at node `q`, + and `C=(n-1)^{2}-(n-1)` is a normalization factor equal to the + number of terms in the sum. + + The resulting `\omega_{r}` takes values between zero and one. + The lower bound cannot be attained for a connected + graph, and the upper bound is attained in the star graph. + + References + ---------- + .. [1] Ernesto Estrada, Desmond J. Higham, Naomichi Hatano, + "Communicability Betweenness in Complex Networks" + Physica A 388 (2009) 764-774. + http://arxiv.org/abs/0905.4102 + + Examples + -------- + >>> G = nx.Graph([(0,1),(1,2),(1,5),(5,4),(2,4),(2,3),(4,3),(3,6)]) + >>> cbc = nx.communicability_betweenness_centrality(G) + """ + import scipy + import scipy.linalg + nodelist = G.nodes() # ordering of nodes in matrix + n = len(nodelist) + A = nx.to_numpy_matrix(G,nodelist) + # convert to 0-1 matrix + A[A!=0.0] = 1 + expA = scipy.linalg.expm(A) + mapping = dict(zip(nodelist,range(n))) + sc = {} + for v in G: + # remove row and col of node v + i = mapping[v] + row = A[i,:].copy() + col = A[:,i].copy() + A[i,:] = 0 + A[:,i] = 0 + B = (expA - scipy.linalg.expm(A)) / expA + # sum with row/col of node v and diag set to zero + B[i,:] = 0 + B[:,i] = 0 + B -= scipy.diag(scipy.diag(B)) + sc[v] = float(B.sum()) + # put row and col back + A[i,:] = row + A[:,i] = col + # rescaling + sc = _rescale(sc,normalized=normalized) + return sc + +def _rescale(sc,normalized): + # helper to rescale betweenness centrality + if normalized is True: + order=len(sc) + if order <=2: + scale=None + else: + scale=1.0/((order-1.0)**2-(order-1.0)) + if scale is not None: + for v in sc: + sc[v] *= scale + return sc + + +@require('numpy','scipy') +@not_implemented_for('directed') +@not_implemented_for('multigraph') +def communicability(G): + r"""Return communicability between all pairs of nodes in G. + + The communicability between pairs of nodes in G is the sum of + closed walks of different lengths starting at node u and ending at node v. + + Parameters + ---------- + G: graph + + Returns + ------- + comm: dictionary of dictionaries + Dictionary of dictionaries keyed by nodes with communicability + as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + communicability_centrality_exp: + Communicability centrality for each node of G using matrix exponential. + communicability_centrality: + Communicability centrality for each node in G using spectral + decomposition. + communicability: + Communicability between pairs of nodes in G. + + Notes + ----- + This algorithm uses a spectral decomposition of the adjacency matrix. + Let G=(V,E) be a simple undirected graph. Using the connection between + the powers of the adjacency matrix and the number of walks in the graph, + the communicability between nodes `u` and `v` based on the graph spectrum + is [1]_ + + .. math:: + C(u,v)=\sum_{j=1}^{n}\phi_{j}(u)\phi_{j}(v)e^{\lambda_{j}}, + + where `\phi_{j}(u)` is the `u\rm{th}` element of the `j\rm{th}` orthonormal + eigenvector of the adjacency matrix associated with the eigenvalue + `\lambda_{j}`. + + References + ---------- + .. [1] Ernesto Estrada, Naomichi Hatano, + "Communicability in complex networks", + Phys. Rev. E 77, 036111 (2008). + http://arxiv.org/abs/0707.0756 + + Examples + -------- + >>> G = nx.Graph([(0,1),(1,2),(1,5),(5,4),(2,4),(2,3),(4,3),(3,6)]) + >>> c = nx.communicability(G) + """ + import numpy + import scipy.linalg + nodelist = G.nodes() # ordering of nodes in matrix + A = nx.to_numpy_matrix(G,nodelist) + # convert to 0-1 matrix + A[A!=0.0] = 1 + w,vec = numpy.linalg.eigh(A) + expw = numpy.exp(w) + mapping = dict(zip(nodelist,range(len(nodelist)))) + sc={} + # computing communicabilities + for u in G: + sc[u]={} + for v in G: + s = 0 + p = mapping[u] + q = mapping[v] + for j in range(len(nodelist)): + s += vec[:,j][p,0]*vec[:,j][q,0]*expw[j] + sc[u][v] = float(s) + return sc + +@require('scipy') +@not_implemented_for('directed') +@not_implemented_for('multigraph') +def communicability_exp(G): + r"""Return communicability between all pairs of nodes in G. + + Communicability between pair of node (u,v) of node in G is the sum of + closed walks of different lengths starting at node u and ending at node v. + + Parameters + ---------- + G: graph + + Returns + ------- + comm: dictionary of dictionaries + Dictionary of dictionaries keyed by nodes with communicability + as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + communicability_centrality_exp: + Communicability centrality for each node of G using matrix exponential. + communicability_centrality: + Communicability centrality for each node in G using spectral + decomposition. + communicability_exp: + Communicability between all pairs of nodes in G using spectral + decomposition. + + Notes + ----- + This algorithm uses matrix exponentiation of the adjacency matrix. + + Let G=(V,E) be a simple undirected graph. Using the connection between + the powers of the adjacency matrix and the number of walks in the graph, + the communicability between nodes u and v is [1]_, + + .. math:: + C(u,v) = (e^A)_{uv}, + + where `A` is the adjacency matrix of G. + + References + ---------- + .. [1] Ernesto Estrada, Naomichi Hatano, + "Communicability in complex networks", + Phys. Rev. E 77, 036111 (2008). + http://arxiv.org/abs/0707.0756 + + Examples + -------- + >>> G = nx.Graph([(0,1),(1,2),(1,5),(5,4),(2,4),(2,3),(4,3),(3,6)]) + >>> c = nx.communicability_exp(G) + """ + import scipy.linalg + nodelist = G.nodes() # ordering of nodes in matrix + A = nx.to_numpy_matrix(G,nodelist) + # convert to 0-1 matrix + A[A!=0.0] = 1 + # communicability matrix + expA = scipy.linalg.expm(A) + mapping = dict(zip(nodelist,range(len(nodelist)))) + sc = {} + for u in G: + sc[u]={} + for v in G: + sc[u][v] = float(expA[mapping[u],mapping[v]]) + return sc + +@require('numpy') +def estrada_index(G): + r"""Return the Estrada index of a the graph G. + + Parameters + ---------- + G: graph + + Returns + ------- + estrada index: float + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See also + -------- + estrada_index_exp + + Notes + ----- + Let `G=(V,E)` be a simple undirected graph with `n` nodes and let + `\lambda_{1}\leq\lambda_{2}\leq\cdots\lambda_{n}` + be a non-increasing ordering of the eigenvalues of its adjacency + matrix `A`. The Estrada index is + + .. math:: + EE(G)=\sum_{j=1}^n e^{\lambda _j}. + + References + ---------- + .. [1] E. Estrada, Characterization of 3D molecular structure, + Chem. Phys. Lett. 319, 713 (2000). + + Examples + -------- + >>> G=nx.Graph([(0,1),(1,2),(1,5),(5,4),(2,4),(2,3),(4,3),(3,6)]) + >>> ei=nx.estrada_index(G) + """ + return sum(communicability_centrality(G).values()) + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness.py new file mode 100644 index 0000000..23cb5bf --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness.py @@ -0,0 +1,361 @@ +""" +Current-flow betweenness centrality measures. +""" +# Copyright (C) 2010-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import random +import networkx as nx +from networkx.algorithms.centrality.flow_matrix import * +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" + +__all__ = ['current_flow_betweenness_centrality', + 'approximate_current_flow_betweenness_centrality', + 'edge_current_flow_betweenness_centrality'] + + +def approximate_current_flow_betweenness_centrality(G, normalized=True, + weight='weight', + dtype=float, solver='full', + epsilon=0.5, kmax=10000): + r"""Compute the approximate current-flow betweenness centrality for nodes. + + Approximates the current-flow betweenness centrality within absolute + error of epsilon with high probability [1]_. + + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by 2/[(n-1)(n-2)] where + n is the number of nodes in G. + + weight : string or None, optional (default='weight') + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + epsilon: float + Absolute error tolerance. + + kmax: int + Maximum number of sample node pairs to use for approximation. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + current_flow_betweenness_centrality + + Notes + ----- + The running time is `O((1/\epsilon^2)m{\sqrt k} \log n)` + and the space required is `O(m)` for n nodes and m edges. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + http://www.inf.uni-konstanz.de/algo/publications/bf-cmbcf-05.pdf + """ + from networkx.utils import reverse_cuthill_mckee_ordering + try: + import numpy as np + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires NumPy ', + 'http://scipy.org/') + try: + from scipy import sparse + from scipy.sparse import linalg + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires SciPy ', + 'http://scipy.org/') + if G.is_directed(): + raise nx.NetworkXError('current_flow_betweenness_centrality() ', + 'not defined for digraphs.') + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + solvername={"full" :FullInverseLaplacian, + "lu": SuperLUInverseLaplacian, + "cg": CGInverseLaplacian} + n = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G,dict(zip(ordering,range(n)))) + L = laplacian_sparse_matrix(H, nodelist=range(n), weight=weight, + dtype=dtype, format='csc') + C = solvername[solver](L, dtype=dtype) # initialize solver + betweenness = dict.fromkeys(H,0.0) + nb = (n-1.0)*(n-2.0) # normalization factor + cstar = n*(n-1)/nb + l = 1 # parameter in approximation, adjustable + k = l*int(np.ceil((cstar/epsilon)**2*np.log(n))) + if k > kmax: + raise nx.NetworkXError('Number random pairs k>kmax (%d>%d) '%(k,kmax), + 'Increase kmax or epsilon') + cstar2k = cstar/(2*k) + for i in range(k): + s,t = random.sample(range(n),2) + b = np.zeros(n, dtype=dtype) + b[s] = 1 + b[t] = -1 + p = C.solve(b) + for v in H: + if v==s or v==t: + continue + for nbr in H[v]: + w = H[v][nbr].get(weight,1.0) + betweenness[v] += w*np.abs(p[v]-p[nbr])*cstar2k + if normalized: + factor = 1.0 + else: + factor = nb/2.0 + # remap to original node names and "unnormalize" if required + return dict((ordering[k],float(v*factor)) for k,v in betweenness.items()) + + +def current_flow_betweenness_centrality(G, normalized=True, weight='weight', + dtype=float, solver='full'): + r"""Compute current-flow betweenness centrality for nodes. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by 2/[(n-1)(n-2)] where + n is the number of nodes in G. + + weight : string or None, optional (default='weight') + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + approximate_current_flow_betweenness_centrality + betweenness_centrality + edge_betweenness_centrality + edge_current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in `O(I(n-1)+mn \log n)` + time [1]_, where `I(n-1)` is the time needed to compute the + inverse Laplacian. For a full matrix this is `O(n^3)` but using + sparse methods you can achieve `O(nm{\sqrt k})` where `k` is the + Laplacian matrix condition number. + + The space required is `O(nw) where `w` is the width of the sparse + Laplacian matrix. Worse case is `w=n` for `O(n^2)`. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + http://www.inf.uni-konstanz.de/algo/publications/bf-cmbcf-05.pdf + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + from networkx.utils import reverse_cuthill_mckee_ordering + try: + import numpy as np + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires NumPy ', + 'http://scipy.org/') + try: + import scipy + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires SciPy ', + 'http://scipy.org/') + if G.is_directed(): + raise nx.NetworkXError('current_flow_betweenness_centrality() ', + 'not defined for digraphs.') + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + n = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G,dict(zip(ordering,range(n)))) + betweenness = dict.fromkeys(H,0.0) # b[v]=0 for v in H + for row,(s,t) in flow_matrix_row(H, weight=weight, dtype=dtype, + solver=solver): + pos = dict(zip(row.argsort()[::-1],range(n))) + for i in range(n): + betweenness[s] += (i-pos[i])*row[i] + betweenness[t] += (n-i-1-pos[i])*row[i] + if normalized: + nb = (n-1.0)*(n-2.0) # normalization factor + else: + nb = 2.0 + for i,v in enumerate(H): # map integers to nodes + betweenness[v] = float((betweenness[v]-i)*2.0/nb) + return dict((ordering[k],v) for k,v in betweenness.items()) + + +def edge_current_flow_betweenness_centrality(G, normalized=True, + weight='weight', + dtype=float, solver='full'): + """Compute current-flow betweenness centrality for edges. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by 2/[(n-1)(n-2)] where + n is the number of nodes in G. + + weight : string or None, optional (default='weight') + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of edge tuples with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_betweenness_centrality + current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in `O(I(n-1)+mn \log n)` + time [1]_, where `I(n-1)` is the time needed to compute the + inverse Laplacian. For a full matrix this is `O(n^3)` but using + sparse methods you can achieve `O(nm{\sqrt k})` where `k` is the + Laplacian matrix condition number. + + The space required is `O(nw) where `w` is the width of the sparse + Laplacian matrix. Worse case is `w=n` for `O(n^2)`. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + http://www.inf.uni-konstanz.de/algo/publications/bf-cmbcf-05.pdf + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + from networkx.utils import reverse_cuthill_mckee_ordering + try: + import numpy as np + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires NumPy ', + 'http://scipy.org/') + try: + import scipy + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires SciPy ', + 'http://scipy.org/') + if G.is_directed(): + raise nx.NetworkXError('edge_current_flow_betweenness_centrality ', + 'not defined for digraphs.') + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + n = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G,dict(zip(ordering,range(n)))) + betweenness=(dict.fromkeys(H.edges(),0.0)) + if normalized: + nb=(n-1.0)*(n-2.0) # normalization factor + else: + nb=2.0 + for row,(e) in flow_matrix_row(H, weight=weight, dtype=dtype, + solver=solver): + pos=dict(zip(row.argsort()[::-1],range(1,n+1))) + for i in range(n): + betweenness[e]+=(i+1-pos[i])*row[i] + betweenness[e]+=(n-i-pos[i])*row[i] + betweenness[e]/=nb + return dict(((ordering[s],ordering[t]),float(v)) + for (s,t),v in betweenness.items()) + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + import scipy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness_subset.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness_subset.py new file mode 100644 index 0000000..7902f30 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_betweenness_subset.py @@ -0,0 +1,263 @@ +""" +Current-flow betweenness centrality measures for subsets of nodes. +""" +# Copyright (C) 2010-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" + +__all__ = ['current_flow_betweenness_centrality_subset', + 'edge_current_flow_betweenness_centrality_subset'] + +import itertools +import networkx as nx +from networkx.algorithms.centrality.flow_matrix import * + + +def current_flow_betweenness_centrality_subset(G,sources,targets, + normalized=True, + weight='weight', + dtype=float, solver='lu'): + r"""Compute current-flow betweenness centrality for subsets of nodes. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + sources: list of nodes + Nodes to use as sources for current + + targets: list of nodes + Nodes to use as sinks for current + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by b=b/(n-1)(n-2) where + n is the number of nodes in G. + + weight : string or None, optional (default='weight') + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + approximate_current_flow_betweenness_centrality + betweenness_centrality + edge_betweenness_centrality + edge_current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in `O(I(n-1)+mn \log n)` + time [1]_, where `I(n-1)` is the time needed to compute the + inverse Laplacian. For a full matrix this is `O(n^3)` but using + sparse methods you can achieve `O(nm{\sqrt k})` where `k` is the + Laplacian matrix condition number. + + The space required is `O(nw) where `w` is the width of the sparse + Laplacian matrix. Worse case is `w=n` for `O(n^2)`. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + http://www.inf.uni-konstanz.de/algo/publications/bf-cmbcf-05.pdf + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + from networkx.utils import reverse_cuthill_mckee_ordering + try: + import numpy as np + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires NumPy ', + 'http://scipy.org/') + try: + import scipy + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires SciPy ', + 'http://scipy.org/') + if G.is_directed(): + raise nx.NetworkXError('current_flow_betweenness_centrality() ', + 'not defined for digraphs.') + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + n = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + mapping=dict(zip(ordering,range(n))) + H = nx.relabel_nodes(G,mapping) + betweenness = dict.fromkeys(H,0.0) # b[v]=0 for v in H + for row,(s,t) in flow_matrix_row(H, weight=weight, dtype=dtype, + solver=solver): + for ss in sources: + i=mapping[ss] + for tt in targets: + j=mapping[tt] + betweenness[s]+=0.5*np.abs(row[i]-row[j]) + betweenness[t]+=0.5*np.abs(row[i]-row[j]) + if normalized: + nb=(n-1.0)*(n-2.0) # normalization factor + else: + nb=2.0 + for v in H: + betweenness[v]=betweenness[v]/nb+1.0/(2-n) + return dict((ordering[k],v) for k,v in betweenness.items()) + + +def edge_current_flow_betweenness_centrality_subset(G, sources, targets, + normalized=True, + weight='weight', + dtype=float, solver='lu'): + """Compute current-flow betweenness centrality for edges using subsets + of nodes. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + sources: list of nodes + Nodes to use as sources for current + + targets: list of nodes + Nodes to use as sinks for current + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by b=b/(n-1)(n-2) where + n is the number of nodes in G. + + weight : string or None, optional (default='weight') + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of edge tuples with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_betweenness_centrality + current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in `O(I(n-1)+mn \log n)` + time [1]_, where `I(n-1)` is the time needed to compute the + inverse Laplacian. For a full matrix this is `O(n^3)` but using + sparse methods you can achieve `O(nm{\sqrt k})` where `k` is the + Laplacian matrix condition number. + + The space required is `O(nw) where `w` is the width of the sparse + Laplacian matrix. Worse case is `w=n` for `O(n^2)`. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + http://www.inf.uni-konstanz.de/algo/publications/bf-cmbcf-05.pdf + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + from networkx.utils import reverse_cuthill_mckee_ordering + try: + import numpy as np + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires NumPy ', + 'http://scipy.org/') + try: + import scipy + except ImportError: + raise ImportError('current_flow_betweenness_centrality requires SciPy ', + 'http://scipy.org/') + if G.is_directed(): + raise nx.NetworkXError('edge_current_flow_betweenness_centrality ', + 'not defined for digraphs.') + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + n = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + mapping=dict(zip(ordering,range(n))) + H = nx.relabel_nodes(G,mapping) + betweenness=(dict.fromkeys(H.edges(),0.0)) + if normalized: + nb=(n-1.0)*(n-2.0) # normalization factor + else: + nb=2.0 + for row,(e) in flow_matrix_row(H, weight=weight, dtype=dtype, + solver=solver): + for ss in sources: + i=mapping[ss] + for tt in targets: + j=mapping[tt] + betweenness[e]+=0.5*np.abs(row[i]-row[j]) + betweenness[e]/=nb + return dict(((ordering[s],ordering[t]),v) + for (s,t),v in betweenness.items()) + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + import scipy + except: + raise SkipTest("NumPy not available") + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_closeness.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_closeness.py new file mode 100644 index 0000000..1a484b2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/current_flow_closeness.py @@ -0,0 +1,127 @@ +""" +Current-flow closeness centrality measures. + +""" +# Copyright (C) 2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """Aric Hagberg """ + +__all__ = ['current_flow_closeness_centrality','information_centrality'] + +import networkx as nx +from networkx.algorithms.centrality.flow_matrix import * + + +def current_flow_closeness_centrality(G, normalized=True, weight='weight', + dtype=float, solver='lu'): + """Compute current-flow closeness centrality for nodes. + + A variant of closeness centrality based on effective + resistance between nodes in a network. This metric + is also known as information centrality. + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional + If True the values are normalized by 1/(n-1) where n is the + number of nodes in G. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of nodes with current flow closeness centrality as the value. + + See Also + -------- + closeness_centrality + + Notes + ----- + The algorithm is from Brandes [1]_. + + See also [2]_ for the original definition of information centrality. + + References + ---------- + .. [1] Ulrik Brandes and Daniel Fleischer, + Centrality Measures Based on Current Flow. + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + http://www.inf.uni-konstanz.de/algo/publications/bf-cmbcf-05.pdf + + .. [2] Stephenson, K. and Zelen, M. + Rethinking centrality: Methods and examples. + Social Networks. Volume 11, Issue 1, March 1989, pp. 1-37 + http://dx.doi.org/10.1016/0378-8733(89)90016-6 + """ + from networkx.utils import reverse_cuthill_mckee_ordering + try: + import numpy as np + except ImportError: + raise ImportError('current_flow_closeness_centrality requires NumPy ', + 'http://scipy.org/') + try: + import scipy + except ImportError: + raise ImportError('current_flow_closeness_centrality requires SciPy ', + 'http://scipy.org/') + if G.is_directed(): + raise nx.NetworkXError('current_flow_closeness_centrality ', + 'not defined for digraphs.') + if G.is_directed(): + raise nx.NetworkXError(\ + "current_flow_closeness_centrality() not defined for digraphs.") + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + solvername={"full" :FullInverseLaplacian, + "lu": SuperLUInverseLaplacian, + "cg": CGInverseLaplacian} + n = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G,dict(zip(ordering,range(n)))) + betweenness = dict.fromkeys(H,0.0) # b[v]=0 for v in H + n = G.number_of_nodes() + L = laplacian_sparse_matrix(H, nodelist=range(n), weight=weight, + dtype=dtype, format='csc') + C2 = solvername[solver](L, width=1, dtype=dtype) # initialize solver + for v in H: + col=C2.get_row(v) + for w in H: + betweenness[v]+=col[v]-2*col[w] + betweenness[w]+=col[v] + + if normalized: + nb=len(betweenness)-1.0 + else: + nb=1.0 + for v in H: + betweenness[v]=nb/(betweenness[v]) + return dict((ordering[k],float(v)) for k,v in betweenness.items()) + +information_centrality=current_flow_closeness_centrality + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/degree_alg.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/degree_alg.py new file mode 100644 index 0000000..c65d967 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/degree_alg.py @@ -0,0 +1,131 @@ +""" +Degree centrality measures. + +""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Sasha Gutfraind (ag362@cornell.edu)']) + +__all__ = ['degree_centrality', + 'in_degree_centrality', + 'out_degree_centrality'] + +import networkx as nx + +def degree_centrality(G): + """Compute the degree centrality for nodes. + + The degree centrality for a node v is the fraction of nodes it + is connected to. + + Parameters + ---------- + G : graph + A networkx graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with degree centrality as the value. + + See Also + -------- + betweenness_centrality, load_centrality, eigenvector_centrality + + Notes + ----- + The degree centrality values are normalized by dividing by the maximum + possible degree in a simple graph n-1 where n is the number of nodes in G. + + For multigraphs or graphs with self loops the maximum degree might + be higher than n-1 and values of degree centrality greater than 1 + are possible. + """ + centrality={} + s=1.0/(len(G)-1.0) + centrality=dict((n,d*s) for n,d in G.degree_iter()) + return centrality + +def in_degree_centrality(G): + """Compute the in-degree centrality for nodes. + + The in-degree centrality for a node v is the fraction of nodes its + incoming edges are connected to. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with in-degree centrality as values. + + See Also + -------- + degree_centrality, out_degree_centrality + + Notes + ----- + The degree centrality values are normalized by dividing by the maximum + possible degree in a simple graph n-1 where n is the number of nodes in G. + + For multigraphs or graphs with self loops the maximum degree might + be higher than n-1 and values of degree centrality greater than 1 + are possible. + """ + if not G.is_directed(): + raise nx.NetworkXError(\ + "in_degree_centrality() not defined for undirected graphs.") + centrality={} + s=1.0/(len(G)-1.0) + centrality=dict((n,d*s) for n,d in G.in_degree_iter()) + return centrality + + +def out_degree_centrality(G): + """Compute the out-degree centrality for nodes. + + The out-degree centrality for a node v is the fraction of nodes its + outgoing edges are connected to. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with out-degree centrality as values. + + See Also + -------- + degree_centrality, in_degree_centrality + + Notes + ----- + The degree centrality values are normalized by dividing by the maximum + possible degree in a simple graph n-1 where n is the number of nodes in G. + + For multigraphs or graphs with self loops the maximum degree might + be higher than n-1 and values of degree centrality greater than 1 + are possible. + """ + if not G.is_directed(): + raise nx.NetworkXError(\ + "out_degree_centrality() not defined for undirected graphs.") + centrality={} + s=1.0/(len(G)-1.0) + centrality=dict((n,d*s) for n,d in G.out_degree_iter()) + return centrality + + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/eigenvector.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/eigenvector.py new file mode 100644 index 0000000..28e0013 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/eigenvector.py @@ -0,0 +1,169 @@ +""" +Eigenvector centrality. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Sasha Gutfraind (ag362@cornell.edu)']) +__all__ = ['eigenvector_centrality', + 'eigenvector_centrality_numpy'] + +def eigenvector_centrality(G,max_iter=100,tol=1.0e-6,nstart=None): + """Compute the eigenvector centrality for the graph G. + + Uses the power method to find the eigenvector for the + largest eigenvalue of the adjacency matrix of G. + + Parameters + ---------- + G : graph + A networkx graph + + max_iter : interger, optional + Maximum number of iterations in power method. + + tol : float, optional + Error tolerance used to check convergence in power method iteration. + + nstart : dictionary, optional + Starting value of eigenvector iteration for each node. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with eigenvector centrality as the value. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> centrality=nx.eigenvector_centrality(G) + >>> print(['%s %0.2f'%(node,centrality[node]) for node in centrality]) + ['0 0.37', '1 0.60', '2 0.60', '3 0.37'] + + Notes + ------ + The eigenvector calculation is done by the power iteration method + and has no guarantee of convergence. The iteration will stop + after max_iter iterations or an error tolerance of + number_of_nodes(G)*tol has been reached. + + For directed graphs this is "right" eigevector centrality. For + "left" eigenvector centrality, first reverse the graph with + G.reverse(). + + See Also + -------- + eigenvector_centrality_numpy + pagerank + hits + """ + from math import sqrt + if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph: + raise nx.NetworkXException("Not defined for multigraphs.") + + if len(G)==0: + raise nx.NetworkXException("Empty graph.") + + if nstart is None: + # choose starting vector with entries of 1/len(G) + x=dict([(n,1.0/len(G)) for n in G]) + else: + x=nstart + # normalize starting vector + s=1.0/sum(x.values()) + for k in x: x[k]*=s + nnodes=G.number_of_nodes() + # make up to max_iter iterations + for i in range(max_iter): + xlast=x + x=dict.fromkeys(xlast, 0) + # do the multiplication y=Ax + for n in x: + for nbr in G[n]: + x[n]+=xlast[nbr]*G[n][nbr].get('weight',1) + # normalize vector + try: + s=1.0/sqrt(sum(v**2 for v in x.values())) + # this should never be zero? + except ZeroDivisionError: + s=1.0 + for n in x: x[n]*=s + # check convergence + err=sum([abs(x[n]-xlast[n]) for n in x]) + if err < nnodes*tol: + return x + + raise nx.NetworkXError("""eigenvector_centrality(): +power iteration failed to converge in %d iterations."%(i+1))""") + + +def eigenvector_centrality_numpy(G): + """Compute the eigenvector centrality for the graph G. + + Parameters + ---------- + G : graph + A networkx graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with eigenvector centrality as the value. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> centrality=nx.eigenvector_centrality_numpy(G) + >>> print(['%s %0.2f'%(node,centrality[node]) for node in centrality]) + ['0 0.37', '1 0.60', '2 0.60', '3 0.37'] + + Notes + ------ + This algorithm uses the NumPy eigenvalue solver. + + For directed graphs this is "right" eigevector centrality. For + "left" eigenvector centrality, first reverse the graph with + G.reverse(). + + See Also + -------- + eigenvector_centrality + pagerank + hits + """ + try: + import numpy as np + except ImportError: + raise ImportError('Requires NumPy: http://scipy.org/') + + if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph: + raise nx.NetworkXException('Not defined for multigraphs.') + + if len(G)==0: + raise nx.NetworkXException('Empty graph.') + + A=nx.adj_matrix(G,nodelist=G.nodes()) + eigenvalues,eigenvectors=np.linalg.eig(A) + # eigenvalue indices in reverse sorted order + ind=eigenvalues.argsort()[::-1] + # eigenvector of largest eigenvalue at ind[0], normalized + largest=np.array(eigenvectors[:,ind[0]]).flatten().real + norm=np.sign(largest.sum())*np.linalg.norm(largest) + centrality=dict(zip(G,map(float,largest/norm))) + return centrality + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + import numpy.linalg + except: + raise SkipTest("numpy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/flow_matrix.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/flow_matrix.py new file mode 100644 index 0000000..d886182 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/flow_matrix.py @@ -0,0 +1,139 @@ +# Helpers for current-flow betweenness and current-flow closness +# Lazy computations for inverse Laplacian and flow-matrix rows. +import networkx as nx + +def flow_matrix_row(G, weight='weight', dtype=float, solver='lu'): + # Generate a row of the current-flow matrix + import numpy as np + from scipy import sparse + from scipy.sparse import linalg + solvername={"full" :FullInverseLaplacian, + "lu": SuperLUInverseLaplacian, + "cg": CGInverseLaplacian} + n = G.number_of_nodes() + L = laplacian_sparse_matrix(G, nodelist=range(n), weight=weight, + dtype=dtype, format='csc') + C = solvername[solver](L, dtype=dtype) # initialize solver + w = C.w # w is the Laplacian matrix width + # row-by-row flow matrix + for u,v,d in G.edges_iter(data=True): + B = np.zeros(w, dtype=dtype) + c = d.get(weight,1.0) + B[u%w] = c + B[v%w] = -c + # get only the rows needed in the inverse laplacian + # and multiply to get the flow matrix row + row = np.dot(B, C.get_rows(u,v)) + yield row,(u,v) + + +# Class to compute the inverse laplacian only for specified rows +# Allows computation of the current-flow matrix without storing entire +# inverse laplacian matrix +class InverseLaplacian(object): + def __init__(self, L, width=None, dtype=None): + global np + import numpy as np + (n,n) = L.shape + self.dtype = dtype + self.n = n + if width is None: + self.w = self.width(L) + else: + self.w = width + self.C = np.zeros((self.w,n), dtype=dtype) + self.L1 = L[1:,1:] + self.init_solver(L) + + def init_solver(self,L): + pass + + def solve(self,r): + raise("Implement solver") + + def solve_inverse(self,r): + raise("Implement solver") + + + def get_rows(self, r1, r2): + for r in range(r1, r2+1): + self.C[r%self.w, 1:] = self.solve_inverse(r) + return self.C + + def get_row(self, r): + self.C[r%self.w, 1:] = self.solve_inverse(r) + return self.C[r%self.w] + + + def width(self,L): + m=0 + for i,row in enumerate(L): + w=0 + x,y = np.nonzero(row) + if len(y) > 0: + v = y-i + w=v.max()-v.min()+1 + m = max(w,m) + return m + +class FullInverseLaplacian(InverseLaplacian): + def init_solver(self,L): + self.IL = np.zeros(L.shape, dtype=self.dtype) + self.IL[1:,1:] = np.linalg.inv(self.L1.todense()) + + def solve(self,rhs): + s = np.zeros(rhs.shape, dtype=self.dtype) + s = np.dot(self.IL,rhs) + return s + + def solve_inverse(self,r): + return self.IL[r,1:] + + +class SuperLUInverseLaplacian(InverseLaplacian): + def init_solver(self,L): + from scipy.sparse import linalg + self.lusolve = linalg.factorized(self.L1.tocsc()) + + def solve_inverse(self,r): + rhs = np.zeros(self.n, dtype=self.dtype) + rhs[r]=1 + return self.lusolve(rhs[1:]) + + def solve(self,rhs): + s = np.zeros(rhs.shape, dtype=self.dtype) + s[1:]=self.lusolve(rhs[1:]) + return s + + + +class CGInverseLaplacian(InverseLaplacian): + def init_solver(self,L): + global linalg + from scipy.sparse import linalg + ilu= linalg.spilu(self.L1.tocsc()) + n=self.n-1 + self.M = linalg.LinearOperator(shape=(n,n), matvec=ilu.solve) + + def solve(self,rhs): + s = np.zeros(rhs.shape, dtype=self.dtype) + s[1:]=linalg.cg(self.L1, rhs[1:], M=self.M)[0] + return s + + def solve_inverse(self,r): + rhs = np.zeros(self.n, self.dtype) + rhs[r] = 1 + return linalg.cg(self.L1, rhs[1:], M=self.M)[0] + + +# graph laplacian, sparse version, will move to linalg/laplacianmatrix.py +def laplacian_sparse_matrix(G, nodelist=None, weight='weight', dtype=None, + format='csr'): + import numpy as np + import scipy.sparse + A = nx.to_scipy_sparse_matrix(G, nodelist=nodelist, weight=weight, + dtype=dtype, format=format) + (n,n) = A.shape + data = np.asarray(A.sum(axis=1).T) + D = scipy.sparse.spdiags(data,0,n,n, format=format) + return D - A diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/katz.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/katz.py new file mode 100644 index 0000000..4babe25 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/katz.py @@ -0,0 +1,296 @@ +""" +Katz centrality. +""" +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import * +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Sasha Gutfraind (ag362@cornell.edu)', + 'Vincent Gauthier (vgauthier@luxbulb.org)']) + +__all__ = ['katz_centrality', + 'katz_centrality_numpy'] + +@not_implemented_for('multigraph') +def katz_centrality(G, alpha=0.1, beta=1.0, + max_iter=1000, tol=1.0e-6, nstart=None, normalized=True): + r"""Compute the Katz centrality for the nodes of the graph G. + + + Katz centrality is related to eigenvalue centrality and PageRank. + The Katz centrality for node `i` is + + .. math:: + + x_i = \alpha \sum_{j} A_{ij} x_j + \beta, + + where `A` is the adjacency matrix of the graph G with eigenvalues `\lambda`. + + The parameter `\beta` controls the initial centrality and + + .. math:: + + \alpha < \frac{1}{\lambda_{max}}. + + + Katz centrality computes the relative influence of a node within a + network by measuring the number of the immediate neighbors (first + degree nodes) and also all other nodes in the network that connect + to the node under consideration through these immediate neighbors. + + Extra weight can be provided to immediate neighbors through the + parameter :math:`\beta`. Connections made with distant neighbors + are, however, penalized by an attenuation factor `\alpha` which + should be strictly less than the inverse largest eigenvalue of the + adjacency matrix in order for the Katz centrality to be computed + correctly. More information is provided in [1]_ . + + + Parameters + ---------- + G : graph + A NetworkX graph + + alpha : float + Attenuation factor + + beta : scalar or dictionary, optional (default=1.0) + Weight attributed to the immediate neighborhood. If not a scalar the + dictionary must have an value for every node. + + max_iter : integer, optional (default=1000) + Maximum number of iterations in power method. + + tol : float, optional (default=1.0e-6) + Error tolerance used to check convergence in power method iteration. + + nstart : dictionary, optional + Starting value of Katz iteration for each node. + + normalized : bool, optional (default=True) + If True normalize the resulting values. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with Katz centrality as the value. + + Examples + -------- + >>> import math + >>> G = nx.path_graph(4) + >>> phi = (1+math.sqrt(5))/2.0 # largest eigenvalue of adj matrix + >>> centrality = nx.katz_centrality(G,1/phi-0.01) + >>> for n,c in sorted(centrality.items()): + ... print("%d %0.2f"%(n,c)) + 0 0.37 + 1 0.60 + 2 0.60 + 3 0.37 + + Notes + ----- + This algorithm it uses the power method to find the eigenvector + corresponding to the largest eigenvalue of the adjacency matrix of G. + The constant alpha should be strictly less than the inverse of largest + eigenvalue of the adjacency matrix for the algorithm to converge. + The iteration will stop after max_iter iterations or an error tolerance of + number_of_nodes(G)*tol has been reached. + + When `\alpha = 1/\lambda_{max}` and `\beta=1` Katz centrality is the same as + eigenvector centrality. + + References + ---------- + .. [1] M. Newman, Networks: An Introduction. Oxford University Press, + USA, 2010, p. 720. + + See Also + -------- + katz_centrality_numpy + eigenvector_centrality + eigenvector_centrality_numpy + pagerank + hits + """ + from math import sqrt + + if len(G)==0: + return {} + + nnodes=G.number_of_nodes() + + if nstart is None: + # choose starting vector with entries of 0 + x=dict([(n,0) for n in G]) + else: + x=nstart + + try: + b = dict.fromkeys(G,float(beta)) + except (TypeError,ValueError): + b = beta + if set(beta) != set(G): + raise nx.NetworkXError('beta dictionary ' + 'must have a value for every node') + + # make up to max_iter iterations + for i in range(max_iter): + xlast=x + x=dict.fromkeys(xlast, 0) + # do the multiplication y = Alpha * Ax - Beta + for n in x: + for nbr in G[n]: + x[n] += xlast[nbr] * G[n][nbr].get('weight',1) + x[n] = alpha*x[n] + b[n] + + # check convergence + err=sum([abs(x[n]-xlast[n]) for n in x]) + if err < nnodes*tol: + if normalized: + # normalize vector + try: + s=1.0/sqrt(sum(v**2 for v in x.values())) + # this should never be zero? + except ZeroDivisionError: + s=1.0 + else: + s = 1 + for n in x: + x[n]*=s + return x + + raise nx.NetworkXError('Power iteration failed to converge in ', + '%d iterations."%(i+1))') + +@not_implemented_for('multigraph') +def katz_centrality_numpy(G, alpha=0.1, beta=1.0, normalized=True): + r"""Compute the Katz centrality for the graph G. + + + Katz centrality is related to eigenvalue centrality and PageRank. + The Katz centrality for node `i` is + + .. math:: + + x_i = \alpha \sum_{j} A_{ij} x_j + \beta, + + where `A` is the adjacency matrix of the graph G with eigenvalues `\lambda`. + + The parameter `\beta` controls the initial centrality and + + .. math:: + + \alpha < \frac{1}{\lambda_{max}}. + + + Katz centrality computes the relative influence of a node within a + network by measuring the number of the immediate neighbors (first + degree nodes) and also all other nodes in the network that connect + to the node under consideration through these immediate neighbors. + + Extra weight can be provided to immediate neighbors through the + parameter :math:`\beta`. Connections made with distant neighbors + are, however, penalized by an attenuation factor `\alpha` which + should be strictly less than the inverse largest eigenvalue of the + adjacency matrix in order for the Katz centrality to be computed + correctly. More information is provided in [1]_ . + + Parameters + ---------- + G : graph + A NetworkX graph + + alpha : float + Attenuation factor + + beta : scalar or dictionary, optional (default=1.0) + Weight attributed to the immediate neighborhood. If not a scalar the + dictionary must have an value for every node. + + normalized : bool + If True normalize the resulting values. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with Katz centrality as the value. + + Examples + -------- + >>> import math + >>> G = nx.path_graph(4) + >>> phi = (1+math.sqrt(5))/2.0 # largest eigenvalue of adj matrix + >>> centrality = nx.katz_centrality_numpy(G,1/phi) + >>> for n,c in sorted(centrality.items()): + ... print("%d %0.2f"%(n,c)) + 0 0.37 + 1 0.60 + 2 0.60 + 3 0.37 + + Notes + ------ + This algorithm uses a direct linear solver to solve the above equation. + The constant alpha should be strictly less than the inverse of largest + eigenvalue of the adjacency matrix for there to be a solution. When + `\alpha = 1/\lambda_{max}` and `\beta=1` Katz centrality is the same as + eigenvector centrality. + + References + ---------- + .. [1] M. Newman, Networks: An Introduction. Oxford University Press, + USA, 2010, p. 720. + + See Also + -------- + katz_centrality + eigenvector_centrality_numpy + eigenvector_centrality + pagerank + hits + """ + try: + import numpy as np + except ImportError: + raise ImportError('Requires NumPy: http://scipy.org/') + if len(G)==0: + return {} + try: + nodelist = beta.keys() + if set(nodelist) != set(G): + raise nx.NetworkXError('beta dictionary ' + 'must have a value for every node') + b = np.array(list(beta.values()),dtype=float) + except AttributeError: + nodelist = G.nodes() + try: + b = np.ones((len(nodelist),1))*float(beta) + except (TypeError,ValueError): + raise nx.NetworkXError('beta must be a number') + + A=nx.adj_matrix(G, nodelist=nodelist) + n = np.array(A).shape[0] + centrality = np.linalg.solve( np.eye(n,n) - (alpha * A) , b) + if normalized: + norm = np.sign(sum(centrality)) * np.linalg.norm(centrality) + else: + norm = 1.0 + centrality=dict(zip(nodelist, map(float,centrality/norm))) + return centrality + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + import numpy.linalg + except: + raise SkipTest("numpy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/load.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/load.py new file mode 100644 index 0000000..2d279e8 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/load.py @@ -0,0 +1,190 @@ +""" +Load centrality. + +""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Sasha Gutfraind (ag362@cornell.edu)']) + +__all__ = ['load_centrality', + 'edge_load'] + +import networkx as nx + +def newman_betweenness_centrality(G,v=None,cutoff=None, + normalized=True, + weight=None): + """Compute load centrality for nodes. + + The load centrality of a node is the fraction of all shortest + paths that pass through that node. + + Parameters + ---------- + G : graph + A networkx graph + + normalized : bool, optional + If True the betweenness values are normalized by b=b/(n-1)(n-2) where + n is the number of nodes in G. + + weight : None or string, optional + If None, edge weights are ignored. + Otherwise holds the name of the edge attribute used as weight. + + cutoff : bool, optional + If specified, only consider paths of length <= cutoff. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with centrality as the value. + + + See Also + -------- + betweenness_centrality() + + Notes + ----- + Load centrality is slightly different than betweenness. + For this load algorithm see the reference + Scientific collaboration networks: II. + Shortest paths, weighted networks, and centrality, + M. E. J. Newman, Phys. Rev. E 64, 016132 (2001). + + """ + if v is not None: # only one node + betweenness=0.0 + for source in G: + ubetween = _node_betweenness(G, source, cutoff, False, weight) + betweenness += ubetween[v] if v in ubetween else 0 + if normalized: + order = G.order() + if order <= 2: + return betweenness # no normalization b=0 for all nodes + betweenness *= 1.0 / ((order-1) * (order-2)) + return betweenness + else: + betweenness = {}.fromkeys(G,0.0) + for source in betweenness: + ubetween = _node_betweenness(G, source, cutoff, False, weight) + for vk in ubetween: + betweenness[vk] += ubetween[vk] + if normalized: + order = G.order() + if order <= 2: + return betweenness # no normalization b=0 for all nodes + scale = 1.0 / ((order-1) * (order-2)) + for v in betweenness: + betweenness[v] *= scale + return betweenness # all nodes + +def _node_betweenness(G,source,cutoff=False,normalized=True,weight=None): + """Node betweenness helper: + see betweenness_centrality for what you probably want. + + This actually computes "load" and not betweenness. + See https://networkx.lanl.gov/ticket/103 + + This calculates the load of each node for paths from a single source. + (The fraction of number of shortests paths from source that go + through each node.) + + To get the load for a node you need to do all-pairs shortest paths. + + If weight is not None then use Dijkstra for finding shortest paths. + In this case a cutoff is not implemented and so is ignored. + + """ + + # get the predecessor and path length data + if weight is None: + (pred,length)=nx.predecessor(G,source,cutoff=cutoff,return_seen=True) + else: + (pred,length)=nx.dijkstra_predecessor_and_distance(G,source,weight=weight) + + # order the nodes by path length + onodes = [ (l,vert) for (vert,l) in length.items() ] + onodes.sort() + onodes[:] = [vert for (l,vert) in onodes if l>0] + + # intialize betweenness + between={}.fromkeys(length,1.0) + + while onodes: + v=onodes.pop() + if v in pred: + num_paths=len(pred[v]) # Discount betweenness if more than + for x in pred[v]: # one shortest path. + if x==source: # stop if hit source because all remaining v + break # also have pred[v]==[source] + between[x]+=between[v]/float(num_paths) + # remove source + for v in between: + between[v]-=1 + # rescale to be between 0 and 1 + if normalized: + l=len(between) + if l > 2: + scale=1.0/float((l-1)*(l-2)) # 1/the number of possible paths + for v in between: + between[v] *= scale + return between + + +load_centrality=newman_betweenness_centrality + + +def edge_load(G,nodes=None,cutoff=False): + """Compute edge load. + + WARNING: + + This module is for demonstration and testing purposes. + + """ + betweenness={} + if not nodes: # find betweenness for every node in graph + nodes=G.nodes() # that probably is what you want... + for source in nodes: + ubetween=_edge_betweenness(G,source,nodes,cutoff=cutoff) + for v in ubetween.keys(): + b=betweenness.setdefault(v,0) # get or set default + betweenness[v]=ubetween[v]+b # cumulative total + return betweenness + +def _edge_betweenness(G,source,nodes,cutoff=False): + """ + Edge betweenness helper. + """ + between={} + # get the predecessor data + #(pred,length)=_fast_predecessor(G,source,cutoff=cutoff) + (pred,length)=nx.predecessor(G,source,cutoff=cutoff,return_seen=True) + # order the nodes by path length + onodes = [ nn for dd,nn in sorted( (dist,n) for n,dist in length.items() )] + # intialize betweenness, doesn't account for any edge weights + for u,v in G.edges(nodes): + between[(u,v)]=1.0 + between[(v,u)]=1.0 + + while onodes: # work through all paths + v=onodes.pop() + if v in pred: + num_paths=len(pred[v]) # Discount betweenness if more than + for w in pred[v]: # one shortest path. + if w in pred: + num_paths=len(pred[w]) # Discount betweenness, mult path + for x in pred[w]: + between[(w,x)]+=between[(v,w)]/num_paths + between[(x,w)]+=between[(w,v)]/num_paths + return between + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality.py new file mode 100644 index 0000000..b0169a9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +def weighted_G(): + G=nx.Graph(); + G.add_edge(0,1,weight=3) + G.add_edge(0,2,weight=2) + G.add_edge(0,3,weight=6) + G.add_edge(0,4,weight=4) + G.add_edge(1,3,weight=5) + G.add_edge(1,5,weight=5) + G.add_edge(2,4,weight=1) + G.add_edge(3,4,weight=2) + G.add_edge(3,5,weight=1) + G.add_edge(4,5,weight=4) + + return G + + +class TestBetweennessCentrality(object): + + def test_K5(self): + """Betweenness centrality: K5""" + G=nx.complete_graph(5) + b=nx.betweenness_centrality(G, + weight=None, + normalized=False) + b_answer={0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_K5_endpoints(self): + """Betweenness centrality: K5 endpoints""" + G=nx.complete_graph(5) + b=nx.betweenness_centrality(G, + weight=None, + normalized=False, + endpoints=True) + b_answer={0: 4.0, 1: 4.0, 2: 4.0, 3: 4.0, 4: 4.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P3_normalized(self): + """Betweenness centrality: P3 normalized""" + G=nx.path_graph(3) + b=nx.betweenness_centrality(G, + weight=None, + normalized=True) + b_answer={0: 0.0, 1: 1.0, 2: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P3(self): + """Betweenness centrality: P3""" + G=nx.path_graph(3) + b_answer={0: 0.0, 1: 1.0, 2: 0.0} + b=nx.betweenness_centrality(G, + weight=None, + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_P3_endpoints(self): + """Betweenness centrality: P3 endpoints""" + G=nx.path_graph(3) + b_answer={0: 2.0, 1: 3.0, 2: 2.0} + b=nx.betweenness_centrality(G, + weight=None, + normalized=False, + endpoints=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_krackhardt_kite_graph(self): + """Betweenness centrality: Krackhardt kite graph""" + G=nx.krackhardt_kite_graph() + b_answer={0: 1.667,1: 1.667,2: 0.000,3: 7.333,4: 0.000, + 5: 16.667,6: 16.667,7: 28.000,8: 16.000,9: 0.000} + for b in b_answer: + b_answer[b]/=2.0 + b=nx.betweenness_centrality(G, + weight=None, + normalized=False) + + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_krackhardt_kite_graph_normalized(self): + """Betweenness centrality: Krackhardt kite graph normalized""" + G=nx.krackhardt_kite_graph() + b_answer={0:0.023,1:0.023,2:0.000,3:0.102,4:0.000, + 5:0.231,6:0.231,7:0.389,8:0.222,9:0.000} + b=nx.betweenness_centrality(G, + weight=None, + normalized=True) + + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_florentine_families_graph(self): + """Betweenness centrality: Florentine families graph""" + G=nx.florentine_families_graph() + b_answer=\ + {'Acciaiuoli': 0.000, + 'Albizzi': 0.212, + 'Barbadori': 0.093, + 'Bischeri': 0.104, + 'Castellani': 0.055, + 'Ginori': 0.000, + 'Guadagni': 0.255, + 'Lamberteschi': 0.000, + 'Medici': 0.522, + 'Pazzi': 0.000, + 'Peruzzi': 0.022, + 'Ridolfi': 0.114, + 'Salviati': 0.143, + 'Strozzi': 0.103, + 'Tornabuoni': 0.092} + + b=nx.betweenness_centrality(G, + weight=None, + normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_ladder_graph(self): + """Betweenness centrality: Ladder graph""" + G = nx.Graph() # ladder_graph(3) + G.add_edges_from([(0,1), (0,2), (1,3), (2,3), + (2,4), (4,5), (3,5)]) + b_answer={0:1.667,1: 1.667,2: 6.667, + 3: 6.667,4: 1.667,5: 1.667} + for b in b_answer: + b_answer[b]/=2.0 + b=nx.betweenness_centrality(G, + weight=None, + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + def test_disconnected_path(self): + """Betweenness centrality: disconnected path""" + G=nx.Graph() + G.add_path([0,1,2]) + G.add_path([3,4,5,6]) + b_answer={0:0,1:1,2:0,3:0,4:2,5:2,6:0} + b=nx.betweenness_centrality(G, + weight=None, + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_disconnected_path_endpoints(self): + """Betweenness centrality: disconnected path endpoints""" + G=nx.Graph() + G.add_path([0,1,2]) + G.add_path([3,4,5,6]) + b_answer={0:2,1:3,2:2,3:3,4:5,5:5,6:3} + b=nx.betweenness_centrality(G, + weight=None, + normalized=False, + endpoints=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_directed_path(self): + """Betweenness centrality: directed path""" + G=nx.DiGraph() + G.add_path([0,1,2]) + b=nx.betweenness_centrality(G, + weight=None, + normalized=False) + b_answer={0: 0.0, 1: 1.0, 2: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_directed_path_normalized(self): + """Betweenness centrality: directed path normalized""" + G=nx.DiGraph() + G.add_path([0,1,2]) + b=nx.betweenness_centrality(G, + weight=None, + normalized=True) + b_answer={0: 0.0, 1: 0.5, 2: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + +class TestWeightedBetweennessCentrality(object): + + def test_K5(self): + """Weighted betweenness centrality: K5""" + G=nx.complete_graph(5) + b=nx.betweenness_centrality(G, + weight='weight', + normalized=False) + b_answer={0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_P3_normalized(self): + """Weighted betweenness centrality: P3 normalized""" + G=nx.path_graph(3) + b=nx.betweenness_centrality(G, + weight='weight', + normalized=True) + b_answer={0: 0.0, 1: 1.0, 2: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P3(self): + """Weighted betweenness centrality: P3""" + G=nx.path_graph(3) + b_answer={0: 0.0, 1: 1.0, 2: 0.0} + b=nx.betweenness_centrality(G, + weight='weight', + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_krackhardt_kite_graph(self): + """Weighted betweenness centrality: Krackhardt kite graph""" + G=nx.krackhardt_kite_graph() + b_answer={0: 1.667,1: 1.667,2: 0.000,3: 7.333,4: 0.000, + 5: 16.667,6: 16.667,7: 28.000,8: 16.000,9: 0.000} + for b in b_answer: + b_answer[b]/=2.0 + + b=nx.betweenness_centrality(G, + weight='weight', + normalized=False) + + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_krackhardt_kite_graph_normalized(self): + """Weighted betweenness centrality: + Krackhardt kite graph normalized + """ + G=nx.krackhardt_kite_graph() + b_answer={0:0.023,1:0.023,2:0.000,3:0.102,4:0.000, + 5:0.231,6:0.231,7:0.389,8:0.222,9:0.000} + b=nx.betweenness_centrality(G, + weight='weight', + normalized=True) + + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_florentine_families_graph(self): + """Weighted betweenness centrality: + Florentine families graph""" + G=nx.florentine_families_graph() + b_answer=\ + {'Acciaiuoli': 0.000, + 'Albizzi': 0.212, + 'Barbadori': 0.093, + 'Bischeri': 0.104, + 'Castellani': 0.055, + 'Ginori': 0.000, + 'Guadagni': 0.255, + 'Lamberteschi': 0.000, + 'Medici': 0.522, + 'Pazzi': 0.000, + 'Peruzzi': 0.022, + 'Ridolfi': 0.114, + 'Salviati': 0.143, + 'Strozzi': 0.103, + 'Tornabuoni': 0.092} + + b=nx.betweenness_centrality(G, + weight='weight', + normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_ladder_graph(self): + """Weighted betweenness centrality: Ladder graph""" + G = nx.Graph() # ladder_graph(3) + G.add_edges_from([(0,1), (0,2), (1,3), (2,3), + (2,4), (4,5), (3,5)]) + b_answer={0:1.667,1: 1.667,2: 6.667, + 3: 6.667,4: 1.667,5: 1.667} + for b in b_answer: + b_answer[b]/=2.0 + b=nx.betweenness_centrality(G, + weight='weight', + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + def test_G(self): + """Weighted betweenness centrality: G""" + G = weighted_G() + b_answer={0: 2.0, 1: 0.0, 2: 4.0, 3: 3.0, 4: 4.0, 5: 0.0} + b=nx.betweenness_centrality(G, + weight='weight', + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_G2(self): + """Weighted betweenness centrality: G2""" + G=nx.DiGraph() + G.add_weighted_edges_from([('s','u',10) ,('s','x',5) , + ('u','v',1) ,('u','x',2) , + ('v','y',1) ,('x','u',3) , + ('x','v',5) ,('x','y',2) , + ('y','s',7) ,('y','v',6)]) + + b_answer={'y':5.0,'x':5.0,'s':4.0,'u':2.0,'v':2.0} + + b=nx.betweenness_centrality(G, + weight='weight', + normalized=False) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + +class TestEdgeBetweennessCentrality(object): + + def test_K5(self): + """Edge betweenness centrality: K5""" + G=nx.complete_graph(5) + b=nx.edge_betweenness_centrality(G, weight=None, normalized=False) + b_answer=dict.fromkeys(G.edges(),1) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_normalized_K5(self): + """Edge betweenness centrality: K5""" + G=nx.complete_graph(5) + b=nx.edge_betweenness_centrality(G, weight=None, normalized=True) + b_answer=dict.fromkeys(G.edges(),1/10.0) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + + def test_C4(self): + """Edge betweenness centrality: C4""" + G=nx.cycle_graph(4) + b=nx.edge_betweenness_centrality(G, weight=None, normalized=True) + b_answer={(0, 1):2,(0, 3):2, (1, 2):2, (2, 3): 2} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]/6.0) + + def test_P4(self): + """Edge betweenness centrality: P4""" + G=nx.path_graph(4) + b=nx.edge_betweenness_centrality(G, weight=None, normalized=False) + b_answer={(0, 1):3,(1, 2):4, (2, 3):3} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_normalized_P4(self): + """Edge betweenness centrality: P4""" + G=nx.path_graph(4) + b=nx.edge_betweenness_centrality(G, weight=None, normalized=True) + b_answer={(0, 1):3,(1, 2):4, (2, 3):3} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]/6.0) + + + def test_balanced_tree(self): + """Edge betweenness centrality: balanced tree""" + G=nx.balanced_tree(r=2,h=2) + b=nx.edge_betweenness_centrality(G, weight=None, normalized=False) + b_answer={(0, 1):12,(0, 2):12, + (1, 3):6,(1, 4):6,(2, 5):6,(2,6):6} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + +class TestWeightedEdgeBetweennessCentrality(object): + + def test_K5(self): + """Edge betweenness centrality: K5""" + G=nx.complete_graph(5) + b=nx.edge_betweenness_centrality(G, weight='weight', normalized=False) + b_answer=dict.fromkeys(G.edges(),1) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_C4(self): + """Edge betweenness centrality: C4""" + G=nx.cycle_graph(4) + b=nx.edge_betweenness_centrality(G, weight='weight', normalized=False) + b_answer={(0, 1):2,(0, 3):2, (1, 2):2, (2, 3): 2} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_P4(self): + """Edge betweenness centrality: P4""" + G=nx.path_graph(4) + b=nx.edge_betweenness_centrality(G, weight='weight', normalized=False) + b_answer={(0, 1):3,(1, 2):4, (2, 3):3} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + + def test_balanced_tree(self): + """Edge betweenness centrality: balanced tree""" + G=nx.balanced_tree(r=2,h=2) + b=nx.edge_betweenness_centrality(G, weight='weight', normalized=False) + b_answer={(0, 1):12,(0, 2):12, + (1, 3):6,(1, 4):6,(2, 5):6,(2,6):6} + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_weighted_graph(self): + eList = [(0, 1, 5), (0, 2, 4), (0, 3, 3), + (0, 4, 2), (1, 2, 4), (1, 3, 1), + (1, 4, 3), (2, 4, 5), (3, 4, 4)] + G = nx.Graph() + G.add_weighted_edges_from(eList) + b = nx.edge_betweenness_centrality(G, weight='weight', normalized=False) + b_answer={(0, 1):0.0, + (0, 2):1.0, + (0, 3):2.0, + (0, 4):1.0, + (1, 2):2.0, + (1, 3):3.5, + (1, 4):1.5, + (2, 4):1.0, + (3, 4):0.5} + + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_normalized_weighted_graph(self): + eList = [(0, 1, 5), (0, 2, 4), (0, 3, 3), + (0, 4, 2), (1, 2, 4), (1, 3, 1), + (1, 4, 3), (2, 4, 5), (3, 4, 4)] + G = nx.Graph() + G.add_weighted_edges_from(eList) + b = nx.edge_betweenness_centrality(G, weight='weight', normalized=True) + b_answer={(0, 1):0.0, + (0, 2):1.0, + (0, 3):2.0, + (0, 4):1.0, + (1, 2):2.0, + (1, 3):3.5, + (1, 4):1.5, + (2, 4):1.0, + (3, 4):0.5} + + norm = len(G)*(len(G)-1)/2.0 + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]/norm) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py new file mode 100644 index 0000000..762b873 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx +from networkx import betweenness_centrality_subset,\ + edge_betweenness_centrality_subset + +class TestSubsetBetweennessCentrality: + + def test_K5(self): + """Betweenness centrality: K5""" + G=networkx.complete_graph(5) + b=betweenness_centrality_subset(G, + sources=[0], + targets=[1,3], + weight=None) + b_answer={0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_P5_directed(self): + """Betweenness centrality: P5 directed""" + G=networkx.DiGraph() + G.add_path(list(range(5))) + b_answer={0:0,1:1,2:1,3:0,4:0,5:0} + b=betweenness_centrality_subset(G, + sources=[0], + targets=[3], + weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_P5(self): + """Betweenness centrality: P5""" + G=networkx.Graph() + G.add_path(list(range(5))) + b_answer={0:0,1:0.5,2:0.5,3:0,4:0,5:0} + b=betweenness_centrality_subset(G, + sources=[0], + targets=[3], + weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_P5_multiple_target(self): + """Betweenness centrality: P5 multiple target""" + G=networkx.Graph() + G.add_path(list(range(5))) + b_answer={0:0,1:1,2:1,3:0.5,4:0,5:0} + b=betweenness_centrality_subset(G, + sources=[0], + targets=[3,4], + weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_box(self): + """Betweenness centrality: box""" + G=networkx.Graph() + G.add_edge(0,1) + G.add_edge(0,2) + G.add_edge(1,3) + G.add_edge(2,3) + b_answer={0:0,1:0.25,2:0.25,3:0} + b=betweenness_centrality_subset(G, + sources=[0], + targets=[3], + weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_box_and_path(self): + """Betweenness centrality: box and path""" + G=networkx.Graph() + G.add_edge(0,1) + G.add_edge(0,2) + G.add_edge(1,3) + G.add_edge(2,3) + G.add_edge(3,4) + G.add_edge(4,5) + b_answer={0:0,1:0.5,2:0.5,3:0.5,4:0,5:0} + b=betweenness_centrality_subset(G, + sources=[0], + targets=[3,4], + weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_box_and_path2(self): + """Betweenness centrality: box and path multiple target""" + G=networkx.Graph() + G.add_edge(0,1) + G.add_edge(1,2) + G.add_edge(2,3) + G.add_edge(1,20) + G.add_edge(20,3) + G.add_edge(3,4) + b_answer={0:0,1:1.0,2:0.5,20:0.5,3:0.5,4:0} + b=betweenness_centrality_subset(G, + sources=[0], + targets=[3,4]) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + +class TestBetweennessCentralitySources: + def test_K5(self): + """Betweenness centrality: K5""" + G=networkx.complete_graph(5) + b=networkx.betweenness_centrality_source(G, + weight=None, + normalized=False) + b_answer={0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_P3(self): + """Betweenness centrality: P3""" + G=networkx.path_graph(3) + b_answer={0: 0.0, 1: 1.0, 2: 0.0} + b=networkx.betweenness_centrality_source(G, + weight=None, + normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + + +class TestEdgeSubsetBetweennessCentrality: + + def test_K5(self): + """Edge betweenness centrality: K5""" + G=networkx.complete_graph(5) + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[1,3], + weight=None) + b_answer=dict.fromkeys(G.edges(),0) + b_answer[(0,3)]=0.5 + b_answer[(0,1)]=0.5 + for n in sorted(G.edges()): + print(n,b[n]) + assert_almost_equal(b[n],b_answer[n]) + + def test_P5_directed(self): + """Edge betweenness centrality: P5 directed""" + G=networkx.DiGraph() + G.add_path(list(range(5))) + b_answer=dict.fromkeys(G.edges(),0) + b_answer[(0,1)]=1 + b_answer[(1,2)]=1 + b_answer[(2,3)]=1 + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[3], + weight=None) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_P5(self): + """Edge betweenness centrality: P5""" + G=networkx.Graph() + G.add_path(list(range(5))) + b_answer=dict.fromkeys(G.edges(),0) + b_answer[(0,1)]=0.5 + b_answer[(1,2)]=0.5 + b_answer[(2,3)]=0.5 + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[3], + weight=None) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_P5_multiple_target(self): + """Edge betweenness centrality: P5 multiple target""" + G=networkx.Graph() + G.add_path(list(range(5))) + b_answer=dict.fromkeys(G.edges(),0) + b_answer[(0,1)]=1 + b_answer[(1,2)]=1 + b_answer[(2,3)]=1 + b_answer[(3,4)]=0.5 + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[3,4], + weight=None) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_box(self): + """Edge etweenness centrality: box""" + G=networkx.Graph() + G.add_edge(0,1) + G.add_edge(0,2) + G.add_edge(1,3) + G.add_edge(2,3) + b_answer=dict.fromkeys(G.edges(),0) + + b_answer[(0,1)]=0.25 + b_answer[(0,2)]=0.25 + b_answer[(1,3)]=0.25 + b_answer[(2,3)]=0.25 + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[3], + weight=None) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + def test_box_and_path(self): + """Edge etweenness centrality: box and path""" + G=networkx.Graph() + G.add_edge(0,1) + G.add_edge(0,2) + G.add_edge(1,3) + G.add_edge(2,3) + G.add_edge(3,4) + G.add_edge(4,5) + b_answer=dict.fromkeys(G.edges(),0) + b_answer[(0,1)]=1.0/2 + b_answer[(0,2)]=1.0/2 + b_answer[(1,3)]=1.0/2 + b_answer[(2,3)]=1.0/2 + b_answer[(3,4)]=1.0/2 + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[3,4], + weight=None) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + + def test_box_and_path2(self): + """Edge betweenness centrality: box and path multiple target""" + G=networkx.Graph() + G.add_edge(0,1) + G.add_edge(1,2) + G.add_edge(2,3) + G.add_edge(1,20) + G.add_edge(20,3) + G.add_edge(3,4) + b_answer=dict.fromkeys(G.edges(),0) + b_answer[(0,1)]=1.0 + b_answer[(1,20)]=1.0/2 + b_answer[(3,20)]=1.0/2 + b_answer[(1,2)]=1.0/2 + b_answer[(2,3)]=1.0/2 + b_answer[(3,4)]=1.0/2 + b=edge_betweenness_centrality_subset(G, + sources=[0], + targets=[3,4], + weight=None) + for n in sorted(G.edges()): + assert_almost_equal(b[n],b_answer[n]) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_closeness_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_closeness_centrality.py new file mode 100644 index 0000000..71b0009 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_closeness_centrality.py @@ -0,0 +1,93 @@ +""" +Tests for degree centrality. +""" +from nose.tools import * +import networkx as nx + +class TestClosenessCentrality: + def setUp(self): + + self.K = nx.krackhardt_kite_graph() + self.P3 = nx.path_graph(3) + self.P4 = nx.path_graph(4) + self.K5 = nx.complete_graph(5) + + self.C4=nx.cycle_graph(4) + self.T=nx.balanced_tree(r=2, h=2) + self.Gb = nx.Graph() + self.Gb.add_edges_from([(0,1), (0,2), (1,3), (2,3), + (2,4), (4,5), (3,5)]) + + + F = nx.florentine_families_graph() + self.F = F + + + def test_k5_closeness(self): + c=nx.closeness_centrality(self.K5) + d={0: 1.000, + 1: 1.000, + 2: 1.000, + 3: 1.000, + 4: 1.000} + for n in sorted(self.K5): + assert_almost_equal(c[n],d[n],places=3) + + def test_p3_closeness(self): + c=nx.closeness_centrality(self.P3) + d={0: 0.667, + 1: 1.000, + 2: 0.667} + for n in sorted(self.P3): + assert_almost_equal(c[n],d[n],places=3) + + def test_krackhardt_closeness(self): + c=nx.closeness_centrality(self.K) + d={0: 0.529, + 1: 0.529, + 2: 0.500, + 3: 0.600, + 4: 0.500, + 5: 0.643, + 6: 0.643, + 7: 0.600, + 8: 0.429, + 9: 0.310} + for n in sorted(self.K): + assert_almost_equal(c[n],d[n],places=3) + + def test_florentine_families_closeness(self): + c=nx.closeness_centrality(self.F) + d={'Acciaiuoli': 0.368, + 'Albizzi': 0.483, + 'Barbadori': 0.4375, + 'Bischeri': 0.400, + 'Castellani': 0.389, + 'Ginori': 0.333, + 'Guadagni': 0.467, + 'Lamberteschi': 0.326, + 'Medici': 0.560, + 'Pazzi': 0.286, + 'Peruzzi': 0.368, + 'Ridolfi': 0.500, + 'Salviati': 0.389, + 'Strozzi': 0.4375, + 'Tornabuoni': 0.483} + for n in sorted(self.F): + assert_almost_equal(c[n],d[n],places=3) + + def test_weighted_closeness(self): + XG=nx.Graph() + XG.add_weighted_edges_from([('s','u',10), ('s','x',5), ('u','v',1), + ('u','x',2), ('v','y',1), ('x','u',3), + ('x','v',5), ('x','y',2), ('y','s',7), + ('y','v',6)]) + c=nx.closeness_centrality(XG,distance='weight') + d={'y': 0.200, + 'x': 0.286, + 's': 0.138, + 'u': 0.235, + 'v': 0.200} + for n in sorted(XG): + assert_almost_equal(c[n],d[n],places=3) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_communicability.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_communicability.py new file mode 100644 index 0000000..d03be65 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_communicability.py @@ -0,0 +1,134 @@ +from collections import defaultdict +from nose.tools import * +from nose import SkipTest +import networkx as nx +from networkx.algorithms.centrality.communicability_alg import * + +class TestCommunicability: + @classmethod + def setupClass(cls): + global numpy + global scipy + try: + import numpy + except ImportError: + raise SkipTest('NumPy not available.') + try: + import scipy + except ImportError: + raise SkipTest('SciPy not available.') + + + def test_communicability_centrality(self): + answer={0: 1.5430806348152433, 1: 1.5430806348152433} + result=communicability_centrality(nx.path_graph(2)) + for k,v in result.items(): + assert_almost_equal(answer[k],result[k],places=7) + + answer1={'1': 1.6445956054135658, + 'Albert': 2.4368257358712189, + 'Aric': 2.4368257358712193, + 'Dan':3.1306328496328168, + 'Franck': 2.3876142275231915} + G1=nx.Graph([('Franck','Aric'),('Aric','Dan'),('Dan','Albert'), + ('Albert','Franck'),('Dan','1'),('Franck','Albert')]) + result1=communicability_centrality(G1) + for k,v in result1.items(): + assert_almost_equal(answer1[k],result1[k],places=7) + result1=communicability_centrality_exp(G1) + for k,v in result1.items(): + assert_almost_equal(answer1[k],result1[k],places=7) + + def test_communicability_betweenness_centrality(self): + answer={0: 0.07017447951484615, 1: 0.71565598701107991, + 2: 0.71565598701107991, 3: 0.07017447951484615} + result=communicability_betweenness_centrality(nx.path_graph(4)) + for k,v in result.items(): + assert_almost_equal(answer[k],result[k],places=7) + + answer1={'1': 0.060039074193949521, + 'Albert': 0.315470761661372, + 'Aric': 0.31547076166137211, + 'Dan': 0.68297778678316201, + 'Franck': 0.21977926617449497} + G1=nx.Graph([('Franck','Aric'), + ('Aric','Dan'),('Dan','Albert'),('Albert','Franck'), + ('Dan','1'),('Franck','Albert')]) + result1=communicability_betweenness_centrality(G1) + for k,v in result1.items(): + assert_almost_equal(answer1[k],result1[k],places=7) + + def test_communicability_betweenness_centrality_small(self): + G = nx.Graph([(1,2)]) + result=communicability_betweenness_centrality(G) + assert_equal(result, {1:0,2:0}) + + + def test_communicability(self): + answer={0 :{0: 1.5430806348152435, + 1: 1.1752011936438012 + }, + 1 :{0: 1.1752011936438012, + 1: 1.5430806348152435 + } + } +# answer={(0, 0): 1.5430806348152435, +# (0, 1): 1.1752011936438012, +# (1, 0): 1.1752011936438012, +# (1, 1): 1.5430806348152435} + + result=communicability(nx.path_graph(2)) + for k1,val in result.items(): + for k2 in val: + assert_almost_equal(answer[k1][k2],result[k1][k2],places=7) + + def test_communicability2(self): + + answer_orig ={('1', '1'): 1.6445956054135658, + ('1', 'Albert'): 0.7430186221096251, + ('1', 'Aric'): 0.7430186221096251, + ('1', 'Dan'): 1.6208126320442937, + ('1', 'Franck'): 0.42639707170035257, + ('Albert', '1'): 0.7430186221096251, + ('Albert', 'Albert'): 2.4368257358712189, + ('Albert', 'Aric'): 1.4368257358712191, + ('Albert', 'Dan'): 2.0472097037446453, + ('Albert', 'Franck'): 1.8340111678944691, + ('Aric', '1'): 0.7430186221096251, + ('Aric', 'Albert'): 1.4368257358712191, + ('Aric', 'Aric'): 2.4368257358712193, + ('Aric', 'Dan'): 2.0472097037446457, + ('Aric', 'Franck'): 1.8340111678944691, + ('Dan', '1'): 1.6208126320442937, + ('Dan', 'Albert'): 2.0472097037446453, + ('Dan', 'Aric'): 2.0472097037446457, + ('Dan', 'Dan'): 3.1306328496328168, + ('Dan', 'Franck'): 1.4860372442192515, + ('Franck', '1'): 0.42639707170035257, + ('Franck', 'Albert'): 1.8340111678944691, + ('Franck', 'Aric'): 1.8340111678944691, + ('Franck', 'Dan'): 1.4860372442192515, + ('Franck', 'Franck'): 2.3876142275231915} + + answer=defaultdict(dict) + for (k1,k2),v in answer_orig.items(): + answer[k1][k2]=v + + G1=nx.Graph([('Franck','Aric'),('Aric','Dan'),('Dan','Albert'), + ('Albert','Franck'),('Dan','1'),('Franck','Albert')]) + + result=communicability(G1) + for k1,val in result.items(): + for k2 in val: + assert_almost_equal(answer[k1][k2],result[k1][k2],places=7) + + result=communicability_exp(G1) + for k1,val in result.items(): + for k2 in val: + assert_almost_equal(answer[k1][k2],result[k1][k2],places=7) + + + def test_estrada_index(self): + answer=1041.2470334195475 + result=estrada_index(nx.karate_club_graph()) + assert_almost_equal(answer,result,places=7) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py new file mode 100644 index 0000000..46e8a2d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx +from nose.plugins.attrib import attr + +from networkx import edge_current_flow_betweenness_centrality \ + as edge_current_flow + +from networkx import approximate_current_flow_betweenness_centrality \ + as approximate_cfbc + +class TestFlowBetweennessCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + import scipy + except ImportError: + raise SkipTest('NumPy not available.') + + def test_K4_normalized(self): + """Betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + b_answer={0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + G.add_edge(0,1,{'weight':0.5,'other':0.3}) + b=networkx.current_flow_betweenness_centrality(G,normalized=True,weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + wb_answer={0: 0.2222222, 1: 0.2222222, 2: 0.30555555, 3: 0.30555555} + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],wb_answer[n]) + wb_answer={0: 0.2051282, 1: 0.2051282, 2: 0.33974358, 3: 0.33974358} + b=networkx.current_flow_betweenness_centrality(G,normalized=True,weight='other') + for n in sorted(G): + assert_almost_equal(b[n],wb_answer[n]) + + def test_K4(self): + """Betweenness centrality: K4""" + G=networkx.complete_graph(4) + for solver in ['full','lu','cg']: + b=networkx.current_flow_betweenness_centrality(G, normalized=False, + solver=solver) + b_answer={0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P4_normalized(self): + """Betweenness centrality: P4 normalized""" + G=networkx.path_graph(4) + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + b_answer={0: 0, 1: 2./3, 2: 2./3, 3:0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P4(self): + """Betweenness centrality: P4""" + G=networkx.path_graph(4) + b=networkx.current_flow_betweenness_centrality(G,normalized=False) + b_answer={0: 0, 1: 2, 2: 2, 3: 0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_star(self): + """Betweenness centrality: star """ + G=networkx.Graph() + G.add_star(['a','b','c','d']) + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + b_answer={'a': 1.0, 'b': 0.0, 'c': 0.0, 'd':0.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + + def test_solers(self): + """Betweenness centrality: alternate solvers""" + G=networkx.complete_graph(4) + for solver in ['full','lu','cg']: + b=networkx.current_flow_betweenness_centrality(G,normalized=False, + solver=solver) + b_answer={0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + +class TestApproximateFlowBetweennessCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + global assert_allclose + try: + import numpy as np + import scipy + from numpy.testing import assert_allclose + except ImportError: + raise SkipTest('NumPy not available.') + + def test_K4_normalized(self): + "Approximate current-flow betweenness centrality: K4 normalized" + G=networkx.complete_graph(4) + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + epsilon=0.1 + ba = approximate_cfbc(G,normalized=True, epsilon=0.5*epsilon) + for n in sorted(G): + assert_allclose(b[n],ba[n],atol=epsilon) + + def test_K4(self): + "Approximate current-flow betweenness centrality: K4" + G=networkx.complete_graph(4) + b=networkx.current_flow_betweenness_centrality(G,normalized=False) + epsilon=0.1 + ba = approximate_cfbc(G,normalized=False, epsilon=0.5*epsilon) + for n in sorted(G): + assert_allclose(b[n],ba[n],atol=epsilon*len(G)**2) + + def test_star(self): + "Approximate current-flow betweenness centrality: star" + G=networkx.Graph() + G.add_star(['a','b','c','d']) + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + epsilon=0.1 + ba = approximate_cfbc(G,normalized=True, epsilon=0.5*epsilon) + for n in sorted(G): + assert_allclose(b[n],ba[n],atol=epsilon) + + def test_grid(self): + "Approximate current-flow betweenness centrality: 2d grid" + G=networkx.grid_2d_graph(4,4) + b=networkx.current_flow_betweenness_centrality(G,normalized=True) + epsilon=0.1 + ba = approximate_cfbc(G,normalized=True, epsilon=0.5*epsilon) + for n in sorted(G): + assert_allclose(b[n],ba[n],atol=epsilon) + + def test_solvers(self): + "Approximate current-flow betweenness centrality: solvers" + G=networkx.complete_graph(4) + epsilon=0.1 + for solver in ['full','lu','cg']: + b=approximate_cfbc(G,normalized=False,solver=solver, + epsilon=0.5*epsilon) + b_answer={0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + assert_allclose(b[n],b_answer[n],atol=epsilon) + + + + + +class TestWeightedFlowBetweennessCentrality(object): + pass + + +class TestEdgeFlowBetweennessCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + import scipy + except ImportError: + raise SkipTest('NumPy not available.') + + def test_K4(self): + """Edge flow betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=edge_current_flow(G,normalized=True) + b_answer=dict.fromkeys(G.edges(),0.25) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + def test_K4_normalized(self): + """Edge flow betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=edge_current_flow(G,normalized=False) + b_answer=dict.fromkeys(G.edges(),0.75) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + def test_C4(self): + """Edge flow betweenness centrality: C4""" + G=networkx.cycle_graph(4) + b=edge_current_flow(G,normalized=False) + b_answer={(0, 1):1.25,(0, 3):1.25, (1, 2):1.25, (2, 3): 1.25} + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + + def test_P4(self): + """Edge betweenness centrality: P4""" + G=networkx.path_graph(4) + b=edge_current_flow(G,normalized=False) + b_answer={(0, 1):1.5,(1, 2):2.0, (2, 3):1.5} + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py new file mode 100644 index 0000000..4c7acd6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx +from nose.plugins.attrib import attr + +from networkx import edge_current_flow_betweenness_centrality \ + as edge_current_flow + +from networkx import edge_current_flow_betweenness_centrality_subset \ + as edge_current_flow_subset + +class TestFlowBetweennessCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + import scipy + except ImportError: + raise SkipTest('NumPy not available.') + + + def test_K4_normalized(self): + """Betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True) + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_K4(self): + """Betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True) + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + # test weighted network + G.add_edge(0,1,{'weight':0.5,'other':0.3}) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True, + weight=None) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True) + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True, + weight='other') + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True,weight='other') + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P4_normalized(self): + """Betweenness centrality: P4 normalized""" + G=networkx.path_graph(4) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True) + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P4(self): + """Betweenness centrality: P4""" + G=networkx.path_graph(4) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True) + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_star(self): + """Betweenness centrality: star """ + G=networkx.Graph() + G.add_star(['a','b','c','d']) + b=networkx.current_flow_betweenness_centrality_subset(G, + G.nodes(), + G.nodes(), + normalized=True) + b_answer=networkx.current_flow_betweenness_centrality(G,normalized=True) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + +# class TestWeightedFlowBetweennessCentrality(): +# pass + + +class TestEdgeFlowBetweennessCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + import scipy + except ImportError: + raise SkipTest('NumPy not available.') + + def test_K4_normalized(self): + """Betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=True) + b_answer=edge_current_flow(G,normalized=True) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + def test_K4(self): + """Betweenness centrality: K4""" + G=networkx.complete_graph(4) + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=False) + b_answer=edge_current_flow(G,normalized=False) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + # test weighted network + G.add_edge(0,1,{'weight':0.5,'other':0.3}) + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=False,weight=None) + # weight is None => same as unweighted network + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=False) + b_answer=edge_current_flow(G,normalized=False) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=False,weight='other') + b_answer=edge_current_flow(G,normalized=False,weight='other') + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + + def test_C4(self): + """Edge betweenness centrality: C4""" + G=networkx.cycle_graph(4) + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=True) + b_answer=edge_current_flow(G,normalized=True) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + + + def test_P4(self): + """Edge betweenness centrality: P4""" + G=networkx.path_graph(4) + b=edge_current_flow_subset(G,G.nodes(),G.nodes(),normalized=True) + b_answer=edge_current_flow(G,normalized=True) + for (s,t),v1 in b_answer.items(): + v2=b.get((s,t),b.get((t,s))) + assert_almost_equal(v1,v2) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_closeness.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_closeness.py new file mode 100644 index 0000000..28598d4 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_current_flow_closeness.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx + +class TestFlowClosenessCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + import scipy + except ImportError: + raise SkipTest('NumPy not available.') + + + def test_K4(self): + """Closeness centrality: K4""" + G=networkx.complete_graph(4) + b=networkx.current_flow_closeness_centrality(G,normalized=True) + b_answer={0: 2.0, 1: 2.0, 2: 2.0, 3: 2.0} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P4_normalized(self): + """Closeness centrality: P4 normalized""" + G=networkx.path_graph(4) + b=networkx.current_flow_closeness_centrality(G,normalized=True) + b_answer={0: 1./2, 1: 3./4, 2: 3./4, 3:1./2} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + def test_P4(self): + """Closeness centrality: P4""" + G=networkx.path_graph(4) + b=networkx.current_flow_closeness_centrality(G,normalized=False) + b_answer={0: 1.0/6, 1: 1.0/4, 2: 1.0/4, 3:1.0/6} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + def test_star(self): + """Closeness centrality: star """ + G=networkx.Graph() + G.add_star(['a','b','c','d']) + b=networkx.current_flow_closeness_centrality(G,normalized=True) + b_answer={'a': 1.0, 'b': 0.6, 'c': 0.6, 'd':0.6} + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + +class TestWeightedFlowClosenessCentrality(object): + pass diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_degree_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_degree_centrality.py new file mode 100644 index 0000000..9109ddc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_degree_centrality.py @@ -0,0 +1,92 @@ +""" + Unit tests for degree centrality. +""" + +from nose.tools import * + +import networkx as nx + + +class TestDegreeCentrality: + def __init__(self): + + self.K = nx.krackhardt_kite_graph() + self.P3 = nx.path_graph(3) + self.K5 = nx.complete_graph(5) + + F = nx.Graph() # Florentine families + F.add_edge('Acciaiuoli','Medici') + F.add_edge('Castellani','Peruzzi') + F.add_edge('Castellani','Strozzi') + F.add_edge('Castellani','Barbadori') + F.add_edge('Medici','Barbadori') + F.add_edge('Medici','Ridolfi') + F.add_edge('Medici','Tornabuoni') + F.add_edge('Medici','Albizzi') + F.add_edge('Medici','Salviati') + F.add_edge('Salviati','Pazzi') + F.add_edge('Peruzzi','Strozzi') + F.add_edge('Peruzzi','Bischeri') + F.add_edge('Strozzi','Ridolfi') + F.add_edge('Strozzi','Bischeri') + F.add_edge('Ridolfi','Tornabuoni') + F.add_edge('Tornabuoni','Guadagni') + F.add_edge('Albizzi','Ginori') + F.add_edge('Albizzi','Guadagni') + F.add_edge('Bischeri','Guadagni') + F.add_edge('Guadagni','Lamberteschi') + self.F = F + + G = nx.DiGraph() + G.add_edge(0,5) + G.add_edge(1,5) + G.add_edge(2,5) + G.add_edge(3,5) + G.add_edge(4,5) + G.add_edge(5,6) + G.add_edge(5,7) + G.add_edge(5,8) + self.G = G + + def test_degree_centrality_1(self): + d = nx.degree_centrality(self.K5) + exact = dict(zip(range(5), [1]*5)) + for n,dc in d.items(): + assert_almost_equal(exact[n], dc) + + def test_degree_centrality_2(self): + d = nx.degree_centrality(self.P3) + exact = {0:0.5, 1:1, 2:0.5} + for n,dc in d.items(): + assert_almost_equal(exact[n], dc) + + def test_degree_centrality_3(self): + d = nx.degree_centrality(self.K) + exact = {0:.444, 1:.444, 2:.333, 3:.667, 4:.333, + 5:.556, 6:.556, 7:.333, 8:.222, 9:.111} + for n,dc in d.items(): + assert_almost_equal(exact[n], float("%5.3f" % dc)) + + def test_degree_centrality_4(self): + d = nx.degree_centrality(self.F) + names = sorted(self.F.nodes()) + dcs = [0.071, 0.214, 0.143, 0.214, 0.214, 0.071, 0.286, + 0.071, 0.429, 0.071, 0.214, 0.214, 0.143, 0.286, 0.214] + exact = dict(zip(names, dcs)) + for n,dc in d.items(): + assert_almost_equal(exact[n], float("%5.3f" % dc)) + + def test_indegree_centrality(self): + d = nx.in_degree_centrality(self.G) + exact = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, + 5: 0.625, 6: 0.125, 7: 0.125, 8: 0.125} + for n,dc in d.items(): + assert_almost_equal(exact[n], dc) + + def test_outdegree_centrality(self): + d = nx.out_degree_centrality(self.G) + exact = {0: 0.125, 1: 0.125, 2: 0.125, 3: 0.125, + 4: 0.125, 5: 0.375, 6: 0.0, 7: 0.0, 8: 0.0} + for n,dc in d.items(): + assert_almost_equal(exact[n], dc) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py new file mode 100644 index 0000000..22b859c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +import math +from nose import SkipTest +from nose.tools import * +import networkx + +class TestEigenvectorCentrality(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + except ImportError: + raise SkipTest('NumPy not available.') + + def test_K5(self): + """Eigenvector centrality: K5""" + G=networkx.complete_graph(5) + b=networkx.eigenvector_centrality(G) + v=math.sqrt(1/5.0) + b_answer=dict.fromkeys(G,v) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + nstart = dict([(n,1) for n in G]) + b=networkx.eigenvector_centrality(G,nstart=nstart) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n]) + + + b=networkx.eigenvector_centrality_numpy(G) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=3) + + + def test_P3(self): + """Eigenvector centrality: P3""" + G=networkx.path_graph(3) + b_answer={0: 0.5, 1: 0.7071, 2: 0.5} + b=networkx.eigenvector_centrality_numpy(G) + for n in sorted(G): + assert_almost_equal(b[n],b_answer[n],places=4) + + + @raises(networkx.NetworkXError) + def test_maxiter(self): + G=networkx.path_graph(3) + b=networkx.eigenvector_centrality(G,max_iter=0) + +class TestEigenvectorCentralityDirected(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + except ImportError: + raise SkipTest('NumPy not available.') + + def setUp(self): + + G=networkx.DiGraph() + + edges=[(1,2),(1,3),(2,4),(3,2),(3,5),(4,2),(4,5),(4,6),\ + (5,6),(5,7),(5,8),(6,8),(7,1),(7,5),\ + (7,8),(8,6),(8,7)] + + G.add_edges_from(edges,weight=2.0) + self.G=G + self.G.evc=[0.25368793, 0.19576478, 0.32817092, 0.40430835, + 0.48199885, 0.15724483, 0.51346196, 0.32475403] + + H=networkx.DiGraph() + + edges=[(1,2),(1,3),(2,4),(3,2),(3,5),(4,2),(4,5),(4,6),\ + (5,6),(5,7),(5,8),(6,8),(7,1),(7,5),\ + (7,8),(8,6),(8,7)] + + G.add_edges_from(edges) + self.H=G + self.H.evc=[0.25368793, 0.19576478, 0.32817092, 0.40430835, + 0.48199885, 0.15724483, 0.51346196, 0.32475403] + + + def test_eigenvector_centrality_weighted(self): + G=self.G + p=networkx.eigenvector_centrality_numpy(G) + for (a,b) in zip(list(p.values()),self.G.evc): + assert_almost_equal(a,b) + + def test_eigenvector_centrality_unweighted(self): + G=self.H + p=networkx.eigenvector_centrality_numpy(G) + for (a,b) in zip(list(p.values()),self.G.evc): + assert_almost_equal(a,b) + + +class TestEigenvectorCentralityExceptions(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + except ImportError: + raise SkipTest('NumPy not available.') + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @raises(networkx.NetworkXException) + def test_multigraph(self): + e = networkx.eigenvector_centrality(networkx.MultiGraph()) + + @raises(networkx.NetworkXException) + def test_multigraph_numpy(self): + e = networkx.eigenvector_centrality_numpy(networkx.MultiGraph()) + + + @raises(networkx.NetworkXException) + def test_empty(self): + e = networkx.eigenvector_centrality(networkx.Graph()) + + @raises(networkx.NetworkXException) + def test_empty_numpy(self): + e = networkx.eigenvector_centrality_numpy(networkx.Graph()) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_katz_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_katz_centrality.py new file mode 100644 index 0000000..9e8e6d2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_katz_centrality.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +import math +from nose import SkipTest +from nose.tools import * +import networkx + +class TestKatzCentrality(object): + + def test_K5(self): + """Katz centrality: K5""" + G = networkx.complete_graph(5) + alpha = 0.1 + b = networkx.katz_centrality(G, alpha) + v = math.sqrt(1 / 5.0) + b_answer = dict.fromkeys(G, v) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n]) + nstart = dict([(n, 1) for n in G]) + b = networkx.katz_centrality(G, alpha, nstart=nstart) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n]) + + def test_P3(self): + """Katz centrality: P3""" + alpha = 0.1 + G = networkx.path_graph(3) + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162} + b = networkx.katz_centrality(G, alpha) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=4) + + @raises(networkx.NetworkXError) + def test_maxiter(self): + alpha = 0.1 + G = networkx.path_graph(3) + b = networkx.katz_centrality(G, alpha, max_iter=0) + + def test_beta_as_scalar(self): + alpha = 0.1 + beta = 0.1 + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162} + G = networkx.path_graph(3) + b = networkx.katz_centrality(G, alpha, beta) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=4) + + def test_beta_as_dict(self): + alpha = 0.1 + beta = {0: 1.0, 1: 1.0, 2: 1.0} + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162} + G = networkx.path_graph(3) + b = networkx.katz_centrality(G, alpha, beta) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=4) + + + def test_multiple_alpha(self): + alpha_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + for alpha in alpha_list: + b_answer = {0.1: {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162}, + 0.2: {0: 0.5454545454545454, 1: 0.6363636363636365, + 2: 0.5454545454545454}, + 0.3: {0: 0.5333964609104419, 1: 0.6564879518897746, + 2: 0.5333964609104419}, + 0.4: {0: 0.5232045649263551, 1: 0.6726915834767423, + 2: 0.5232045649263551}, + 0.5: {0: 0.5144957746691622, 1: 0.6859943117075809, + 2: 0.5144957746691622}, + 0.6: {0: 0.5069794004195823, 1: 0.6970966755769258, + 2: 0.5069794004195823}} + G = networkx.path_graph(3) + b = networkx.katz_centrality(G, alpha) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[alpha][n], places=4) + + @raises(networkx.NetworkXException) + def test_multigraph(self): + e = networkx.katz_centrality(networkx.MultiGraph(), 0.1) + + def test_empty(self): + e = networkx.katz_centrality(networkx.Graph(), 0.1) + assert_equal(e, {}) + + @raises(networkx.NetworkXException) + def test_bad_beta(self): + G = networkx.Graph([(0,1)]) + beta = {0:77} + e = networkx.katz_centrality(G, 0.1,beta=beta) + + @raises(networkx.NetworkXException) + def test_bad_beta_numbe(self): + G = networkx.Graph([(0,1)]) + e = networkx.katz_centrality(G, 0.1,beta='foo') + + +class TestKatzCentralityNumpy(object): + numpy = 1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + except ImportError: + raise SkipTest('NumPy not available.') + + def test_K5(self): + """Katz centrality: K5""" + G = networkx.complete_graph(5) + alpha = 0.1 + b = networkx.katz_centrality(G, alpha) + v = math.sqrt(1 / 5.0) + b_answer = dict.fromkeys(G, v) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n]) + nstart = dict([(n, 1) for n in G]) + b = networkx.eigenvector_centrality_numpy(G) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=3) + + def test_P3(self): + """Katz centrality: P3""" + alpha = 0.1 + G = networkx.path_graph(3) + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162} + b = networkx.katz_centrality_numpy(G, alpha) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=4) + + + def test_beta_as_scalar(self): + alpha = 0.1 + beta = 0.1 + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162} + G = networkx.path_graph(3) + b = networkx.katz_centrality_numpy(G, alpha, beta) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=4) + + + def test_beta_as_dict(self): + alpha = 0.1 + beta = {0: 1.0, 1: 1.0, 2: 1.0} + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162} + G = networkx.path_graph(3) + b = networkx.katz_centrality_numpy(G, alpha, beta) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[n], places=4) + + + def test_multiple_alpha(self): + alpha_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + for alpha in alpha_list: + b_answer = {0.1: {0: 0.5598852584152165, 1: 0.6107839182711449, + 2: 0.5598852584152162}, + 0.2: {0: 0.5454545454545454, 1: 0.6363636363636365, + 2: 0.5454545454545454}, + 0.3: {0: 0.5333964609104419, 1: 0.6564879518897746, + 2: 0.5333964609104419}, + 0.4: {0: 0.5232045649263551, 1: 0.6726915834767423, + 2: 0.5232045649263551}, + 0.5: {0: 0.5144957746691622, 1: 0.6859943117075809, + 2: 0.5144957746691622}, + 0.6: {0: 0.5069794004195823, 1: 0.6970966755769258, + 2: 0.5069794004195823}} + G = networkx.path_graph(3) + b = networkx.katz_centrality_numpy(G, alpha) + for n in sorted(G): + assert_almost_equal(b[n], b_answer[alpha][n], places=4) + + @raises(networkx.NetworkXException) + def test_multigraph(self): + e = networkx.katz_centrality(networkx.MultiGraph(), 0.1) + + def test_empty(self): + e = networkx.katz_centrality(networkx.Graph(), 0.1) + assert_equal(e, {}) + + @raises(networkx.NetworkXException) + def test_bad_beta(self): + G = networkx.Graph([(0,1)]) + beta = {0:77} + e = networkx.katz_centrality_numpy(G, 0.1,beta=beta) + + @raises(networkx.NetworkXException) + def test_bad_beta_numbe(self): + G = networkx.Graph([(0,1)]) + e = networkx.katz_centrality_numpy(G, 0.1,beta='foo') + + +class TestKatzCentralityDirected(object): + def setUp(self): + G = networkx.DiGraph() + edges = [(1, 2),(1, 3),(2, 4),(3, 2),(3, 5),(4, 2),(4, 5),(4, 6),(5, 6), + (5, 7),(5, 8),(6, 8),(7, 1),(7, 5),(7, 8),(8, 6),(8, 7)] + G.add_edges_from(edges, weight=2.0) + self.G = G + self.G.alpha = 0.1 + self.G.evc = [ + 0.3289589783189635, + 0.2832077296243516, + 0.3425906003685471, + 0.3970420865198392, + 0.41074871061646284, + 0.272257430756461, + 0.4201989685435462, + 0.34229059218038554, + ] + + H = networkx.DiGraph(edges) + self.H = G + self.H.alpha = 0.1 + self.H.evc = [ + 0.3289589783189635, + 0.2832077296243516, + 0.3425906003685471, + 0.3970420865198392, + 0.41074871061646284, + 0.272257430756461, + 0.4201989685435462, + 0.34229059218038554, + ] + + def test_eigenvector_centrality_weighted(self): + G = self.G + alpha = self.G.alpha + p = networkx.katz_centrality(G, alpha) + for (a, b) in zip(list(p.values()), self.G.evc): + assert_almost_equal(a, b) + + def test_eigenvector_centrality_unweighted(self): + G = self.H + alpha = self.H.alpha + p = networkx.katz_centrality(G, alpha) + for (a, b) in zip(list(p.values()), self.G.evc): + assert_almost_equal(a, b) + + +class TestKatzCentralityDirectedNumpy(TestKatzCentralityDirected): + numpy = 1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + + @classmethod + def setupClass(cls): + global np + try: + import numpy as np + except ImportError: + raise SkipTest('NumPy not available.') + + def test_eigenvector_centrality_weighted(self): + G = self.G + alpha = self.G.alpha + p = networkx.katz_centrality_numpy(G, alpha) + for (a, b) in zip(list(p.values()), self.G.evc): + assert_almost_equal(a, b) + + def test_eigenvector_centrality_unweighted(self): + G = self.H + alpha = self.H.alpha + p = networkx.katz_centrality_numpy(G, alpha) + for (a, b) in zip(list(p.values()), self.G.evc): + assert_almost_equal(a, b) + +class TestKatzEigenvectorVKatz(object): + numpy = 1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + + @classmethod + def setupClass(cls): + global np + global eigvals + try: + import numpy as np + from numpy.linalg import eigvals + except ImportError: + raise SkipTest('NumPy not available.') + + def test_eigenvector_v_katz_random(self): + G = networkx.gnp_random_graph(10,0.5) + l = float(max(eigvals(networkx.adjacency_matrix(G)))) + e = networkx.eigenvector_centrality_numpy(G) + k = networkx.katz_centrality_numpy(G, 1.0/l) + for n in G: + assert_almost_equal(e[n], k[n]) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_load_centrality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_load_centrality.py new file mode 100644 index 0000000..8939d26 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/centrality/tests/test_load_centrality.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + + +class TestLoadCentrality: + + def setUp(self): + + G=nx.Graph(); + G.add_edge(0,1,weight=3) + G.add_edge(0,2,weight=2) + G.add_edge(0,3,weight=6) + G.add_edge(0,4,weight=4) + G.add_edge(1,3,weight=5) + G.add_edge(1,5,weight=5) + G.add_edge(2,4,weight=1) + G.add_edge(3,4,weight=2) + G.add_edge(3,5,weight=1) + G.add_edge(4,5,weight=4) + self.G=G + self.exact_weighted={0: 4.0, 1: 0.0, 2: 8.0, 3: 6.0, 4: 8.0, 5: 0.0} + self.K = nx.krackhardt_kite_graph() + self.P3 = nx.path_graph(3) + self.P4 = nx.path_graph(4) + self.K5 = nx.complete_graph(5) + + self.C4=nx.cycle_graph(4) + self.T=nx.balanced_tree(r=2, h=2) + self.Gb = nx.Graph() + self.Gb.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), + (2, 4), (4, 5), (3, 5)]) + self.F = nx.florentine_families_graph() + self.D = nx.cycle_graph(3, create_using=nx.DiGraph()) + self.D.add_edges_from([(3, 0), (4, 3)]) + + def test_not_strongly_connected(self): + b = nx.load_centrality(self.D) + result = {0: 5./12, + 1: 1./4, + 2: 1./12, + 3: 1./4, + 4: 0.000} + for n in sorted(self.D): + assert_almost_equal(result[n], b[n], places=3) + assert_almost_equal(result[n], nx.load_centrality(self.D, n), places=3) + + def test_weighted_load(self): + b=nx.load_centrality(self.G,weight='weight',normalized=False) + for n in sorted(self.G): + assert_equal(b[n],self.exact_weighted[n]) + + def test_k5_load(self): + G=self.K5 + c=nx.load_centrality(G) + d={0: 0.000, + 1: 0.000, + 2: 0.000, + 3: 0.000, + 4: 0.000} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + def test_p3_load(self): + G=self.P3 + c=nx.load_centrality(G) + d={0: 0.000, + 1: 1.000, + 2: 0.000} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + c=nx.load_centrality(G,v=1) + assert_almost_equal(c,1.0) + c=nx.load_centrality(G,v=1,normalized=True) + assert_almost_equal(c,1.0) + + + def test_p2_load(self): + G=nx.path_graph(2) + c=nx.load_centrality(G) + d={0: 0.000, + 1: 0.000} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + + def test_krackhardt_load(self): + G=self.K + c=nx.load_centrality(G) + d={0: 0.023, + 1: 0.023, + 2: 0.000, + 3: 0.102, + 4: 0.000, + 5: 0.231, + 6: 0.231, + 7: 0.389, + 8: 0.222, + 9: 0.000} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + def test_florentine_families_load(self): + G=self.F + c=nx.load_centrality(G) + d={'Acciaiuoli': 0.000, + 'Albizzi': 0.211, + 'Barbadori': 0.093, + 'Bischeri': 0.104, + 'Castellani': 0.055, + 'Ginori': 0.000, + 'Guadagni': 0.251, + 'Lamberteschi': 0.000, + 'Medici': 0.522, + 'Pazzi': 0.000, + 'Peruzzi': 0.022, + 'Ridolfi': 0.117, + 'Salviati': 0.143, + 'Strozzi': 0.106, + 'Tornabuoni': 0.090} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + + def test_unnormalized_k5_load(self): + G=self.K5 + c=nx.load_centrality(G,normalized=False) + d={0: 0.000, + 1: 0.000, + 2: 0.000, + 3: 0.000, + 4: 0.000} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + def test_unnormalized_p3_load(self): + G=self.P3 + c=nx.load_centrality(G,normalized=False) + d={0: 0.000, + 1: 2.000, + 2: 0.000} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + + def test_unnormalized_krackhardt_load(self): + G=self.K + c=nx.load_centrality(G,normalized=False) + d={0: 1.667, + 1: 1.667, + 2: 0.000, + 3: 7.333, + 4: 0.000, + 5: 16.667, + 6: 16.667, + 7: 28.000, + 8: 16.000, + 9: 0.000} + + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + def test_unnormalized_florentine_families_load(self): + G=self.F + c=nx.load_centrality(G,normalized=False) + + d={'Acciaiuoli': 0.000, + 'Albizzi': 38.333, + 'Barbadori': 17.000, + 'Bischeri': 19.000, + 'Castellani': 10.000, + 'Ginori': 0.000, + 'Guadagni': 45.667, + 'Lamberteschi': 0.000, + 'Medici': 95.000, + 'Pazzi': 0.000, + 'Peruzzi': 4.000, + 'Ridolfi': 21.333, + 'Salviati': 26.000, + 'Strozzi': 19.333, + 'Tornabuoni': 16.333} + for n in sorted(G): + assert_almost_equal(c[n],d[n],places=3) + + + def test_load_betweenness_difference(self): + # Difference Between Load and Betweenness + # --------------------------------------- The smallest graph + # that shows the difference between load and betweenness is + # G=ladder_graph(3) (Graph B below) + + # Graph A and B are from Tao Zhou, Jian-Guo Liu, Bing-Hong + # Wang: Comment on ``Scientific collaboration + # networks. II. Shortest paths, weighted networks, and + # centrality". http://arxiv.org/pdf/physics/0511084 + + # Notice that unlike here, their calculation adds to 1 to the + # betweennes of every node i for every path from i to every + # other node. This is exactly what it should be, based on + # Eqn. (1) in their paper: the eqn is B(v) = \sum_{s\neq t, + # s\neq v}{\frac{\sigma_{st}(v)}{\sigma_{st}}}, therefore, + # they allow v to be the target node. + + # We follow Brandes 2001, who follows Freeman 1977 that make + # the sum for betweenness of v exclude paths where v is either + # the source or target node. To agree with their numbers, we + # must additionally, remove edge (4,8) from the graph, see AC + # example following (there is a mistake in the figure in their + # paper - personal communication). + + # A = nx.Graph() + # A.add_edges_from([(0,1), (1,2), (1,3), (2,4), + # (3,5), (4,6), (4,7), (4,8), + # (5,8), (6,9), (7,9), (8,9)]) + B = nx.Graph() # ladder_graph(3) + B.add_edges_from([(0,1), (0,2), (1,3), (2,3), (2,4), (4,5), (3,5)]) + c = nx.load_centrality(B,normalized=False) + d={0: 1.750, + 1: 1.750, + 2: 6.500, + 3: 6.500, + 4: 1.750, + 5: 1.750} + for n in sorted(B): + assert_almost_equal(c[n],d[n],places=3) + + + def test_c4_edge_load(self): + G=self.C4 + c = nx.edge_load(G) + d={(0, 1): 6.000, + (0, 3): 6.000, + (1, 2): 6.000, + (2, 3): 6.000} + for n in G.edges(): + assert_almost_equal(c[n],d[n],places=3) + + def test_p4_edge_load(self): + G=self.P4 + c = nx.edge_load(G) + d={(0, 1): 6.000, + (1, 2): 8.000, + (2, 3): 6.000} + for n in G.edges(): + assert_almost_equal(c[n],d[n],places=3) + + def test_k5_edge_load(self): + G=self.K5 + c = nx.edge_load(G) + d={(0, 1): 5.000, + (0, 2): 5.000, + (0, 3): 5.000, + (0, 4): 5.000, + (1, 2): 5.000, + (1, 3): 5.000, + (1, 4): 5.000, + (2, 3): 5.000, + (2, 4): 5.000, + (3, 4): 5.000} + for n in G.edges(): + assert_almost_equal(c[n],d[n],places=3) + + def test_tree_edge_load(self): + G=self.T + c = nx.edge_load(G) + d={(0, 1): 24.000, + (0, 2): 24.000, + (1, 3): 12.000, + (1, 4): 12.000, + (2, 5): 12.000, + (2, 6): 12.000} + for n in G.edges(): + assert_almost_equal(c[n],d[n],places=3) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/__init__.py new file mode 100644 index 0000000..cf8e951 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/__init__.py @@ -0,0 +1,3 @@ +from networkx.algorithms.chordal.chordal_alg import * + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/chordal_alg.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/chordal_alg.py new file mode 100644 index 0000000..8eb6404 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/chordal_alg.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- +""" +Algorithms for chordal graphs. + +A graph is chordal if every cycle of length at least 4 has a chord +(an edge joining two nodes not adjacent in the cycle). +http://en.wikipedia.org/wiki/Chordal_graph +""" +import networkx as nx +import random +import sys + +__authors__ = "\n".join(['Jesus Cerquides ']) +# Copyright (C) 2010 by +# Jesus Cerquides +# All rights reserved. +# BSD license. + +__all__ = ['is_chordal', + 'find_induced_nodes', + 'chordal_graph_cliques', + 'chordal_graph_treewidth', + 'NetworkXTreewidthBoundExceeded'] + +class NetworkXTreewidthBoundExceeded(nx.NetworkXException): + """Exception raised when a treewidth bound has been provided and it has + been exceeded""" + + +def is_chordal(G): + """Checks whether G is a chordal graph. + + A graph is chordal if every cycle of length at least 4 has a chord + (an edge joining two nodes not adjacent in the cycle). + + Parameters + ---------- + G : graph + A NetworkX graph. + + Returns + ------- + chordal : bool + True if G is a chordal graph and False otherwise. + + Raises + ------ + NetworkXError + The algorithm does not support DiGraph, MultiGraph and MultiDiGraph. + If the input graph is an instance of one of these classes, a + NetworkXError is raised. + + Examples + -------- + >>> import networkx as nx + >>> e=[(1,2),(1,3),(2,3),(2,4),(3,4),(3,5),(3,6),(4,5),(4,6),(5,6)] + >>> G=nx.Graph(e) + >>> nx.is_chordal(G) + True + + Notes + ----- + The routine tries to go through every node following maximum cardinality + search. It returns False when it finds that the separator for any node + is not a clique. Based on the algorithms in [1]_. + + References + ---------- + .. [1] R. E. Tarjan and M. Yannakakis, Simple linear-time algorithms + to test chordality of graphs, test acyclicity of hypergraphs, and + selectively reduce acyclic hypergraphs, SIAM J. Comput., 13 (1984), + pp. 566–579. + """ + if G.is_directed(): + raise nx.NetworkXError('Directed graphs not supported') + if G.is_multigraph(): + raise nx.NetworkXError('Multiply connected graphs not supported.') + if len(_find_chordality_breaker(G))==0: + return True + else: + return False + +def find_induced_nodes(G,s,t,treewidth_bound=sys.maxsize): + """Returns the set of induced nodes in the path from s to t. + + Parameters + ---------- + G : graph + A chordal NetworkX graph + s : node + Source node to look for induced nodes + t : node + Destination node to look for induced nodes + treewith_bound: float + Maximum treewidth acceptable for the graph H. The search + for induced nodes will end as soon as the treewidth_bound is exceeded. + + Returns + ------- + I : Set of nodes + The set of induced nodes in the path from s to t in G + + Raises + ------ + NetworkXError + The algorithm does not support DiGraph, MultiGraph and MultiDiGraph. + If the input graph is an instance of one of these classes, a + NetworkXError is raised. + The algorithm can only be applied to chordal graphs. If + the input graph is found to be non-chordal, a NetworkXError is raised. + + Examples + -------- + >>> import networkx as nx + >>> G=nx.Graph() + >>> G = nx.generators.classic.path_graph(10) + >>> I = nx.find_induced_nodes(G,1,9,2) + >>> list(I) + [1, 2, 3, 4, 5, 6, 7, 8, 9] + + Notes + ----- + G must be a chordal graph and (s,t) an edge that is not in G. + + If a treewidth_bound is provided, the search for induced nodes will end + as soon as the treewidth_bound is exceeded. + + The algorithm is inspired by Algorithm 4 in [1]_. + A formal definition of induced node can also be found on that reference. + + References + ---------- + .. [1] Learning Bounded Treewidth Bayesian Networks. + Gal Elidan, Stephen Gould; JMLR, 9(Dec):2699--2731, 2008. + http://jmlr.csail.mit.edu/papers/volume9/elidan08a/elidan08a.pdf + """ + if not is_chordal(G): + raise nx.NetworkXError("Input graph is not chordal.") + + H = nx.Graph(G) + H.add_edge(s,t) + I = set() + triplet = _find_chordality_breaker(H,s,treewidth_bound) + while triplet: + (u,v,w) = triplet + I.update(triplet) + for n in triplet: + if n!=s: + H.add_edge(s,n) + triplet = _find_chordality_breaker(H,s,treewidth_bound) + if I: + # Add t and the second node in the induced path from s to t. + I.add(t) + for u in G[s]: + if len(I & set(G[u]))==2: + I.add(u) + break + return I + +def chordal_graph_cliques(G): + """Returns the set of maximal cliques of a chordal graph. + + The algorithm breaks the graph in connected components and performs a + maximum cardinality search in each component to get the cliques. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + cliques : A set containing the maximal cliques in G. + + Raises + ------ + NetworkXError + The algorithm does not support DiGraph, MultiGraph and MultiDiGraph. + If the input graph is an instance of one of these classes, a + NetworkXError is raised. + The algorithm can only be applied to chordal graphs. If the + input graph is found to be non-chordal, a NetworkXError is raised. + + Examples + -------- + >>> import networkx as nx + >>> e= [(1,2),(1,3),(2,3),(2,4),(3,4),(3,5),(3,6),(4,5),(4,6),(5,6),(7,8)] + >>> G = nx.Graph(e) + >>> G.add_node(9) + >>> setlist = nx.chordal_graph_cliques(G) + """ + if not is_chordal(G): + raise nx.NetworkXError("Input graph is not chordal.") + + cliques = set() + for C in nx.connected.connected_component_subgraphs(G): + cliques |= _connected_chordal_graph_cliques(C) + + return cliques + + +def chordal_graph_treewidth(G): + """Returns the treewidth of the chordal graph G. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + treewidth : int + The size of the largest clique in the graph minus one. + + Raises + ------ + NetworkXError + The algorithm does not support DiGraph, MultiGraph and MultiDiGraph. + If the input graph is an instance of one of these classes, a + NetworkXError is raised. + The algorithm can only be applied to chordal graphs. If + the input graph is found to be non-chordal, a NetworkXError is raised. + + Examples + -------- + >>> import networkx as nx + >>> e = [(1,2),(1,3),(2,3),(2,4),(3,4),(3,5),(3,6),(4,5),(4,6),(5,6),(7,8)] + >>> G = nx.Graph(e) + >>> G.add_node(9) + >>> nx.chordal_graph_treewidth(G) + 3 + + References + ---------- + .. [1] http://en.wikipedia.org/wiki/Tree_decomposition#Treewidth + """ + if not is_chordal(G): + raise nx.NetworkXError("Input graph is not chordal.") + + max_clique = -1 + for clique in nx.chordal_graph_cliques(G): + max_clique = max(max_clique,len(clique)) + return max_clique - 1 + +def _is_complete_graph(G): + """Returns True if G is a complete graph.""" + if G.number_of_selfloops()>0: + raise nx.NetworkXError("Self loop found in _is_complete_graph()") + n = G.number_of_nodes() + if n < 2: + return True + e = G.number_of_edges() + max_edges = ((n * (n-1))/2) + return e == max_edges + + +def _find_missing_edge(G): + """ Given a non-complete graph G, returns a missing edge.""" + nodes=set(G) + for u in G: + missing=nodes-set(list(G[u].keys())+[u]) + if missing: + return (u,missing.pop()) + + +def _max_cardinality_node(G,choices,wanna_connect): + """Returns a the node in choices that has more connections in G + to nodes in wanna_connect. + """ +# max_number = None + max_number = -1 + for x in choices: + number=len([y for y in G[x] if y in wanna_connect]) + if number > max_number: + max_number = number + max_cardinality_node = x + return max_cardinality_node + + +def _find_chordality_breaker(G,s=None,treewidth_bound=sys.maxsize): + """ Given a graph G, starts a max cardinality search + (starting from s if s is given and from a random node otherwise) + trying to find a non-chordal cycle. + + If it does find one, it returns (u,v,w) where u,v,w are the three + nodes that together with s are involved in the cycle. + """ + + unnumbered = set(G) + if s is None: + s = random.choice(list(unnumbered)) + unnumbered.remove(s) + numbered = set([s]) +# current_treewidth = None + current_treewidth = -1 + while unnumbered:# and current_treewidth <= treewidth_bound: + v = _max_cardinality_node(G,unnumbered,numbered) + unnumbered.remove(v) + numbered.add(v) + clique_wanna_be = set(G[v]) & numbered + sg = G.subgraph(clique_wanna_be) + if _is_complete_graph(sg): + # The graph seems to be chordal by now. We update the treewidth + current_treewidth = max(current_treewidth,len(clique_wanna_be)) + if current_treewidth > treewidth_bound: + raise nx.NetworkXTreewidthBoundExceeded(\ + "treewidth_bound exceeded: %s"%current_treewidth) + else: + # sg is not a clique, + # look for an edge that is not included in sg + (u,w) = _find_missing_edge(sg) + return (u,v,w) + return () + + + +def _connected_chordal_graph_cliques(G): + """Return the set of maximal cliques of a connected chordal graph.""" + if G.number_of_nodes() == 1: + x = frozenset(G.nodes()) + return set([x]) + else: + cliques = set() + unnumbered = set(G.nodes()) + v = random.choice(list(unnumbered)) + unnumbered.remove(v) + numbered = set([v]) + clique_wanna_be = set([v]) + while unnumbered: + v = _max_cardinality_node(G,unnumbered,numbered) + unnumbered.remove(v) + numbered.add(v) + new_clique_wanna_be = set(G.neighbors(v)) & numbered + sg = G.subgraph(clique_wanna_be) + if _is_complete_graph(sg): + new_clique_wanna_be.add(v) + if not new_clique_wanna_be >= clique_wanna_be: + cliques.add(frozenset(clique_wanna_be)) + clique_wanna_be = new_clique_wanna_be + else: + raise nx.NetworkXError("Input graph is not chordal.") + cliques.add(frozenset(clique_wanna_be)) + return cliques + + + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/tests/test_chordal.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/tests/test_chordal.py new file mode 100644 index 0000000..4ec0b5b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/chordal/tests/test_chordal.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestMCS: + + def setUp(self): + # simple graph + connected_chordal_G=nx.Graph() + connected_chordal_G.add_edges_from([(1,2),(1,3),(2,3),(2,4),(3,4), + (3,5),(3,6),(4,5),(4,6),(5,6)]) + self.connected_chordal_G=connected_chordal_G + + chordal_G = nx.Graph() + chordal_G.add_edges_from([(1,2),(1,3),(2,3),(2,4),(3,4), + (3,5),(3,6),(4,5),(4,6),(5,6),(7,8)]) + chordal_G.add_node(9) + self.chordal_G=chordal_G + + non_chordal_G = nx.Graph() + non_chordal_G.add_edges_from([(1,2),(1,3),(2,4),(2,5),(3,4),(3,5)]) + self.non_chordal_G = non_chordal_G + + def test_is_chordal(self): + assert_false(nx.is_chordal(self.non_chordal_G)) + assert_true(nx.is_chordal(self.chordal_G)) + assert_true(nx.is_chordal(self.connected_chordal_G)) + assert_true(nx.is_chordal(nx.complete_graph(3))) + assert_true(nx.is_chordal(nx.cycle_graph(3))) + assert_false(nx.is_chordal(nx.cycle_graph(5))) + + def test_induced_nodes(self): + G = nx.generators.classic.path_graph(10) + I = nx.find_induced_nodes(G,1,9,2) + assert_equal(I,set([1,2,3,4,5,6,7,8,9])) + assert_raises(nx.NetworkXTreewidthBoundExceeded, + nx.find_induced_nodes,G,1,9,1) + I = nx.find_induced_nodes(self.chordal_G,1,6) + assert_equal(I,set([1,2,4,6])) + assert_raises(nx.NetworkXError, + nx.find_induced_nodes,self.non_chordal_G,1,5) + + def test_chordal_find_cliques(self): + cliques = set([frozenset([9]),frozenset([7,8]),frozenset([1,2,3]), + frozenset([2,3,4]),frozenset([3,4,5,6])]) + assert_equal(nx.chordal_graph_cliques(self.chordal_G),cliques) + + def test_chordal_find_cliques_path(self): + G = nx.path_graph(10) + cliqueset = nx.chordal_graph_cliques(G) + for (u,v) in G.edges_iter(): + assert_true(frozenset([u,v]) in cliqueset + or frozenset([v,u]) in cliqueset) + + def test_chordal_find_cliquesCC(self): + cliques = set([frozenset([1,2,3]),frozenset([2,3,4]), + frozenset([3,4,5,6])]) + assert_equal(nx.chordal_graph_cliques(self.connected_chordal_G),cliques) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/clique.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/clique.py new file mode 100644 index 0000000..08d9ade --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/clique.py @@ -0,0 +1,516 @@ +""" +======= +Cliques +======= + +Find and manipulate cliques of graphs. + +Note that finding the largest clique of a graph has been +shown to be an NP-complete problem; the algorithms here +could take a long time to run. + +http://en.wikipedia.org/wiki/Clique_problem +""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx +from networkx.utils.decorators import * +__author__ = """Dan Schult (dschult@colgate.edu)""" +__all__ = ['find_cliques', 'find_cliques_recursive', 'make_max_clique_graph', + 'make_clique_bipartite' ,'graph_clique_number', + 'graph_number_of_cliques', 'node_clique_number', + 'number_of_cliques', 'cliques_containing_node', + 'project_down', 'project_up'] + + +@not_implemented_for('directed') +def find_cliques(G): + """Search for all maximal cliques in a graph. + + Maximal cliques are the largest complete subgraph containing + a given node. The largest maximal clique is sometimes called + the maximum clique. + + Returns + ------- + generator of lists: genetor of member list for each maximal clique + + See Also + -------- + find_cliques_recursive : + A recursive version of the same algorithm + + Notes + ----- + To obtain a list of cliques, use list(find_cliques(G)). + + Based on the algorithm published by Bron & Kerbosch (1973) [1]_ + as adapated by Tomita, Tanaka and Takahashi (2006) [2]_ + and discussed in Cazals and Karande (2008) [3]_. + The method essentially unrolls the recursion used in + the references to avoid issues of recursion stack depth. + + This algorithm is not suitable for directed graphs. + + This algorithm ignores self-loops and parallel edges as + clique is not conventionally defined with such edges. + + There are often many cliques in graphs. This algorithm can + run out of memory for large graphs. + + References + ---------- + .. [1] Bron, C. and Kerbosch, J. 1973. + Algorithm 457: finding all cliques of an undirected graph. + Commun. ACM 16, 9 (Sep. 1973), 575-577. + http://portal.acm.org/citation.cfm?doid=362342.362367 + + .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi, + The worst-case time complexity for generating all maximal + cliques and computational experiments, + Theoretical Computer Science, Volume 363, Issue 1, + Computing and Combinatorics, + 10th Annual International Conference on + Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28-42 + http://dx.doi.org/10.1016/j.tcs.2006.06.015 + + .. [3] F. Cazals, C. Karande, + A note on the problem of reporting maximal cliques, + Theoretical Computer Science, + Volume 407, Issues 1-3, 6 November 2008, Pages 564-568, + http://dx.doi.org/10.1016/j.tcs.2008.05.010 + """ + # Cache nbrs and find first pivot (highest degree) + maxconn=-1 + nnbrs={} + pivotnbrs=set() # handle empty graph + for n,nbrs in G.adjacency_iter(): + nbrs=set(nbrs) + nbrs.discard(n) + conn = len(nbrs) + if conn > maxconn: + nnbrs[n] = pivotnbrs = nbrs + maxconn = conn + else: + nnbrs[n] = nbrs + # Initial setup + cand=set(nnbrs) + smallcand = set(cand - pivotnbrs) + done=set() + stack=[] + clique_so_far=[] + # Start main loop + while smallcand or stack: + try: + # Any nodes left to check? + n=smallcand.pop() + except KeyError: + # back out clique_so_far + cand,done,smallcand = stack.pop() + clique_so_far.pop() + continue + # Add next node to clique + clique_so_far.append(n) + cand.remove(n) + done.add(n) + nn=nnbrs[n] + new_cand = cand & nn + new_done = done & nn + # check if we have more to search + if not new_cand: + if not new_done: + # Found a clique! + yield clique_so_far[:] + clique_so_far.pop() + continue + # Shortcut--only one node left! + if not new_done and len(new_cand)==1: + yield clique_so_far + list(new_cand) + clique_so_far.pop() + continue + # find pivot node (max connected in cand) + # look in done nodes first + numb_cand=len(new_cand) + maxconndone=-1 + for n in new_done: + cn = new_cand & nnbrs[n] + conn=len(cn) + if conn > maxconndone: + pivotdonenbrs=cn + maxconndone=conn + if maxconndone==numb_cand: + break + # Shortcut--this part of tree already searched + if maxconndone == numb_cand: + clique_so_far.pop() + continue + # still finding pivot node + # look in cand nodes second + maxconn=-1 + for n in new_cand: + cn = new_cand & nnbrs[n] + conn=len(cn) + if conn > maxconn: + pivotnbrs=cn + maxconn=conn + if maxconn == numb_cand-1: + break + # pivot node is max connected in cand from done or cand + if maxconndone > maxconn: + pivotnbrs = pivotdonenbrs + # save search status for later backout + stack.append( (cand, done, smallcand) ) + cand=new_cand + done=new_done + smallcand = cand - pivotnbrs + + +def find_cliques_recursive(G): + """Recursive search for all maximal cliques in a graph. + + Maximal cliques are the largest complete subgraph containing + a given point. The largest maximal clique is sometimes called + the maximum clique. + + Returns + ------- + list of lists: list of members in each maximal clique + + See Also + -------- + find_cliques : An nonrecursive version of the same algorithm + + Notes + ----- + Based on the algorithm published by Bron & Kerbosch (1973) [1]_ + as adapated by Tomita, Tanaka and Takahashi (2006) [2]_ + and discussed in Cazals and Karande (2008) [3]_. + + This implementation returns a list of lists each of + which contains the members of a maximal clique. + + This algorithm ignores self-loops and parallel edges as + clique is not conventionally defined with such edges. + + References + ---------- + .. [1] Bron, C. and Kerbosch, J. 1973. + Algorithm 457: finding all cliques of an undirected graph. + Commun. ACM 16, 9 (Sep. 1973), 575-577. + http://portal.acm.org/citation.cfm?doid=362342.362367 + + .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi, + The worst-case time complexity for generating all maximal + cliques and computational experiments, + Theoretical Computer Science, Volume 363, Issue 1, + Computing and Combinatorics, + 10th Annual International Conference on + Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28-42 + http://dx.doi.org/10.1016/j.tcs.2006.06.015 + + .. [3] F. Cazals, C. Karande, + A note on the problem of reporting maximal cliques, + Theoretical Computer Science, + Volume 407, Issues 1-3, 6 November 2008, Pages 564-568, + http://dx.doi.org/10.1016/j.tcs.2008.05.010 + """ + nnbrs={} + for n,nbrs in G.adjacency_iter(): + nbrs=set(nbrs) + nbrs.discard(n) + nnbrs[n]=nbrs + if not nnbrs: return [] # empty graph + cand=set(nnbrs) + done=set() + clique_so_far=[] + cliques=[] + _extend(nnbrs,cand,done,clique_so_far,cliques) + return cliques + +def _extend(nnbrs,cand,done,so_far,cliques): + # find pivot node (max connections in cand) + maxconn=-1 + numb_cand=len(cand) + for n in done: + cn = cand & nnbrs[n] + conn=len(cn) + if conn > maxconn: + pivotnbrs=cn + maxconn=conn + if conn==numb_cand: + # All possible cliques already found + return + for n in cand: + cn = cand & nnbrs[n] + conn=len(cn) + if conn > maxconn: + pivotnbrs=cn + maxconn=conn + # Use pivot to reduce number of nodes to examine + smallercand = set(cand - pivotnbrs) + for n in smallercand: + cand.remove(n) + so_far.append(n) + nn=nnbrs[n] + new_cand=cand & nn + new_done=done & nn + if not new_cand and not new_done: + # Found the clique + cliques.append(so_far[:]) + elif not new_done and len(new_cand) is 1: + # shortcut if only one node left + cliques.append(so_far+list(new_cand)) + else: + _extend(nnbrs, new_cand, new_done, so_far, cliques) + done.add(so_far.pop()) + + +def make_max_clique_graph(G,create_using=None,name=None): + """ Create the maximal clique graph of a graph. + + Finds the maximal cliques and treats these as nodes. + The nodes are connected if they have common members in + the original graph. Theory has done a lot with clique + graphs, but I haven't seen much on maximal clique graphs. + + Notes + ----- + This should be the same as make_clique_bipartite followed + by project_up, but it saves all the intermediate steps. + """ + cliq=list(map(set,find_cliques(G))) + if create_using: + B=create_using + B.clear() + else: + B=networkx.Graph() + if name is not None: + B.name=name + + for i,cl in enumerate(cliq): + B.add_node(i+1) + for j,other_cl in enumerate(cliq[:i]): + # if not cl.isdisjoint(other_cl): #Requires 2.6 + intersect=cl & other_cl + if intersect: # Not empty + B.add_edge(i+1,j+1) + return B + +def make_clique_bipartite(G,fpos=None,create_using=None,name=None): + """Create a bipartite clique graph from a graph G. + + Nodes of G are retained as the "bottom nodes" of B and + cliques of G become "top nodes" of B. + Edges are present if a bottom node belongs to the clique + represented by the top node. + + Returns a Graph with additional attribute dict B.node_type + which is keyed by nodes to "Bottom" or "Top" appropriately. + + if fpos is not None, a second additional attribute dict B.pos + is created to hold the position tuple of each node for viewing + the bipartite graph. + """ + cliq=list(find_cliques(G)) + if create_using: + B=create_using + B.clear() + else: + B=networkx.Graph() + if name is not None: + B.name=name + + B.add_nodes_from(G) + B.node_type={} # New Attribute for B + for n in B: + B.node_type[n]="Bottom" + + if fpos: + B.pos={} # New Attribute for B + delta_cpos=1./len(cliq) + delta_ppos=1./G.order() + cpos=0. + ppos=0. + for i,cl in enumerate(cliq): + name= -i-1 # Top nodes get negative names + B.add_node(name) + B.node_type[name]="Top" + if fpos: + if name not in B.pos: + B.pos[name]=(0.2,cpos) + cpos +=delta_cpos + for v in cl: + B.add_edge(name,v) + if fpos is not None: + if v not in B.pos: + B.pos[v]=(0.8,ppos) + ppos +=delta_ppos + return B + +def project_down(B,create_using=None,name=None): + """Project a bipartite graph B down onto its "bottom nodes". + + The nodes retain their names and are connected if they + share a common top node in the bipartite graph. + + Returns a Graph. + """ + if create_using: + G=create_using + G.clear() + else: + G=networkx.Graph() + if name is not None: + G.name=name + + for v,Bvnbrs in B.adjacency_iter(): + if B.node_type[v]=="Bottom": + G.add_node(v) + for cv in Bvnbrs: + G.add_edges_from([(v,u) for u in B[cv] if u!=v]) + return G + +def project_up(B,create_using=None,name=None): + """Project a bipartite graph B down onto its "bottom nodes". + + The nodes retain their names and are connected if they + share a common Bottom Node in the Bipartite Graph. + + Returns a Graph. + """ + if create_using: + G=create_using + G.clear() + else: + G=networkx.Graph() + if name is not None: + G.name=name + + for v,Bvnbrs in B.adjacency_iter(): + if B.node_type[v]=="Top": + vname= -v #Change sign of name for Top Nodes + G.add_node(vname) + for cv in Bvnbrs: + # Note: -u changes the name (not Top node anymore) + G.add_edges_from([(vname,-u) for u in B[cv] if u!=v]) + return G + +def graph_clique_number(G,cliques=None): + """Return the clique number (size of the largest clique) for G. + + An optional list of cliques can be input if already computed. + """ + if cliques is None: + cliques=find_cliques(G) + return max( [len(c) for c in cliques] ) + + +def graph_number_of_cliques(G,cliques=None): + """Returns the number of maximal cliques in G. + + An optional list of cliques can be input if already computed. + """ + if cliques is None: + cliques=list(find_cliques(G)) + return len(cliques) + + +def node_clique_number(G,nodes=None,cliques=None): + """ Returns the size of the largest maximal clique containing + each given node. + + Returns a single or list depending on input nodes. + Optional list of cliques can be input if already computed. + """ + if cliques is None: + if nodes is not None: + # Use ego_graph to decrease size of graph + if isinstance(nodes,list): + d={} + for n in nodes: + H=networkx.ego_graph(G,n) + d[n]=max( (len(c) for c in find_cliques(H)) ) + else: + H=networkx.ego_graph(G,nodes) + d=max( (len(c) for c in find_cliques(H)) ) + return d + # nodes is None--find all cliques + cliques=list(find_cliques(G)) + + if nodes is None: + nodes=G.nodes() # none, get entire graph + + if not isinstance(nodes, list): # check for a list + v=nodes + # assume it is a single value + d=max([len(c) for c in cliques if v in c]) + else: + d={} + for v in nodes: + d[v]=max([len(c) for c in cliques if v in c]) + return d + + # if nodes is None: # none, use entire graph + # nodes=G.nodes() + # elif not isinstance(nodes, list): # check for a list + # nodes=[nodes] # assume it is a single value + + # if cliques is None: + # cliques=list(find_cliques(G)) + # d={} + # for v in nodes: + # d[v]=max([len(c) for c in cliques if v in c]) + + # if nodes in G: + # return d[v] #return single value + # return d + + +def number_of_cliques(G,nodes=None,cliques=None): + """Returns the number of maximal cliques for each node. + + Returns a single or list depending on input nodes. + Optional list of cliques can be input if already computed. + """ + if cliques is None: + cliques=list(find_cliques(G)) + + if nodes is None: + nodes=G.nodes() # none, get entire graph + + if not isinstance(nodes, list): # check for a list + v=nodes + # assume it is a single value + numcliq=len([1 for c in cliques if v in c]) + else: + numcliq={} + for v in nodes: + numcliq[v]=len([1 for c in cliques if v in c]) + return numcliq + + +def cliques_containing_node(G,nodes=None,cliques=None): + """Returns a list of cliques containing the given node. + + Returns a single list or list of lists depending on input nodes. + Optional list of cliques can be input if already computed. + """ + if cliques is None: + cliques=list(find_cliques(G)) + + if nodes is None: + nodes=G.nodes() # none, get entire graph + + if not isinstance(nodes, list): # check for a list + v=nodes + # assume it is a single value + vcliques=[c for c in cliques if v in c] + else: + vcliques={} + for v in nodes: + vcliques[v]=[c for c in cliques if v in c] + return vcliques diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cluster.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cluster.py new file mode 100644 index 0000000..a442431 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cluster.py @@ -0,0 +1,363 @@ +# -*- coding: utf-8 -*- +"""Algorithms to characterize the number of triangles in a graph.""" +from itertools import combinations +import networkx as nx +from networkx import NetworkXError +__author__ = """\n""".join(['Aric Hagberg ', + 'Dan Schult (dschult@colgate.edu)', + 'Pieter Swart (swart@lanl.gov)', + 'Jordi Torrents ']) +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__all__= ['triangles', 'average_clustering', 'clustering', 'transitivity', + 'square_clustering'] + +def triangles(G, nodes=None): + """Compute the number of triangles. + + Finds the number of triangles that include a node as one vertex. + + Parameters + ---------- + G : graph + A networkx graph + nodes : container of nodes, optional (default= all nodes in G) + Compute triangles for nodes in this container. + + Returns + ------- + out : dictionary + Number of triangles keyed by node label. + + Examples + -------- + >>> G=nx.complete_graph(5) + >>> print(nx.triangles(G,0)) + 6 + >>> print(nx.triangles(G)) + {0: 6, 1: 6, 2: 6, 3: 6, 4: 6} + >>> print(list(nx.triangles(G,(0,1)).values())) + [6, 6] + + Notes + ----- + When computing triangles for the entire graph each triangle is counted + three times, once at each node. Self loops are ignored. + + """ + if G.is_directed(): + raise NetworkXError("triangles() is not defined for directed graphs.") + if nodes in G: + # return single value + return next(_triangles_and_degree_iter(G,nodes))[2] // 2 + return dict( (v,t // 2) for v,d,t in _triangles_and_degree_iter(G,nodes)) + +def _triangles_and_degree_iter(G,nodes=None): + """ Return an iterator of (node, degree, triangles). + + This double counts triangles so you may want to divide by 2. + See degree() and triangles() for definitions and details. + + """ + if G.is_multigraph(): + raise NetworkXError("Not defined for multigraphs.") + + if nodes is None: + nodes_nbrs = G.adj.items() + else: + nodes_nbrs= ( (n,G[n]) for n in G.nbunch_iter(nodes) ) + + for v,v_nbrs in nodes_nbrs: + vs=set(v_nbrs)-set([v]) + ntriangles=0 + for w in vs: + ws=set(G[w])-set([w]) + ntriangles+=len(vs.intersection(ws)) + yield (v,len(vs),ntriangles) + + +def _weighted_triangles_and_degree_iter(G, nodes=None, weight='weight'): + """ Return an iterator of (node, degree, weighted_triangles). + + Used for weighted clustering. + + """ + if G.is_multigraph(): + raise NetworkXError("Not defined for multigraphs.") + + if weight is None or G.edges()==[]: + max_weight=1.0 + else: + max_weight=float(max(d.get(weight,1.0) + for u,v,d in G.edges(data=True))) + if nodes is None: + nodes_nbrs = G.adj.items() + else: + nodes_nbrs= ( (n,G[n]) for n in G.nbunch_iter(nodes) ) + + for i,nbrs in nodes_nbrs: + inbrs=set(nbrs)-set([i]) + weighted_triangles=0.0 + seen=set() + for j in inbrs: + wij=G[i][j].get(weight,1.0)/max_weight + seen.add(j) + jnbrs=set(G[j])-seen # this keeps from double counting + for k in inbrs&jnbrs: + wjk=G[j][k].get(weight,1.0)/max_weight + wki=G[i][k].get(weight,1.0)/max_weight + weighted_triangles+=(wij*wjk*wki)**(1.0/3.0) + yield (i,len(inbrs),weighted_triangles*2) + + +def average_clustering(G, nodes=None, weight=None, count_zeros=True): + r"""Compute the average clustering coefficient for the graph G. + + The clustering coefficient for the graph is the average, + + .. math:: + + C = \frac{1}{n}\sum_{v \in G} c_v, + + where `n` is the number of nodes in `G`. + + Parameters + ---------- + G : graph + + nodes : container of nodes, optional (default=all nodes in G) + Compute average clustering for nodes in this container. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used as a weight. + If None, then each edge has weight 1. + + count_zeros : bool (default=False) + If False include only the nodes with nonzero clustering in the average. + + Returns + ------- + avg : float + Average clustering + + Examples + -------- + >>> G=nx.complete_graph(5) + >>> print(nx.average_clustering(G)) + 1.0 + + Notes + ----- + This is a space saving routine; it might be faster + to use the clustering function to get a list and then take the average. + + Self loops are ignored. + + References + ---------- + .. [1] Generalizations of the clustering coefficient to weighted + complex networks by J. Saramäki, M. Kivelä, J.-P. Onnela, + K. Kaski, and J. Kertész, Physical Review E, 75 027105 (2007). + http://jponnela.com/web_documents/a9.pdf + .. [2] Marcus Kaiser, Mean clustering coefficients: the role of isolated + nodes and leafs on clustering measures for small-world networks. + http://arxiv.org/abs/0802.2512 + """ + c=clustering(G,nodes,weight=weight).values() + if not count_zeros: + c = [v for v in c if v > 0] + return sum(c)/float(len(c)) + +def clustering(G, nodes=None, weight=None): + r"""Compute the clustering coefficient for nodes. + + For unweighted graphs, the clustering of a node `u` + is the fraction of possible triangles through that node that exist, + + .. math:: + + c_u = \frac{2 T(u)}{deg(u)(deg(u)-1)}, + + where `T(u)` is the number of triangles through node `u` and + `deg(u)` is the degree of `u`. + + For weighted graphs, the clustering is defined + as the geometric average of the subgraph edge weights [1]_, + + .. math:: + + c_u = \frac{1}{deg(u)(deg(u)-1))} + \sum_{uv} (\hat{w}_{uv} \hat{w}_{uw} \hat{w}_{vw})^{1/3}. + + The edge weights `\hat{w}_{uv}` are normalized by the maximum weight in the + network `\hat{w}_{uv} = w_{uv}/\max(w)`. + + The value of `c_u` is assigned to 0 if `deg(u) < 2`. + + Parameters + ---------- + G : graph + + nodes : container of nodes, optional (default=all nodes in G) + Compute clustering for nodes in this container. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used as a weight. + If None, then each edge has weight 1. + + Returns + ------- + out : float, or dictionary + Clustering coefficient at specified nodes + + Examples + -------- + >>> G=nx.complete_graph(5) + >>> print(nx.clustering(G,0)) + 1.0 + >>> print(nx.clustering(G)) + {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} + + Notes + ----- + Self loops are ignored. + + References + ---------- + .. [1] Generalizations of the clustering coefficient to weighted + complex networks by J. Saramäki, M. Kivelä, J.-P. Onnela, + K. Kaski, and J. Kertész, Physical Review E, 75 027105 (2007). + http://jponnela.com/web_documents/a9.pdf + """ + if G.is_directed(): + raise NetworkXError('Clustering algorithms are not defined ', + 'for directed graphs.') + if weight is not None: + td_iter=_weighted_triangles_and_degree_iter(G,nodes,weight) + else: + td_iter=_triangles_and_degree_iter(G,nodes) + + clusterc={} + + for v,d,t in td_iter: + if t==0: + clusterc[v]=0.0 + else: + clusterc[v]=t/float(d*(d-1)) + + if nodes in G: + return list(clusterc.values())[0] # return single value + return clusterc + +def transitivity(G): + r"""Compute graph transitivity, the fraction of all possible triangles + present in G. + + Possible triangles are identified by the number of "triads" + (two edges with a shared vertex). + + The transitivity is + + .. math:: + + T = 3\frac{\#triangles}{\#triads}. + + Parameters + ---------- + G : graph + + Returns + ------- + out : float + Transitivity + + Examples + -------- + >>> G = nx.complete_graph(5) + >>> print(nx.transitivity(G)) + 1.0 + """ + triangles=0 # 6 times number of triangles + contri=0 # 2 times number of connected triples + for v,d,t in _triangles_and_degree_iter(G): + contri += d*(d-1) + triangles += t + if triangles==0: # we had no triangles or possible triangles + return 0.0 + else: + return triangles/float(contri) + +def square_clustering(G, nodes=None): + r""" Compute the squares clustering coefficient for nodes. + + For each node return the fraction of possible squares that exist at + the node [1]_ + + .. math:: + C_4(v) = \frac{ \sum_{u=1}^{k_v} + \sum_{w=u+1}^{k_v} q_v(u,w) }{ \sum_{u=1}^{k_v} + \sum_{w=u+1}^{k_v} [a_v(u,w) + q_v(u,w)]}, + + where `q_v(u,w)` are the number of common neighbors of `u` and `w` + other than `v` (ie squares), and + `a_v(u,w) = (k_u - (1+q_v(u,w)+\theta_{uv}))(k_w - (1+q_v(u,w)+\theta_{uw}))`, + where `\theta_{uw} = 1` if `u` and `w` are connected and 0 otherwise. + + Parameters + ---------- + G : graph + + nodes : container of nodes, optional (default=all nodes in G) + Compute clustering for nodes in this container. + + Returns + ------- + c4 : dictionary + A dictionary keyed by node with the square clustering coefficient value. + + Examples + -------- + >>> G=nx.complete_graph(5) + >>> print(nx.square_clustering(G,0)) + 1.0 + >>> print(nx.square_clustering(G)) + {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} + + Notes + ----- + While `C_3(v)` (triangle clustering) gives the probability that + two neighbors of node v are connected with each other, `C_4(v)` is + the probability that two neighbors of node v share a common + neighbor different from v. This algorithm can be applied to both + bipartite and unipartite networks. + + References + ---------- + .. [1] Pedro G. Lind, Marta C. González, and Hans J. Herrmann. 2005 + Cycles and clustering in bipartite networks. + Physical Review E (72) 056127. + """ + if nodes is None: + node_iter = G + else: + node_iter = G.nbunch_iter(nodes) + clustering = {} + for v in node_iter: + clustering[v] = 0.0 + potential=0 + for u,w in combinations(G[v], 2): + squares = len((set(G[u]) & set(G[w])) - set([v])) + clustering[v] += squares + degm = squares + 1.0 + if w in G[u]: + degm += 1 + potential += (len(G[u]) - degm) * (len(G[w]) - degm) + squares + if potential > 0: + clustering[v] /= potential + if nodes in G: + return list(clustering.values())[0] # return single value + return clustering diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/__init__.py new file mode 100644 index 0000000..c3c2285 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/__init__.py @@ -0,0 +1 @@ +from networkx.algorithms.community.kclique import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/kclique.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/kclique.py new file mode 100644 index 0000000..dc95b58 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/kclique.py @@ -0,0 +1,82 @@ +#-*- coding: utf-8 -*- +# Copyright (C) 2011 by +# Conrad Lee +# Aric Hagberg +# All rights reserved. +# BSD license. +from collections import defaultdict +import networkx as nx +__author__ = """\n""".join(['Conrad Lee ', + 'Aric Hagberg ']) +__all__ = ['k_clique_communities'] + +def k_clique_communities(G, k, cliques=None): + """Find k-clique communities in graph using the percolation method. + + A k-clique community is the union of all cliques of size k that + can be reached through adjacent (sharing k-1 nodes) k-cliques. + + Parameters + ---------- + G : NetworkX graph + + k : int + Size of smallest clique + + cliques: list or generator + Precomputed cliques (use networkx.find_cliques(G)) + + Returns + ------- + Yields sets of nodes, one for each k-clique community. + + Examples + -------- + >>> G = nx.complete_graph(5) + >>> K5 = nx.convert_node_labels_to_integers(G,first_label=2) + >>> G.add_edges_from(K5.edges()) + >>> c = list(nx.k_clique_communities(G, 4)) + >>> list(c[0]) + [0, 1, 2, 3, 4, 5, 6] + >>> list(nx.k_clique_communities(G, 6)) + [] + + References + ---------- + .. [1] Gergely Palla, Imre Derényi, Illés Farkas1, and Tamás Vicsek, + Uncovering the overlapping community structure of complex networks + in nature and society Nature 435, 814-818, 2005, + doi:10.1038/nature03607 + """ + if k < 2: + raise nx.NetworkXError("k=%d, k must be greater than 1."%k) + if cliques is None: + cliques = nx.find_cliques(G) + cliques = [frozenset(c) for c in cliques if len(c) >= k] + + # First index which nodes are in which cliques + membership_dict = defaultdict(list) + for clique in cliques: + for node in clique: + membership_dict[node].append(clique) + + # For each clique, see which adjacent cliques percolate + perc_graph = nx.Graph() + perc_graph.add_nodes_from(cliques) + for clique in cliques: + for adj_clique in _get_adjacent_cliques(clique, membership_dict): + if len(clique.intersection(adj_clique)) >= (k - 1): + perc_graph.add_edge(clique, adj_clique) + + # Connected components of clique graph with perc edges + # are the percolated cliques + for component in nx.connected_components(perc_graph): + yield(frozenset.union(*component)) + +def _get_adjacent_cliques(clique, membership_dict): + adjacent_cliques = set() + for n in clique: + for adj_clique in membership_dict[n]: + if clique != adj_clique: + adjacent_cliques.add(adj_clique) + return adjacent_cliques diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/tests/test_kclique.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/tests/test_kclique.py new file mode 100644 index 0000000..8debca6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/community/tests/test_kclique.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from itertools import combinations +from networkx import k_clique_communities + +def test_overlaping_K5(): + G = nx.Graph() + G.add_edges_from(combinations(range(5), 2)) # Add a five clique + G.add_edges_from(combinations(range(2,7), 2)) # Add another five clique + c = list(nx.k_clique_communities(G, 4)) + assert_equal(c,[frozenset([0, 1, 2, 3, 4, 5, 6])]) + c= list(nx.k_clique_communities(G, 5)) + assert_equal(set(c),set([frozenset([0,1,2,3,4]),frozenset([2,3,4,5,6])])) + +def test_isolated_K5(): + G = nx.Graph() + G.add_edges_from(combinations(range(0,5), 2)) # Add a five clique + G.add_edges_from(combinations(range(5,10), 2)) # Add another five clique + c= list(nx.k_clique_communities(G, 5)) + assert_equal(set(c),set([frozenset([0,1,2,3,4]),frozenset([5,6,7,8,9])])) + +def test_zachary(): + z = nx.karate_club_graph() + # clique percolation with k=2 is just connected components + zachary_k2_ground_truth = set([frozenset(z.nodes())]) + zachary_k3_ground_truth = set([frozenset([0, 1, 2, 3, 7, 8, 12, 13, 14, + 15, 17, 18, 19, 20, 21, 22, 23, + 26, 27, 28, 29, 30, 31, 32, 33]), + frozenset([0, 4, 5, 6, 10, 16]), + frozenset([24, 25, 31])]) + zachary_k4_ground_truth = set([frozenset([0, 1, 2, 3, 7, 13]), + frozenset([8, 32, 30, 33]), + frozenset([32, 33, 29, 23])]) + zachary_k5_ground_truth = set([frozenset([0, 1, 2, 3, 7, 13])]) + zachary_k6_ground_truth = set([]) + + assert set(k_clique_communities(z, 2)) == zachary_k2_ground_truth + assert set(k_clique_communities(z, 3)) == zachary_k3_ground_truth + assert set(k_clique_communities(z, 4)) == zachary_k4_ground_truth + assert set(k_clique_communities(z, 5)) == zachary_k5_ground_truth + assert set(k_clique_communities(z, 6)) == zachary_k6_ground_truth + +@raises(nx.NetworkXError) +def test_bad_k(): + c = list(k_clique_communities(nx.Graph(),1)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/__init__.py new file mode 100644 index 0000000..36c9391 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/__init__.py @@ -0,0 +1,5 @@ +from networkx.algorithms.components.connected import * +from networkx.algorithms.components.strongly_connected import * +from networkx.algorithms.components.weakly_connected import * +from networkx.algorithms.components.attracting import * +from networkx.algorithms.components.biconnected import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/attracting.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/attracting.py new file mode 100644 index 0000000..d0e75c2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/attracting.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" +Attracting components. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__authors__ = "\n".join(['Christopher Ellison']) +__all__ = ['number_attracting_components', + 'attracting_components', + 'is_attracting_component', + 'attracting_component_subgraphs', + ] + +def attracting_components(G): + """Returns a list of attracting components in `G`. + + An attracting component in a directed graph `G` is a strongly connected + component with the property that a random walker on the graph will never + leave the component, once it enters the component. + + The nodes in attracting components can also be thought of as recurrent + nodes. If a random walker enters the attractor containing the node, then + the node will be visited infinitely often. + + Parameters + ---------- + G : DiGraph, MultiDiGraph + The graph to be analyzed. + + Returns + ------- + attractors : list + The list of attracting components, sorted from largest attracting + component to smallest attracting component. + + See Also + -------- + number_attracting_components + is_attracting_component + attracting_component_subgraphs + + """ + scc = nx.strongly_connected_components(G) + cG = nx.condensation(G, scc) + attractors = [scc[n] for n in cG if cG.out_degree(n) == 0] + attractors.sort(key=len,reverse=True) + return attractors + + +def number_attracting_components(G): + """Returns the number of attracting components in `G`. + + Parameters + ---------- + G : DiGraph, MultiDiGraph + The graph to be analyzed. + + Returns + ------- + n : int + The number of attracting components in G. + + See Also + -------- + attracting_components + is_attracting_component + attracting_component_subgraphs + + """ + n = len(attracting_components(G)) + return n + + +def is_attracting_component(G): + """Returns True if `G` consists of a single attracting component. + + Parameters + ---------- + G : DiGraph, MultiDiGraph + The graph to be analyzed. + + Returns + ------- + attracting : bool + True if `G` has a single attracting component. Otherwise, False. + + See Also + -------- + attracting_components + number_attracting_components + attracting_component_subgraphs + + """ + ac = attracting_components(G) + if len(ac[0]) == len(G): + attracting = True + else: + attracting = False + return attracting + + +def attracting_component_subgraphs(G): + """Returns a list of attracting component subgraphs from `G`. + + Parameters + ---------- + G : DiGraph, MultiDiGraph + The graph to be analyzed. + + Returns + ------- + subgraphs : list + A list of node-induced subgraphs of the attracting components of `G`. + + Notes + ----- + Graph, node, and edge attributes are copied to the subgraphs. + + See Also + -------- + attracting_components + number_attracting_components + is_attracting_component + + """ + subgraphs = [G.subgraph(ac).copy() for ac in attracting_components(G)] + return subgraphs + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/biconnected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/biconnected.py new file mode 100644 index 0000000..0185c2c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/biconnected.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +""" +Biconnected components and articulation points. +""" +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from itertools import chain +import networkx as nx +__author__ = '\n'.join(['Jordi Torrents ', + 'Dan Schult ', + 'Aric Hagberg ']) +__all__ = ['biconnected_components', + 'biconnected_component_edges', + 'biconnected_component_subgraphs', + 'is_biconnected', + 'articulation_points', + ] + +def is_biconnected(G): + """Return True if the graph is biconnected, False otherwise. + + A graph is biconnected if, and only if, it cannot be disconnected by + removing only one node (and all edges incident on that node). If + removing a node increases the number of disconnected components + in the graph, that node is called an articulation point, or cut + vertex. A biconnected graph has no articulation points. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + biconnected : bool + True if the graph is biconnected, False otherwise. + + Raises + ------ + NetworkXError : + If the input graph is not undirected. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> print(nx.is_biconnected(G)) + False + >>> G.add_edge(0,3) + >>> print(nx.is_biconnected(G)) + True + + See Also + -------- + biconnected_components, + articulation_points, + biconnected_component_edges, + biconnected_component_subgraphs + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + """ + bcc = list(biconnected_components(G)) + if not bcc: # No bicomponents (it could be an empty graph) + return False + return len(bcc[0]) == len(G) + +def biconnected_component_edges(G): + """Return a generator of lists of edges, one list for each biconnected + component of the input graph. + + Biconnected components are maximal subgraphs such that the removal of a + node (and all edges incident on that node) will not disconnect the + subgraph. Note that nodes may be part of more than one biconnected + component. Those nodes are articulation points, or cut vertices. However, + each edge belongs to one, and only one, biconnected component. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + edges : generator + Generator of lists of edges, one list for each bicomponent. + + Raises + ------ + NetworkXError : + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.barbell_graph(4,2) + >>> print(nx.is_biconnected(G)) + False + >>> components = nx.biconnected_component_edges(G) + >>> G.add_edge(2,8) + >>> print(nx.is_biconnected(G)) + True + >>> components = nx.biconnected_component_edges(G) + + See Also + -------- + is_biconnected, + biconnected_components, + articulation_points, + biconnected_component_subgraphs + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + """ + return sorted(_biconnected_dfs(G,components=True), key=len, reverse=True) + +def biconnected_components(G): + """Return a generator of sets of nodes, one set for each biconnected + component of the graph + + Biconnected components are maximal subgraphs such that the removal of a + node (and all edges incident on that node) will not disconnect the + subgraph. Note that nodes may be part of more than one biconnected + component. Those nodes are articulation points, or cut vertices. The + removal of articulation points will increase the number of connected + components of the graph. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + nodes : generator + Generator of sets of nodes, one set for each biconnected component. + + Raises + ------ + NetworkXError : + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.barbell_graph(4,2) + >>> print(nx.is_biconnected(G)) + False + >>> components = nx.biconnected_components(G) + >>> G.add_edge(2,8) + >>> print(nx.is_biconnected(G)) + True + >>> components = nx.biconnected_components(G) + + See Also + -------- + is_biconnected, + articulation_points, + biconnected_component_edges, + biconnected_component_subgraphs + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + """ + bicomponents = (set(chain.from_iterable(comp)) + for comp in _biconnected_dfs(G,components=True)) + return sorted(bicomponents, key=len, reverse=True) + +def biconnected_component_subgraphs(G): + """Return a generator of graphs, one graph for each biconnected component + of the input graph. + + Biconnected components are maximal subgraphs such that the removal of a + node (and all edges incident on that node) will not disconnect the + subgraph. Note that nodes may be part of more than one biconnected + component. Those nodes are articulation points, or cut vertices. The + removal of articulation points will increase the number of connected + components of the graph. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + graphs : generator + Generator of graphs, one graph for each biconnected component. + + Raises + ------ + NetworkXError : + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.barbell_graph(4,2) + >>> print(nx.is_biconnected(G)) + False + >>> subgraphs = nx.biconnected_component_subgraphs(G) + + See Also + -------- + is_biconnected, + articulation_points, + biconnected_component_edges, + biconnected_components + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + Graph, node, and edge attributes are copied to the subgraphs. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + """ + def edge_subgraph(G,edges): + # create new graph and copy subgraph into it + H = G.__class__() + for u,v in edges: + H.add_edge(u,v,attr_dict=G[u][v]) + for n in H: + H.node[n]=G.node[n].copy() + H.graph=G.graph.copy() + return H + return (edge_subgraph(G,edges) for edges in + sorted(_biconnected_dfs(G,components=True), key=len, reverse=True)) + +def articulation_points(G): + """Return a generator of articulation points, or cut vertices, of a graph. + + An articulation point or cut vertex is any node whose removal (along with + all its incident edges) increases the number of connected components of + a graph. An undirected connected graph without articulation points is + biconnected. Articulation points belong to more than one biconnected + component of a graph. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + articulation points : generator + generator of nodes + + Raises + ------ + NetworkXError : + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.barbell_graph(4,2) + >>> print(nx.is_biconnected(G)) + False + >>> list(nx.articulation_points(G)) + [6, 5, 4, 3] + >>> G.add_edge(2,8) + >>> print(nx.is_biconnected(G)) + True + >>> list(nx.articulation_points(G)) + [] + + See Also + -------- + is_biconnected, + biconnected_components, + biconnected_component_edges, + biconnected_component_subgraphs + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + """ + return _biconnected_dfs(G,components=False) + +def _biconnected_dfs(G, components=True): + # depth-first search algorithm to generate articulation points + # and biconnected components + if G.is_directed(): + raise nx.NetworkXError('Not allowed for directed graph G. ' + 'Use UG=G.to_undirected() to create an ' + 'undirected graph.') + visited = set() + for start in G: + if start in visited: + continue + discovery = {start:0} # "time" of first discovery of node during search + low = {start:0} + root_children = 0 + visited.add(start) + edge_stack = [] + stack = [(start, start, iter(G[start]))] + while stack: + grandparent, parent, children = stack[-1] + try: + child = next(children) + if grandparent == child: + continue + if child in visited: + if discovery[child] <= discovery[parent]: # back edge + low[parent] = min(low[parent],discovery[child]) + if components: + edge_stack.append((parent,child)) + else: + low[child] = discovery[child] = len(discovery) + visited.add(child) + stack.append((parent, child, iter(G[child]))) + if components: + edge_stack.append((parent,child)) + except StopIteration: + stack.pop() + if len(stack) > 1: + if low[parent] >= discovery[grandparent]: + if components: + ind = edge_stack.index((grandparent,parent)) + yield edge_stack[ind:] + edge_stack=edge_stack[:ind] + else: + yield grandparent + low[grandparent] = min(low[parent], low[grandparent]) + elif stack: # length 1 so grandparent is root + root_children += 1 + if components: + ind = edge_stack.index((grandparent,parent)) + yield edge_stack[ind:] + if not components: + # root node is articulation point if it has more than 1 child + if root_children > 1: + yield start diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/connected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/connected.py new file mode 100644 index 0000000..088a99e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/connected.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +""" +Connected components. +""" +__authors__ = "\n".join(['Eben Kenah', + 'Aric Hagberg (hagberg@lanl.gov)' + 'Christopher Ellison']) +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['number_connected_components', + 'connected_components', + 'connected_component_subgraphs', + 'is_connected', + 'node_connected_component', + ] + +import networkx as nx + +def connected_components(G): + """Return nodes in connected components of graph. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + comp : list of lists + A list of nodes for each component of G. + + See Also + -------- + strongly_connected_components + + Notes + ----- + The list is ordered from largest connected component to smallest. + For undirected graphs only. + """ + if G.is_directed(): + raise nx.NetworkXError("""Not allowed for directed graph G. + Use UG=G.to_undirected() to create an undirected graph.""") + seen={} + components=[] + for v in G: + if v not in seen: + c=nx.single_source_shortest_path_length(G,v) + components.append(list(c.keys())) + seen.update(c) + components.sort(key=len,reverse=True) + return components + + +def number_connected_components(G): + """Return number of connected components in graph. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + n : integer + Number of connected components + + See Also + -------- + connected_components + + Notes + ----- + For undirected graphs only. + """ + return len(connected_components(G)) + + +def is_connected(G): + """Test graph connectivity. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + connected : bool + True if the graph is connected, false otherwise. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> print(nx.is_connected(G)) + True + + See Also + -------- + connected_components + + Notes + ----- + For undirected graphs only. + """ + if G.is_directed(): + raise nx.NetworkXError(\ + """Not allowed for directed graph G. +Use UG=G.to_undirected() to create an undirected graph.""") + + if len(G)==0: + raise nx.NetworkXPointlessConcept( + """Connectivity is undefined for the null graph.""") + + return len(nx.single_source_shortest_path_length(G, + next(G.nodes_iter())))==len(G) + + +def connected_component_subgraphs(G): + """Return connected components as subgraphs. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + glist : list + A list of graphs, one for each connected component of G. + + Examples + -------- + Get largest connected component as subgraph + + >>> G=nx.path_graph(4) + >>> G.add_edge(5,6) + >>> H=nx.connected_component_subgraphs(G)[0] + + See Also + -------- + connected_components + + Notes + ----- + The list is ordered from largest connected component to smallest. + For undirected graphs only. + + Graph, node, and edge attributes are copied to the subgraphs. + """ + cc=connected_components(G) + graph_list=[] + for c in cc: + graph_list.append(G.subgraph(c).copy()) + return graph_list + + +def node_connected_component(G,n): + """Return nodes in connected components of graph containing node n. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + n : node label + A node in G + + Returns + ------- + comp : lists + A list of nodes in component of G containing node n. + + See Also + -------- + connected_components + + Notes + ----- + For undirected graphs only. + """ + if G.is_directed(): + raise nx.NetworkXError("""Not allowed for directed graph G. + Use UG=G.to_undirected() to create an undirected graph.""") + return list(nx.single_source_shortest_path_length(G,n).keys()) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/strongly_connected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/strongly_connected.py new file mode 100644 index 0000000..fbdec13 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/strongly_connected.py @@ -0,0 +1,359 @@ +# -*- coding: utf-8 -*- +""" +Strongly connected components. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__authors__ = "\n".join(['Eben Kenah', + 'Aric Hagberg (hagberg@lanl.gov)' + 'Christopher Ellison', + 'Ben Edwards (bedwards@cs.unm.edu)']) + +__all__ = ['number_strongly_connected_components', + 'strongly_connected_components', + 'strongly_connected_component_subgraphs', + 'is_strongly_connected', + 'strongly_connected_components_recursive', + 'kosaraju_strongly_connected_components', + 'condensation'] + +def strongly_connected_components(G): + """Return nodes in strongly connected components of graph. + + Parameters + ---------- + G : NetworkX Graph + An directed graph. + + Returns + ------- + comp : list of lists + A list of nodes for each component of G. + The list is ordered from largest connected component to smallest. + + Raises + ------ + NetworkXError: If G is undirected. + + See Also + -------- + connected_components, weakly_connected_components + + Notes + ----- + Uses Tarjan's algorithm with Nuutila's modifications. + Nonrecursive version of algorithm. + + References + ---------- + .. [1] Depth-first search and linear graph algorithms, R. Tarjan + SIAM Journal of Computing 1(2):146-160, (1972). + + .. [2] On finding the strongly connected components in a directed graph. + E. Nuutila and E. Soisalon-Soinen + Information Processing Letters 49(1): 9-14, (1994).. + """ + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + Use connected_components() """) + preorder={} + lowlink={} + scc_found={} + scc_queue = [] + scc_list=[] + i=0 # Preorder counter + for source in G: + if source not in scc_found: + queue=[source] + while queue: + v=queue[-1] + if v not in preorder: + i=i+1 + preorder[v]=i + done=1 + v_nbrs=G[v] + for w in v_nbrs: + if w not in preorder: + queue.append(w) + done=0 + break + if done==1: + lowlink[v]=preorder[v] + for w in v_nbrs: + if w not in scc_found: + if preorder[w]>preorder[v]: + lowlink[v]=min([lowlink[v],lowlink[w]]) + else: + lowlink[v]=min([lowlink[v],preorder[w]]) + queue.pop() + if lowlink[v]==preorder[v]: + scc_found[v]=True + scc=[v] + while scc_queue and preorder[scc_queue[-1]]>preorder[v]: + k=scc_queue.pop() + scc_found[k]=True + scc.append(k) + scc_list.append(scc) + else: + scc_queue.append(v) + scc_list.sort(key=len,reverse=True) + return scc_list + + +def kosaraju_strongly_connected_components(G,source=None): + """Return nodes in strongly connected components of graph. + + Parameters + ---------- + G : NetworkX Graph + An directed graph. + + Returns + ------- + comp : list of lists + A list of nodes for each component of G. + The list is ordered from largest connected component to smallest. + + Raises + ------ + NetworkXError: If G is undirected + + See Also + -------- + connected_components + + Notes + ----- + Uses Kosaraju's algorithm. + """ + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + Use connected_components() """) + components=[] + G=G.reverse(copy=False) + post=list(nx.dfs_postorder_nodes(G,source=source)) + G=G.reverse(copy=False) + seen={} + while post: + r=post.pop() + if r in seen: + continue + c=nx.dfs_preorder_nodes(G,r) + new=[v for v in c if v not in seen] + seen.update([(u,True) for u in new]) + components.append(new) + components.sort(key=len,reverse=True) + return components + + +def strongly_connected_components_recursive(G): + """Return nodes in strongly connected components of graph. + + Recursive version of algorithm. + + Parameters + ---------- + G : NetworkX Graph + An directed graph. + + Returns + ------- + comp : list of lists + A list of nodes for each component of G. + The list is ordered from largest connected component to smallest. + + Raises + ------ + NetworkXError : If G is undirected + + See Also + -------- + connected_components + + Notes + ----- + Uses Tarjan's algorithm with Nuutila's modifications. + + References + ---------- + .. [1] Depth-first search and linear graph algorithms, R. Tarjan + SIAM Journal of Computing 1(2):146-160, (1972). + + .. [2] On finding the strongly connected components in a directed graph. + E. Nuutila and E. Soisalon-Soinen + Information Processing Letters 49(1): 9-14, (1994).. + """ + def visit(v,cnt): + root[v]=cnt + visited[v]=cnt + cnt+=1 + stack.append(v) + for w in G[v]: + if w not in visited: visit(w,cnt) + if w not in component: + root[v]=min(root[v],root[w]) + if root[v]==visited[v]: + component[v]=root[v] + tmpc=[v] # hold nodes in this component + while stack[-1]!=v: + w=stack.pop() + component[w]=root[v] + tmpc.append(w) + stack.remove(v) + scc.append(tmpc) # add to scc list + + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + Use connected_components() """) + + scc=[] + visited={} + component={} + root={} + cnt=0 + stack=[] + for source in G: + if source not in visited: + visit(source,cnt) + + scc.sort(key=len,reverse=True) + return scc + + +def strongly_connected_component_subgraphs(G): + """Return strongly connected components as subgraphs. + + Parameters + ---------- + G : NetworkX Graph + A graph. + + Returns + ------- + glist : list + A list of graphs, one for each strongly connected component of G. + + See Also + -------- + connected_component_subgraphs + + Notes + ----- + The list is ordered from largest strongly connected component to smallest. + + Graph, node, and edge attributes are copied to the subgraphs. + """ + cc=strongly_connected_components(G) + graph_list=[] + for c in cc: + graph_list.append(G.subgraph(c).copy()) + return graph_list + + +def number_strongly_connected_components(G): + """Return number of strongly connected components in graph. + + Parameters + ---------- + G : NetworkX graph + A directed graph. + + Returns + ------- + n : integer + Number of strongly connected components + + See Also + -------- + connected_components + + Notes + ----- + For directed graphs only. + """ + return len(strongly_connected_components(G)) + + +def is_strongly_connected(G): + """Test directed graph for strong connectivity. + + Parameters + ---------- + G : NetworkX Graph + A directed graph. + + Returns + ------- + connected : bool + True if the graph is strongly connected, False otherwise. + + See Also + -------- + strongly_connected_components + + Notes + ----- + For directed graphs only. + """ + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + See is_connected() for connectivity test.""") + + if len(G)==0: + raise nx.NetworkXPointlessConcept( + """Connectivity is undefined for the null graph.""") + + return len(strongly_connected_components(G)[0])==len(G) + +def condensation(G, scc=None): + """Returns the condensation of G. + + The condensation of G is the graph with each of the strongly connected + components contracted into a single node. + + Parameters + ---------- + G : NetworkX DiGraph + A directed graph. + + scc: list (optional, default=None) + A list of strongly connected components. If provided, the elements in + `scc` must partition the nodes in `G`. If not provided, it will be + calculated as scc=nx.strongly_connected_components(G). + + Returns + ------- + C : NetworkX DiGraph + The condensation of G. The node labels are integers corresponding + to the index of the component in the list of strongly connected + components. + + Raises + ------ + NetworkXError: If G is not directed + + Notes + ----- + After contracting all strongly connected components to a single node, + the resulting graph is a directed acyclic graph. + """ + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + See is_connected() for connectivity test.""") + if scc is None: + scc = nx.strongly_connected_components(G) + mapping = {} + C = nx.DiGraph() + for i,component in enumerate(scc): + for n in component: + mapping[n] = i + C.add_nodes_from(range(len(scc))) + for u,v in G.edges(): + if mapping[u] != mapping[v]: + C.add_edge(mapping[u],mapping[v]) + return C diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_attracting.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_attracting.py new file mode 100644 index 0000000..c2108dd --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_attracting.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + + +class TestAttractingComponents(object): + def setUp(self): + self.G1 = nx.DiGraph() + self.G1.add_edges_from([(5,11),(11,2),(11,9),(11,10), + (7,11),(7,8),(8,9),(3,8),(3,10)]) + self.G2 = nx.DiGraph() + self.G2.add_edges_from([(0,1),(0,2),(1,1),(1,2),(2,1)]) + + self.G3 = nx.DiGraph() + self.G3.add_edges_from([(0,1),(1,2),(2,1),(0,3),(3,4),(4,3)]) + + def test_attracting_components(self): + ac = nx.attracting_components(self.G1) + assert_true([2] in ac) + assert_true([9] in ac) + assert_true([10] in ac) + + ac = nx.attracting_components(self.G2) + ac = [tuple(sorted(x)) for x in ac] + assert_true(ac == [(1,2)]) + + ac = nx.attracting_components(self.G3) + ac = [tuple(sorted(x)) for x in ac] + assert_true((1,2) in ac) + assert_true((3,4) in ac) + assert_equal(len(ac), 2) + + def test_number_attacting_components(self): + assert_equal(len(nx.attracting_components(self.G1)), 3) + assert_equal(len(nx.attracting_components(self.G2)), 1) + assert_equal(len(nx.attracting_components(self.G3)), 2) + + def test_is_attracting_component(self): + assert_false(nx.is_attracting_component(self.G1)) + assert_false(nx.is_attracting_component(self.G2)) + assert_false(nx.is_attracting_component(self.G3)) + g2 = self.G3.subgraph([1,2]) + assert_true(nx.is_attracting_component(g2)) + + def test_attracting_component_subgraphs(self): + subgraphs = nx.attracting_component_subgraphs(self.G1) + for subgraph in subgraphs: + assert_equal(len(subgraph), 1) + + self.G2.add_edge(1,2,eattr='red') # test attrs copied to subgraphs + self.G2.node[2]['nattr']='blue' + self.G2.graph['gattr']='green' + subgraphs = nx.attracting_component_subgraphs(self.G2) + assert_equal(len(subgraphs), 1) + SG2=subgraphs[0] + assert_true(1 in SG2) + assert_true(2 in SG2) + assert_equal(SG2[1][2]['eattr'],'red') + assert_equal(SG2.node[2]['nattr'],'blue') + assert_equal(SG2.graph['gattr'],'green') + SG2.add_edge(1,2,eattr='blue') + assert_equal(SG2[1][2]['eattr'],'blue') + assert_equal(self.G2[1][2]['eattr'],'red') + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_biconnected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_biconnected.py new file mode 100644 index 0000000..85f967a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_biconnected.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx.algorithms.components import biconnected + +def assert_components_equal(x,y): + sx = set((frozenset([frozenset(e) for e in c]) for c in x)) + sy = set((frozenset([frozenset(e) for e in c]) for c in y)) + assert_equal(sx,sy) + +def test_barbell(): + G=nx.barbell_graph(8,4) + G.add_path([7,20,21,22]) + G.add_cycle([22,23,24,25]) + pts=set(biconnected.articulation_points(G)) + assert_equal(pts,set([7,8,9,10,11,12,20,21,22])) + + answer = [set([12, 13, 14, 15, 16, 17, 18, 19]), + set([0, 1, 2, 3, 4, 5, 6, 7]), + set([22, 23, 24, 25]), + set([11, 12]), + set([10, 11]), + set([9, 10]), + set([8, 9]), + set([7, 8]), + set([21, 22]), + set([20, 21]), + set([7, 20])] + bcc=list(biconnected.biconnected_components(G)) + bcc.sort(key=len, reverse=True) + assert_equal(bcc,answer) + + G.add_edge(2,17) + pts=set(biconnected.articulation_points(G)) + assert_equal(pts,set([7,20,21,22])) + +def test_articulation_points_cycle(): + G=nx.cycle_graph(3) + G.add_cycle([1,3,4]) + pts=set(biconnected.articulation_points(G)) + assert_equal(pts,set([1])) + +def test_is_biconnected(): + G=nx.cycle_graph(3) + assert_true(biconnected.is_biconnected(G)) + G.add_cycle([1,3,4]) + assert_false(biconnected.is_biconnected(G)) + +def test_empty_is_biconnected(): + G=nx.empty_graph(5) + assert_false(biconnected.is_biconnected(G)) + G.add_edge(0,1) + assert_false(biconnected.is_biconnected(G)) + +def test_biconnected_components_cycle(): + G=nx.cycle_graph(3) + G.add_cycle([1,3,4]) + pts = set(map(frozenset,biconnected.biconnected_components(G))) + assert_equal(pts,set([frozenset([0,1,2]),frozenset([1,3,4])])) + +def test_biconnected_component_subgraphs_cycle(): + G=nx.cycle_graph(3) + G.add_cycle([1,3,4,5]) + G.add_edge(1,3,eattr='red') # test copying of edge data + G.node[1]['nattr']='blue' + G.graph['gattr']='green' + Gc = set(biconnected.biconnected_component_subgraphs(G)) + assert_equal(len(Gc),2) + g1,g2=Gc + if 0 in g1: + assert_true(nx.is_isomorphic(g1,nx.Graph([(0,1),(0,2),(1,2)]))) + assert_true(nx.is_isomorphic(g2,nx.Graph([(1,3),(1,5),(3,4),(4,5)]))) + assert_equal(g2[1][3]['eattr'],'red') + assert_equal(g2.node[1]['nattr'],'blue') + assert_equal(g2.graph['gattr'],'green') + g2[1][3]['eattr']='blue' + assert_equal(g2[1][3]['eattr'],'blue') + assert_equal(G[1][3]['eattr'],'red') + else: + assert_true(nx.is_isomorphic(g1,nx.Graph([(1,3),(1,5),(3,4),(4,5)]))) + assert_true(nx.is_isomorphic(g2,nx.Graph([(0,1),(0,2),(1,2)]))) + assert_equal(g1[1][3]['eattr'],'red') + assert_equal(g1.node[1]['nattr'],'blue') + assert_equal(g1.graph['gattr'],'green') + g1[1][3]['eattr']='blue' + assert_equal(g1[1][3]['eattr'],'blue') + assert_equal(G[1][3]['eattr'],'red') + + +def test_biconnected_components1(): + # graph example from + # http://www.ibluemojo.com/school/articul_algorithm.html + edges=[(0,1), + (0,5), + (0,6), + (0,14), + (1,5), + (1,6), + (1,14), + (2,4), + (2,10), + (3,4), + (3,15), + (4,6), + (4,7), + (4,10), + (5,14), + (6,14), + (7,9), + (8,9), + (8,12), + (8,13), + (10,15), + (11,12), + (11,13), + (12,13)] + G=nx.Graph(edges) + pts = set(biconnected.articulation_points(G)) + assert_equal(pts,set([4,6,7,8,9])) + comps = list(biconnected.biconnected_component_edges(G)) + answer = [ + [(3,4),(15,3),(10,15),(10,4),(2,10),(4,2)], + [(13,12),(13,8),(11,13),(12,11),(8,12)], + [(9,8)], + [(7,9)], + [(4,7)], + [(6,4)], + [(14,0),(5,1),(5,0),(14,5),(14,1),(6,14),(6,0),(1,6),(0,1)], + ] + assert_components_equal(comps,answer) + +def test_biconnected_components2(): + G=nx.Graph() + G.add_cycle('ABC') + G.add_cycle('CDE') + G.add_cycle('FIJHG') + G.add_cycle('GIJ') + G.add_edge('E','G') + comps = list(biconnected.biconnected_component_edges(G)) + answer = [ + [tuple('GF'),tuple('FI'),tuple('IG'),tuple('IJ'),tuple('JG'),tuple('JH'),tuple('HG')], + [tuple('EG')], + [tuple('CD'),tuple('DE'),tuple('CE')], + [tuple('AB'),tuple('BC'),tuple('AC')] + ] + assert_components_equal(comps,answer) + +def test_biconnected_davis(): + D = nx.davis_southern_women_graph() + bcc = list(biconnected.biconnected_components(D))[0] + assert_true(set(D) == bcc) # All nodes in a giant bicomponent + # So no articulation points + assert_equal(list(biconnected.articulation_points(D)),[]) + +def test_biconnected_karate(): + K = nx.karate_club_graph() + answer = [set([0, 1, 2, 3, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]), + set([0, 4, 5, 6, 10, 16]), + set([0, 11])] + bcc = list(biconnected.biconnected_components(K)) + bcc.sort(key=len, reverse=True) + assert_true(list(biconnected.biconnected_components(K)) == answer) + assert_equal(list(biconnected.articulation_points(K)),[0]) + +def test_biconnected_eppstein(): + # tests from http://www.ics.uci.edu/~eppstein/PADS/Biconnectivity.py + G1 = nx.Graph({ + 0: [1,2,5], + 1: [0,5], + 2: [0,3,4], + 3: [2,4,5,6], + 4: [2,3,5,6], + 5: [0,1,3,4], + 6: [3,4]}) + G2 = nx.Graph({ + 0: [2,5], + 1: [3,8], + 2: [0,3,5], + 3: [1,2,6,8], + 4: [7], + 5: [0,2], + 6: [3,8], + 7: [4], + 8: [1,3,6]}) + assert_true(biconnected.is_biconnected(G1)) + assert_false(biconnected.is_biconnected(G2)) + answer_G2 = [set([1, 3, 6, 8]), set([0, 2, 5]), set([2, 3]), set([4, 7])] + bcc = list(biconnected.biconnected_components(G2)) + bcc.sort(key=len, reverse=True) + assert_equal(bcc, answer_G2) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_connected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_connected.py new file mode 100644 index 0000000..ae0247b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_connected.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx import convert_node_labels_to_integers as cnlti +from networkx import NetworkXError + +class TestConnected: + + def setUp(self): + G1=cnlti(nx.grid_2d_graph(2,2),first_label=0,ordering="sorted") + G2=cnlti(nx.lollipop_graph(3,3),first_label=4,ordering="sorted") + G3=cnlti(nx.house_graph(),first_label=10,ordering="sorted") + self.G=nx.union(G1,G2) + self.G=nx.union(self.G,G3) + self.DG=nx.DiGraph([(1,2),(1,3),(2,3)]) + self.grid=cnlti(nx.grid_2d_graph(4,4),first_label=1) + + def test_connected_components(self): + cc=nx.connected_components + G=self.G + C=[[0, 1, 2, 3], [4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]] + assert_equal(sorted([sorted(g) for g in cc(G)]),sorted(C)) + + def test_number_connected_components(self): + ncc=nx.number_connected_components + assert_equal(ncc(self.G),3) + + def test_number_connected_components2(self): + ncc=nx.number_connected_components + assert_equal(ncc(self.grid),1) + + def test_connected_components2(self): + cc=nx.connected_components + G=self.grid + C=[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]] + assert_equal(sorted([sorted(g) for g in cc(G)]),sorted(C)) + + def test_node_connected_components(self): + ncc=nx.node_connected_component + G=self.grid + C=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + assert_equal(sorted(ncc(G,1)),sorted(C)) + + def test_connected_component_subgraphs(self): + G=self.grid + G.add_edge(1,2,eattr='red') # test attributes copied to subgraphs + G.node[1]['nattr']='blue' + G.graph['gattr']='green' + ccs=nx.connected_component_subgraphs(G) + assert_equal(len(ccs),1) + sg=ccs[0] + assert_equal(sorted(sg.nodes()),list(range(1,17))) + assert_equal(sg[1][2]['eattr'],'red') + assert_equal(sg.node[1]['nattr'],'blue') + assert_equal(sg.graph['gattr'],'green') + sg[1][2]['eattr']='blue' + assert_equal(G[1][2]['eattr'],'red') + assert_equal(sg[1][2]['eattr'],'blue') + + + def test_is_connected(self): + assert_true(nx.is_connected(self.grid)) + G=nx.Graph() + G.add_nodes_from([1,2]) + assert_false(nx.is_connected(G)) + + def test_connected_raise(self): + assert_raises(NetworkXError,nx.connected_components,self.DG) + assert_raises(NetworkXError,nx.number_connected_components,self.DG) + assert_raises(NetworkXError,nx.connected_component_subgraphs,self.DG) + assert_raises(NetworkXError,nx.node_connected_component,self.DG,1) + assert_raises(NetworkXError,nx.is_connected,self.DG) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_strongly_connected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_strongly_connected.py new file mode 100644 index 0000000..d2569a9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_strongly_connected.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx import NetworkXError + +class TestStronglyConnected: + + def setUp(self): + self.gc=[] + G=nx.DiGraph() + G.add_edges_from([(1,2),(2,3),(2,8),(3,4),(3,7), + (4,5),(5,3),(5,6),(7,4),(7,6),(8,1),(8,7)]) + C=[[3, 4, 5, 7], [1, 2, 8], [6]] + self.gc.append((G,C)) + + G= nx.DiGraph() + G.add_edges_from([(1,2),(1,3),(1,4),(4,2),(3,4),(2,3)]) + C = [[2, 3, 4],[1]] + self.gc.append((G,C)) + + G = nx.DiGraph() + G.add_edges_from([(1,2),(2,3),(3,2),(2,1)]) + C = [[1, 2, 3]] + self.gc.append((G,C)) + + # Eppstein's tests + G = nx.DiGraph({ 0:[1],1:[2,3],2:[4,5],3:[4,5],4:[6],5:[],6:[]}) + C = [[0],[1],[2],[3],[4],[5],[6]] + self.gc.append((G,C)) + + G = nx.DiGraph({0:[1],1:[2,3,4],2:[0,3],3:[4],4:[3]}) + C = [[0,1,2],[3,4]] + self.gc.append((G,C)) + + + def test_tarjan(self): + scc=nx.strongly_connected_components + for G,C in self.gc: + assert_equal(sorted([sorted(g) for g in scc(G)]),sorted(C)) + + + def test_tarjan_recursive(self): + scc=nx.strongly_connected_components_recursive + for G,C in self.gc: + assert_equal(sorted([sorted(g) for g in scc(G)]),sorted(C)) + + + def test_kosaraju(self): + scc=nx.kosaraju_strongly_connected_components + for G,C in self.gc: + assert_equal(sorted([sorted(g) for g in scc(G)]),sorted(C)) + + def test_number_strongly_connected_components(self): + ncc=nx.number_strongly_connected_components + for G,C in self.gc: + assert_equal(ncc(G),len(C)) + + def test_is_strongly_connected(self): + for G,C in self.gc: + if len(C)==1: + assert_true(nx.is_strongly_connected(G)) + else: + assert_false(nx.is_strongly_connected(G)) + + + def test_strongly_connected_component_subgraphs(self): + scc=nx.strongly_connected_component_subgraphs + for G,C in self.gc: + assert_equal(sorted([sorted(g.nodes()) for g in scc(G)]),sorted(C)) + G,C=self.gc[0] + G.add_edge(1,2,eattr='red') + G.node[1]['nattr']='blue' + G.graph['gattr']='green' + sgs=scc(G)[1] + assert_equal(sgs[1][2]['eattr'],'red') + assert_equal(sgs.node[1]['nattr'],'blue') + assert_equal(sgs.graph['gattr'],'green') + sgs[1][2]['eattr']='blue' + assert_equal(G[1][2]['eattr'],'red') + assert_equal(sgs[1][2]['eattr'],'blue') + + def test_contract_scc1(self): + G = nx.DiGraph() + G.add_edges_from([(1,2),(2,3),(2,11),(2,12),(3,4),(4,3),(4,5), + (5,6),(6,5),(6,7),(7,8),(7,9),(7,10),(8,9), + (9,7),(10,6),(11,2),(11,4),(11,6),(12,6),(12,11)]) + scc = nx.strongly_connected_components(G) + cG = nx.condensation(G, scc) + # DAG + assert_true(nx.is_directed_acyclic_graph(cG)) + # # nodes + assert_equal(sorted(cG.nodes()),[0,1,2,3]) + # # edges + mapping={} + for i,component in enumerate(scc): + for n in component: + mapping[n] = i + edge=(mapping[2],mapping[3]) + assert_true(cG.has_edge(*edge)) + edge=(mapping[2],mapping[5]) + assert_true(cG.has_edge(*edge)) + edge=(mapping[3],mapping[5]) + assert_true(cG.has_edge(*edge)) + + def test_contract_scc_isolate(self): + # Bug found and fixed in [1687]. + G = nx.DiGraph() + G.add_edge(1,2) + G.add_edge(2,1) + scc = nx.strongly_connected_components(G) + cG = nx.condensation(G, scc) + assert_equal(cG.nodes(),[0]) + assert_equal(cG.edges(),[]) + + def test_contract_scc_edge(self): + G = nx.DiGraph() + G.add_edge(1,2) + G.add_edge(2,1) + G.add_edge(2,3) + G.add_edge(3,4) + G.add_edge(4,3) + scc = nx.strongly_connected_components(G) + cG = nx.condensation(G, scc) + assert_equal(cG.nodes(),[0,1]) + if 1 in scc[0]: + edge = (0,1) + else: + edge = (1,0) + assert_equal(cG.edges(),[edge]) + + def test_connected_raise(self): + G=nx.Graph() + assert_raises(NetworkXError,nx.strongly_connected_components,G) + assert_raises(NetworkXError,nx.kosaraju_strongly_connected_components,G) + assert_raises(NetworkXError,nx.strongly_connected_components_recursive,G) + assert_raises(NetworkXError,nx.strongly_connected_component_subgraphs,G) + assert_raises(NetworkXError,nx.is_strongly_connected,G) + assert_raises(NetworkXError,nx.condensation,G) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_weakly_connected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_weakly_connected.py new file mode 100644 index 0000000..d05e8ed --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/tests/test_weakly_connected.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx import NetworkXError + +class TestWeaklyConnected: + + def setUp(self): + self.gc=[] + G=nx.DiGraph() + G.add_edges_from([(1,2),(2,3),(2,8),(3,4),(3,7), + (4,5),(5,3),(5,6),(7,4),(7,6),(8,1),(8,7)]) + C=[[3, 4, 5, 7], [1, 2, 8], [6]] + self.gc.append((G,C)) + + G= nx.DiGraph() + G.add_edges_from([(1,2),(1,3),(1,4),(4,2),(3,4),(2,3)]) + C = [[2, 3, 4],[1]] + self.gc.append((G,C)) + + G = nx.DiGraph() + G.add_edges_from([(1,2),(2,3),(3,2),(2,1)]) + C = [[1, 2, 3]] + self.gc.append((G,C)) + + # Eppstein's tests + G = nx.DiGraph({ 0:[1],1:[2,3],2:[4,5],3:[4,5],4:[6],5:[],6:[]}) + C = [[0],[1],[2],[3],[4],[5],[6]] + self.gc.append((G,C)) + + G = nx.DiGraph({0:[1],1:[2,3,4],2:[0,3],3:[4],4:[3]}) + C = [[0,1,2],[3,4]] + self.gc.append((G,C)) + + + def test_weakly_connected_components(self): + wcc=nx.weakly_connected_components + cc=nx.connected_components + for G,C in self.gc: + U=G.to_undirected() + w=sorted([sorted(g) for g in wcc(G)]) + c=sorted([sorted(g) for g in cc(U)]) + assert_equal(w,c) + + def test_number_weakly_connected_components(self): + wcc=nx.number_weakly_connected_components + cc=nx.number_connected_components + for G,C in self.gc: + U=G.to_undirected() + w=wcc(G) + c=cc(U) + assert_equal(w,c) + + def test_weakly_connected_component_subgraphs(self): + wcc=nx.weakly_connected_component_subgraphs + cc=nx.connected_component_subgraphs + for G,C in self.gc: + U=G.to_undirected() + w=sorted([sorted(g.nodes()) for g in wcc(G)]) + c=sorted([sorted(g.nodes()) for g in cc(U)]) + assert_equal(w,c) + G,C=self.gc[0] + G.add_edge(1,2,eattr='red') + G.node[1]['nattr']='blue' + G.graph['gattr']='green' + sgs=wcc(G)[0] + assert_equal(sgs[1][2]['eattr'],'red') + assert_equal(sgs.node[1]['nattr'],'blue') + assert_equal(sgs.graph['gattr'],'green') + sgs[1][2]['eattr']='blue' + assert_equal(G[1][2]['eattr'],'red') + assert_equal(sgs[1][2]['eattr'],'blue') + + def test_is_weakly_connected(self): + wcc=nx.is_weakly_connected + cc=nx.is_connected + for G,C in self.gc: + U=G.to_undirected() + assert_equal(wcc(G),cc(U)) + + + def test_connected_raise(self): + G=nx.Graph() + assert_raises(NetworkXError,nx.weakly_connected_components,G) + assert_raises(NetworkXError,nx.number_weakly_connected_components,G) + assert_raises(NetworkXError,nx.weakly_connected_component_subgraphs,G) + assert_raises(NetworkXError,nx.is_weakly_connected,G) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/weakly_connected.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/weakly_connected.py new file mode 100644 index 0000000..410a6c2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/components/weakly_connected.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +Weakly connected components. +""" +__authors__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)' + 'Christopher Ellison']) +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['number_weakly_connected_components', + 'weakly_connected_components', + 'weakly_connected_component_subgraphs', + 'is_weakly_connected' + ] + +import networkx as nx + +def weakly_connected_components(G): + """Return weakly connected components of G. + """ + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + Use connected_components() """) + seen={} + components=[] + for v in G: + if v not in seen: + c=_single_source_shortest_unipath_length(G,v) + components.append(list(c.keys())) + seen.update(c) + components.sort(key=len,reverse=True) + return components + + +def number_weakly_connected_components(G): + """Return the number of connected components in G. + For directed graphs only. + """ + return len(weakly_connected_components(G)) + +def weakly_connected_component_subgraphs(G): + """Return weakly connected components as subgraphs. + + Graph, node, and edge attributes are copied to the subgraphs. + """ + wcc=weakly_connected_components(G) + graph_list=[] + for c in wcc: + graph_list.append(G.subgraph(c).copy()) + return graph_list + +def is_weakly_connected(G): + """Test directed graph for weak connectivity. + + Parameters + ---------- + G : NetworkX Graph + A directed graph. + + Returns + ------- + connected : bool + True if the graph is weakly connected, False otherwise. + + See Also + -------- + strongly_connected_components + + Notes + ----- + For directed graphs only. + """ + if not G.is_directed(): + raise nx.NetworkXError("""Not allowed for undirected graph G. + See is_connected() for connectivity test.""") + + if len(G)==0: + raise nx.NetworkXPointlessConcept( + """Connectivity is undefined for the null graph.""") + + return len(weakly_connected_components(G)[0])==len(G) + +def _single_source_shortest_unipath_length(G,source,cutoff=None): + """Compute the shortest path lengths from source to all reachable nodes. + + The direction of the edge between nodes is ignored. + + For directed graphs only. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path + + cutoff : integer, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + lengths : dictionary + Dictionary of shortest path lengths keyed by target. + """ + # namespace speedups + Gsucc = G.succ + Gpred = G.pred + + seen={} # level (number of hops) when seen in BFS + level=0 # the current level + nextlevel = set([source]) # set of nodes to check at next level + while nextlevel: + thislevel=nextlevel # advance to next level + nextlevel = set() # and start a new list (fringe) + for v in thislevel: + if v not in seen: + seen[v]=level # set the level of vertex v + nextlevel.update(Gsucc[v]) # add successors of v + nextlevel.update(Gpred[v]) # add predecessors of v + if (cutoff is not None and cutoff <= level): break + level=level+1 + return seen # return all path lengths as dictionary diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/__init__.py new file mode 100644 index 0000000..d14c33e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/__init__.py @@ -0,0 +1,4 @@ +"""Flow based connectivity and cut algorithms +""" +from networkx.algorithms.connectivity.connectivity import * +from networkx.algorithms.connectivity.cuts import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/connectivity.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/connectivity.py new file mode 100644 index 0000000..bf6f272 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/connectivity.py @@ -0,0 +1,607 @@ +# -*- coding: utf-8 -*- +""" +Flow based connectivity algorithms +""" +import itertools +import networkx as nx + +__author__ = '\n'.join(['Jordi Torrents ']) + +__all__ = [ 'average_node_connectivity', + 'local_node_connectivity', + 'node_connectivity', + 'local_edge_connectivity', + 'edge_connectivity', + 'all_pairs_node_connectivity_matrix', + 'dominating_set', + ] + +def average_node_connectivity(G): + r"""Returns the average connectivity of a graph G. + + The average connectivity `\bar{\kappa}` of a graph G is the average + of local node connectivity over all pairs of nodes of G [1]_ . + + .. math:: + + \bar{\kappa}(G) = \frac{\sum_{u,v} \kappa_{G}(u,v)}{{n \choose 2}} + + Parameters + ---------- + + G : NetworkX graph + Undirected graph + + Returns + ------- + K : float + Average node connectivity + + See also + -------- + local_node_connectivity + node_connectivity + local_edge_connectivity + edge_connectivity + max_flow + ford_fulkerson + + References + ---------- + .. [1] Beineke, L., O. Oellermann, and R. Pippert (2002). The average + connectivity of a graph. Discrete mathematics 252(1-3), 31-45. + http://www.sciencedirect.com/science/article/pii/S0012365X01001807 + + """ + if G.is_directed(): + iter_func = itertools.permutations + else: + iter_func = itertools.combinations + + H, mapping = _aux_digraph_node_connectivity(G) + num = 0. + den = 0. + for u,v in iter_func(G, 2): + den += 1 + num += local_node_connectivity(G, u, v, aux_digraph=H, mapping=mapping) + + if den == 0: # Null Graph + return 0 + return num/den + +def _aux_digraph_node_connectivity(G): + r""" Creates a directed graph D from an undirected graph G to compute flow + based node connectivity. + + For an undirected graph G having `n` nodes and `m` edges we derive a + directed graph D with 2n nodes and 2m+n arcs by replacing each + original node `v` with two nodes `vA`,`vB` linked by an (internal) + arc in D. Then for each edge (u,v) in G we add two arcs (uB,vA) + and (vB,uA) in D. Finally we set the attribute capacity = 1 for each + arc in D [1]. + + For a directed graph having `n` nodes and `m` arcs we derive a + directed graph D with 2n nodes and m+n arcs by replacing each + original node `v` with two nodes `vA`,`vB` linked by an (internal) + arc `(vA,vB)` in D. Then for each arc (u,v) in G we add one arc (uB,vA) + in D. Finally we set the attribute capacity = 1 for each arc in D. + + References + ---------- + .. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and + Erlebach, 'Network Analysis: Methodological Foundations', Lecture + Notes in Computer Science, Volume 3418, Springer-Verlag, 2005. + http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf + + """ + directed = G.is_directed() + + mapping = {} + D = nx.DiGraph() + for i,node in enumerate(G): + mapping[node] = i + D.add_node('%dA' % i,id=node) + D.add_node('%dB' % i,id=node) + D.add_edge('%dA' % i, '%dB' % i, capacity=1) + + edges = [] + for (source, target) in G.edges(): + edges.append(('%sB' % mapping[source], '%sA' % mapping[target])) + if not directed: + edges.append(('%sB' % mapping[target], '%sA' % mapping[source])) + + D.add_edges_from(edges, capacity=1) + return D, mapping + +def local_node_connectivity(G, s, t, aux_digraph=None, mapping=None): + r"""Computes local node connectivity for nodes s and t. + + Local node connectivity for two non adjacent nodes s and t is the + minimum number of nodes that must be removed (along with their incident + edges) to disconnect them. + + This is a flow based implementation of node connectivity. We compute the + maximum flow on an auxiliary digraph build from the original input + graph (see below for details). This is equal to the local node + connectivity because the value of a maximum s-t-flow is equal to the + capacity of a minimum s-t-cut (Ford and Fulkerson theorem) [1]_ . + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + s : node + Source node + + t : node + Target node + + aux_digraph : NetworkX DiGraph (default=None) + Auxiliary digraph to compute flow based node connectivity. If None + the auxiliary digraph is build. + + mapping : dict (default=None) + Dictionary with a mapping of node names in G and in the auxiliary digraph. + + Returns + ------- + K : integer + local node connectivity for nodes s and t + + Examples + -------- + >>> # Platonic icosahedral graph has node connectivity 5 + >>> # for each non adjacent node pair + >>> G = nx.icosahedral_graph() + >>> nx.local_node_connectivity(G,0,6) + 5 + + Notes + ----- + This is a flow based implementation of node connectivity. We compute the + maximum flow using the Ford and Fulkerson algorithm on an auxiliary digraph + build from the original input graph: + + For an undirected graph G having `n` nodes and `m` edges we derive a + directed graph D with 2n nodes and 2m+n arcs by replacing each + original node `v` with two nodes `v_A`, `v_B` linked by an (internal) + arc in `D`. Then for each edge (`u`, `v`) in G we add two arcs + (`u_B`, `v_A`) and (`v_B`, `u_A`) in `D`. Finally we set the attribute + capacity = 1 for each arc in `D` [1]_ . + + For a directed graph G having `n` nodes and `m` arcs we derive a + directed graph `D` with `2n` nodes and `m+n` arcs by replacing each + original node `v` with two nodes `v_A`, `v_B` linked by an (internal) + arc `(v_A, v_B)` in D. Then for each arc `(u,v)` in G we add one arc + `(u_B,v_A)` in `D`. Finally we set the attribute capacity = 1 for + each arc in `D`. + + This is equal to the local node connectivity because the value of + a maximum s-t-flow is equal to the capacity of a minimum s-t-cut (Ford + and Fulkerson theorem). + + See also + -------- + node_connectivity + all_pairs_node_connectivity_matrix + local_edge_connectivity + edge_connectivity + max_flow + ford_fulkerson + + References + ---------- + .. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and + Erlebach, 'Network Analysis: Methodological Foundations', Lecture + Notes in Computer Science, Volume 3418, Springer-Verlag, 2005. + http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf + + """ + if aux_digraph is None or mapping is None: + H, mapping = _aux_digraph_node_connectivity(G) + else: + H = aux_digraph + return nx.max_flow(H,'%sB' % mapping[s], '%sA' % mapping[t]) + +def node_connectivity(G, s=None, t=None): + r"""Returns node connectivity for a graph or digraph G. + + Node connectivity is equal to the minimum number of nodes that + must be removed to disconnect G or render it trivial. If source + and target nodes are provided, this function returns the local node + connectivity: the minimum number of nodes that must be removed to break + all paths from source to target in G. + + This is a flow based implementation. The algorithm is based in + solving a number of max-flow problems (ie local st-node connectivity, + see local_node_connectivity) to determine the capacity of the + minimum cut on an auxiliary directed network that corresponds to the + minimum node cut of G. It handles both directed and undirected graphs. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + s : node + Source node. Optional (default=None) + + t : node + Target node. Optional (default=None) + + Returns + ------- + K : integer + Node connectivity of G, or local node connectivity if source + and target were provided + + Examples + -------- + >>> # Platonic icosahedral graph is 5-node-connected + >>> G = nx.icosahedral_graph() + >>> nx.node_connectivity(G) + 5 + >>> nx.node_connectivity(G, 3, 7) + 5 + + Notes + ----- + This is a flow based implementation of node connectivity. The + algorithm works by solving `O((n-\delta-1+\delta(\delta-1)/2)` max-flow + problems on an auxiliary digraph. Where `\delta` is the minimum degree + of G. For details about the auxiliary digraph and the computation of + local node connectivity see local_node_connectivity. + + This implementation is based on algorithm 11 in [1]_. We use the Ford + and Fulkerson algorithm to compute max flow (see ford_fulkerson). + + See also + -------- + local_node_connectivity + all_pairs_node_connectivity_matrix + local_edge_connectivity + edge_connectivity + max_flow + ford_fulkerson + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + + """ + # Local node connectivity + if s is not None and t is not None: + if s not in G: + raise nx.NetworkXError('node %s not in graph' % s) + if t not in G: + raise nx.NetworkXError('node %s not in graph' % t) + return local_node_connectivity(G, s, t) + # Global node connectivity + if G.is_directed(): + if not nx.is_weakly_connected(G): + return 0 + iter_func = itertools.permutations + # I think that it is necessary to consider both predecessors + # and successors for directed graphs + def neighbors(v): + return itertools.chain.from_iterable([G.predecessors_iter(v), + G.successors_iter(v)]) + else: + if not nx.is_connected(G): + return 0 + iter_func = itertools.combinations + neighbors = G.neighbors_iter + # Initial guess \kappa = n - 1 + K = G.order()-1 + deg = G.degree() + min_deg = min(deg.values()) + v = next(n for n,d in deg.items() if d==min_deg) + # Reuse the auxiliary digraph + H, mapping = _aux_digraph_node_connectivity(G) + # compute local node connectivity with all non-neighbors nodes + for w in set(G) - set(neighbors(v)) - set([v]): + K = min(K, local_node_connectivity(G, v, w, + aux_digraph=H, mapping=mapping)) + # Same for non adjacent pairs of neighbors of v + for x,y in iter_func(neighbors(v), 2): + if y in G[x]: continue + K = min(K, local_node_connectivity(G, x, y, + aux_digraph=H, mapping=mapping)) + return K + +def all_pairs_node_connectivity_matrix(G): + """Return a numpy 2d ndarray with node connectivity between all pairs + of nodes. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + K : 2d numpy ndarray + node connectivity between all pairs of nodes. + + See also + -------- + local_node_connectivity + node_connectivity + local_edge_connectivity + edge_connectivity + max_flow + ford_fulkerson + + """ + try: + import numpy + except ImportError: + raise ImportError(\ + "all_pairs_node_connectivity_matrix() requires NumPy") + + n = G.order() + M = numpy.zeros((n, n), dtype=int) + # Create auxiliary Digraph + D, mapping = _aux_digraph_node_connectivity(G) + + if G.is_directed(): + for u, v in itertools.permutations(G, 2): + K = local_node_connectivity(G, u, v, aux_digraph=D, mapping=mapping) + M[mapping[u],mapping[v]] = K + else: + for u, v in itertools.combinations(G, 2): + K = local_node_connectivity(G, u, v, aux_digraph=D, mapping=mapping) + M[mapping[u],mapping[v]] = M[mapping[v],mapping[u]] = K + + return M + +def _aux_digraph_edge_connectivity(G): + """Auxiliary digraph for computing flow based edge connectivity + + If the input graph is undirected, we replace each edge (u,v) with + two reciprocal arcs (u,v) and (v,u) and then we set the attribute + 'capacity' for each arc to 1. If the input graph is directed we simply + add the 'capacity' attribute. Part of algorithm 1 in [1]_ . + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. (this is a + chapter, look for the reference of the book). + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + """ + if G.is_directed(): + if nx.get_edge_attributes(G, 'capacity'): + return G + D = G.copy() + capacity = dict((e,1) for e in D.edges()) + nx.set_edge_attributes(D, 'capacity', capacity) + return D + else: + D = G.to_directed() + capacity = dict((e,1) for e in D.edges()) + nx.set_edge_attributes(D, 'capacity', capacity) + return D + +def local_edge_connectivity(G, u, v, aux_digraph=None): + r"""Returns local edge connectivity for nodes s and t in G. + + Local edge connectivity for two nodes s and t is the minimum number + of edges that must be removed to disconnect them. + + This is a flow based implementation of edge connectivity. We compute the + maximum flow on an auxiliary digraph build from the original + network (see below for details). This is equal to the local edge + connectivity because the value of a maximum s-t-flow is equal to the + capacity of a minimum s-t-cut (Ford and Fulkerson theorem) [1]_ . + + Parameters + ---------- + G : NetworkX graph + Undirected or directed graph + + s : node + Source node + + t : node + Target node + + aux_digraph : NetworkX DiGraph (default=None) + Auxiliary digraph to compute flow based edge connectivity. If None + the auxiliary digraph is build. + + Returns + ------- + K : integer + local edge connectivity for nodes s and t + + Examples + -------- + >>> # Platonic icosahedral graph has edge connectivity 5 + >>> # for each non adjacent node pair + >>> G = nx.icosahedral_graph() + >>> nx.local_edge_connectivity(G,0,6) + 5 + + Notes + ----- + This is a flow based implementation of edge connectivity. We compute the + maximum flow using the Ford and Fulkerson algorithm on an auxiliary digraph + build from the original graph: + + If the input graph is undirected, we replace each edge (u,v) with + two reciprocal arcs `(u,v)` and `(v,u)` and then we set the attribute + 'capacity' for each arc to 1. If the input graph is directed we simply + add the 'capacity' attribute. This is an implementation of algorithm 1 + in [1]_. + + The maximum flow in the auxiliary network is equal to the local edge + connectivity because the value of a maximum s-t-flow is equal to the + capacity of a minimum s-t-cut (Ford and Fulkerson theorem). + + See also + -------- + local_node_connectivity + node_connectivity + edge_connectivity + max_flow + ford_fulkerson + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + + """ + if aux_digraph is None: + H = _aux_digraph_edge_connectivity(G) + else: + H = aux_digraph + return nx.max_flow(H, u, v) + +def edge_connectivity(G, s=None, t=None): + r"""Returns the edge connectivity of the graph or digraph G. + + The edge connectivity is equal to the minimum number of edges that + must be removed to disconnect G or render it trivial. If source + and target nodes are provided, this function returns the local edge + connectivity: the minimum number of edges that must be removed to + break all paths from source to target in G. + + This is a flow based implementation. The algorithm is based in solving + a number of max-flow problems (ie local st-edge connectivity, see + local_edge_connectivity) to determine the capacity of the minimum + cut on an auxiliary directed network that corresponds to the minimum + edge cut of G. It handles both directed and undirected graphs. + + Parameters + ---------- + G : NetworkX graph + Undirected or directed graph + + s : node + Source node. Optional (default=None) + + t : node + Target node. Optional (default=None) + + Returns + ------- + K : integer + Edge connectivity for G, or local edge connectivity if source + and target were provided + + Examples + -------- + >>> # Platonic icosahedral graph is 5-edge-connected + >>> G = nx.icosahedral_graph() + >>> nx.edge_connectivity(G) + 5 + + Notes + ----- + This is a flow based implementation of global edge connectivity. For + undirected graphs the algorithm works by finding a 'small' dominating + set of nodes of G (see algorithm 7 in [1]_ ) and computing local max flow + (see local_edge_connectivity) between an arbitrary node in the dominating + set and the rest of nodes in it. This is an implementation of + algorithm 6 in [1]_ . + + For directed graphs, the algorithm does n calls to the max flow function. + This is an implementation of algorithm 8 in [1]_ . We use the Ford and + Fulkerson algorithm to compute max flow (see ford_fulkerson). + + See also + -------- + local_node_connectivity + node_connectivity + local_edge_connectivity + max_flow + ford_fulkerson + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + + """ + # Local edge connectivity + if s is not None and t is not None: + if s not in G: + raise nx.NetworkXError('node %s not in graph' % s) + if t not in G: + raise nx.NetworkXError('node %s not in graph' % t) + return local_edge_connectivity(G, s, t) + # Global edge connectivity + if G.is_directed(): + # Algorithm 8 in [1] + if not nx.is_weakly_connected(G): + return 0 + # initial value for lambda is min degree (\delta(G)) + L = min(G.degree().values()) + # reuse auxiliary digraph + H = _aux_digraph_edge_connectivity(G) + nodes = G.nodes() + n = len(nodes) + for i in range(n): + try: + L = min(L, local_edge_connectivity(G, nodes[i], + nodes[i+1], aux_digraph=H)) + except IndexError: # last node! + L = min(L, local_edge_connectivity(G, nodes[i], + nodes[0], aux_digraph=H)) + return L + else: # undirected + # Algorithm 6 in [1] + if not nx.is_connected(G): + return 0 + # initial value for lambda is min degree (\delta(G)) + L = min(G.degree().values()) + # reuse auxiliary digraph + H = _aux_digraph_edge_connectivity(G) + # A dominating set is \lambda-covering + # We need a dominating set with at least two nodes + for node in G: + D = dominating_set(G, start_with=node) + v = D.pop() + if D: break + else: + # in complete graphs the dominating sets will always be of one node + # thus we return min degree + return L + for w in D: + L = min(L, local_edge_connectivity(G, v, w, aux_digraph=H)) + return L + +def dominating_set(G, start_with=None): + # Algorithm 7 in [1] + all_nodes = set(G) + if start_with is None: + v = set(G).pop() # pick a node + else: + if start_with not in G: + raise nx.NetworkXError('node %s not in G' % start_with) + v = start_with + D = set([v]) + ND = set([nbr for nbr in G[v]]) + other = all_nodes - ND - D + while other: + w = other.pop() + D.add(w) + ND.update([nbr for nbr in G[w] if nbr not in D]) + other = all_nodes - ND - D + return D + +def is_dominating_set(G, nbunch): + # Proposed by Dan on the mailing list + allnodes=set(G) + testset=set(n for n in nbunch if n in G) + nbrs=set() + for n in testset: + nbrs.update(G[n]) + if nbrs - allnodes: # some nodes left--not dominating + return False + else: + return True + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/cuts.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/cuts.py new file mode 100644 index 0000000..a55bc0d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/cuts.py @@ -0,0 +1,382 @@ +# -*- coding: utf-8 -*- +""" +Flow based cut algorithms +""" +# http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf +# http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf +import itertools +from operator import itemgetter +import networkx as nx +from networkx.algorithms.connectivity.connectivity import \ + _aux_digraph_node_connectivity, _aux_digraph_edge_connectivity, \ + dominating_set, node_connectivity + +__author__ = '\n'.join(['Jordi Torrents ']) + +__all__ = [ 'minimum_st_node_cut', + 'minimum_node_cut', + 'minimum_st_edge_cut', + 'minimum_edge_cut', + ] + +def minimum_st_edge_cut(G, s, t, capacity='capacity'): + """Returns the edges of the cut-set of a minimum (s, t)-cut. + + We use the max-flow min-cut theorem, i.e., the capacity of a minimum + capacity cut is equal to the flow value of a maximum flow. + + Parameters + ---------- + G : NetworkX graph + Edges of the graph are expected to have an attribute called + 'capacity'. If this attribute is not present, the edge is + considered to have infinite capacity. + + s : node + Source node for the flow. + + t : node + Sink node for the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + Returns + ------- + cutset : set + Set of edges that, if removed from the graph, will disconnect it + + Raises + ------ + NetworkXUnbounded + If the graph has a path of infinite capacity, all cuts have + infinite capacity and the function raises a NetworkXError. + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_edge('x','a', capacity = 3.0) + >>> G.add_edge('x','b', capacity = 1.0) + >>> G.add_edge('a','c', capacity = 3.0) + >>> G.add_edge('b','c', capacity = 5.0) + >>> G.add_edge('b','d', capacity = 4.0) + >>> G.add_edge('d','e', capacity = 2.0) + >>> G.add_edge('c','y', capacity = 2.0) + >>> G.add_edge('e','y', capacity = 3.0) + >>> sorted(nx.minimum_edge_cut(G, 'x', 'y')) + [('c', 'y'), ('x', 'b')] + >>> nx.min_cut(G, 'x', 'y') + 3.0 + """ + try: + flow, H = nx.ford_fulkerson_flow_and_auxiliary(G, s, t, capacity=capacity) + cutset = set() + # Compute reachable nodes from source in the residual network + reachable = set(nx.single_source_shortest_path(H,s)) + # And unreachable nodes + others = set(H) - reachable # - set([s]) + # Any edge in the original network linking these two partitions + # is part of the edge cutset + for u, nbrs in ((n, G[n]) for n in reachable): + cutset.update((u,v) for v in nbrs if v in others) + return cutset + except nx.NetworkXUnbounded: + # Should we raise any other exception or just let ford_fulkerson + # propagate nx.NetworkXUnbounded ? + raise nx.NetworkXUnbounded("Infinite capacity path, no minimum cut.") + +def minimum_st_node_cut(G, s, t, aux_digraph=None, mapping=None): + r"""Returns a set of nodes of minimum cardinality that disconnect source + from target in G. + + This function returns the set of nodes of minimum cardinality that, + if removed, would destroy all paths among source and target in G. + + Parameters + ---------- + G : NetworkX graph + + s : node + Source node. + + t : node + Target node. + + Returns + ------- + cutset : set + Set of nodes that, if removed, would destroy all paths between + source and target in G. + + Examples + -------- + >>> # Platonic icosahedral graph has node connectivity 5 + >>> G = nx.icosahedral_graph() + >>> len(nx.minimum_node_cut(G, 0, 6)) + 5 + + Notes + ----- + This is a flow based implementation of minimum node cut. The algorithm + is based in solving a number of max-flow problems (ie local st-node + connectivity, see local_node_connectivity) to determine the capacity + of the minimum cut on an auxiliary directed network that corresponds + to the minimum node cut of G. It handles both directed and undirected + graphs. + + This implementation is based on algorithm 11 in [1]_. We use the Ford + and Fulkerson algorithm to compute max flow (see ford_fulkerson). + + See also + -------- + node_connectivity + edge_connectivity + minimum_edge_cut + max_flow + ford_fulkerson + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + + """ + if aux_digraph is None or mapping is None: + H, mapping = _aux_digraph_node_connectivity(G) + else: + H = aux_digraph + edge_cut = minimum_st_edge_cut(H, '%sB' % mapping[s], '%sA' % mapping[t]) + # Each node in the original graph maps to two nodes of the auxiliary graph + node_cut = set(H.node[node]['id'] for edge in edge_cut for node in edge) + return node_cut - set([s,t]) + +def minimum_node_cut(G, s=None, t=None): + r"""Returns a set of nodes of minimum cardinality that disconnects G. + + If source and target nodes are provided, this function returns the + set of nodes of minimum cardinality that, if removed, would destroy + all paths among source and target in G. If not, it returns a set + of nodes of minimum cardinality that disconnects G. + + Parameters + ---------- + G : NetworkX graph + + s : node + Source node. Optional (default=None) + + t : node + Target node. Optional (default=None) + + Returns + ------- + cutset : set + Set of nodes that, if removed, would disconnect G. If source + and target nodes are provided, the set contians the nodes that + if removed, would destroy all paths between source and target. + + Examples + -------- + >>> # Platonic icosahedral graph has node connectivity 5 + >>> G = nx.icosahedral_graph() + >>> len(nx.minimum_node_cut(G)) + 5 + >>> # this is the minimum over any pair of non adjacent nodes + >>> from itertools import combinations + >>> for u,v in combinations(G, 2): + ... if v not in G[u]: + ... assert(len(nx.minimum_node_cut(G,u,v)) == 5) + ... + + Notes + ----- + This is a flow based implementation of minimum node cut. The algorithm + is based in solving a number of max-flow problems (ie local st-node + connectivity, see local_node_connectivity) to determine the capacity + of the minimum cut on an auxiliary directed network that corresponds + to the minimum node cut of G. It handles both directed and undirected + graphs. + + This implementation is based on algorithm 11 in [1]_. We use the Ford + and Fulkerson algorithm to compute max flow (see ford_fulkerson). + + See also + -------- + node_connectivity + edge_connectivity + minimum_edge_cut + max_flow + ford_fulkerson + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + + """ + # Local minimum node cut + if s is not None and t is not None: + if s not in G: + raise nx.NetworkXError('node %s not in graph' % s) + if t not in G: + raise nx.NetworkXError('node %s not in graph' % t) + return minimum_st_node_cut(G, s, t) + # Global minimum node cut + # Analog to the algoritm 11 for global node connectivity in [1] + if G.is_directed(): + if not nx.is_weakly_connected(G): + raise nx.NetworkXError('Input graph is not connected') + iter_func = itertools.permutations + def neighbors(v): + return itertools.chain.from_iterable([G.predecessors_iter(v), + G.successors_iter(v)]) + else: + if not nx.is_connected(G): + raise nx.NetworkXError('Input graph is not connected') + iter_func = itertools.combinations + neighbors = G.neighbors_iter + # Choose a node with minimum degree + deg = G.degree() + min_deg = min(deg.values()) + v = next(n for n,d in deg.items() if d == min_deg) + # Initial node cutset is all neighbors of the node with minimum degree + min_cut = set(G[v]) + # Reuse the auxiliary digraph + H, mapping = _aux_digraph_node_connectivity(G) + # compute st node cuts between v and all its non-neighbors nodes in G + # and store the minimum + for w in set(G) - set(neighbors(v)) - set([v]): + this_cut = minimum_st_node_cut(G, v, w, aux_digraph=H, mapping=mapping) + if len(min_cut) >= len(this_cut): + min_cut = this_cut + # Same for non adjacent pairs of neighbors of v + for x,y in iter_func(neighbors(v),2): + if y in G[x]: continue + this_cut = minimum_st_node_cut(G, x, y, aux_digraph=H, mapping=mapping) + if len(min_cut) >= len(this_cut): + min_cut = this_cut + return min_cut + +def minimum_edge_cut(G, s=None, t=None): + r"""Returns a set of edges of minimum cardinality that disconnects G. + + If source and target nodes are provided, this function returns the + set of edges of minimum cardinality that, if removed, would break + all paths among source and target in G. If not, it returns a set of + edges of minimum cardinality that disconnects G. + + Parameters + ---------- + G : NetworkX graph + + s : node + Source node. Optional (default=None) + + t : node + Target node. Optional (default=None) + + Returns + ------- + cutset : set + Set of edges that, if removed, would disconnect G. If source + and target nodes are provided, the set contians the edges that + if removed, would destroy all paths between source and target. + + Examples + -------- + >>> # Platonic icosahedral graph has edge connectivity 5 + >>> G = nx.icosahedral_graph() + >>> len(nx.minimum_edge_cut(G)) + 5 + >>> # this is the minimum over any pair of nodes + >>> from itertools import combinations + >>> for u,v in combinations(G, 2): + ... assert(len(nx.minimum_edge_cut(G,u,v)) == 5) + ... + + Notes + ----- + This is a flow based implementation of minimum edge cut. For + undirected graphs the algorithm works by finding a 'small' dominating + set of nodes of G (see algorithm 7 in [1]_) and computing the maximum + flow between an arbitrary node in the dominating set and the rest of + nodes in it. This is an implementation of algorithm 6 in [1]_. + + For directed graphs, the algorithm does n calls to the max flow function. + This is an implementation of algorithm 8 in [1]_. We use the Ford and + Fulkerson algorithm to compute max flow (see ford_fulkerson). + + See also + -------- + node_connectivity + edge_connectivity + minimum_node_cut + max_flow + ford_fulkerson + + References + ---------- + .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms. + http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf + + """ + # reuse auxiliary digraph + H = _aux_digraph_edge_connectivity(G) + # Local minimum edge cut if s and t are not None + if s is not None and t is not None: + if s not in G: + raise nx.NetworkXError('node %s not in graph' % s) + if t not in G: + raise nx.NetworkXError('node %s not in graph' % t) + return minimum_st_edge_cut(H, s, t) + # Global minimum edge cut + # Analog to the algoritm for global edge connectivity + if G.is_directed(): + # Based on algorithm 8 in [1] + if not nx.is_weakly_connected(G): + raise nx.NetworkXError('Input graph is not connected') + # Initial cutset is all edges of a node with minimum degree + deg = G.degree() + min_deg = min(deg.values()) + node = next(n for n,d in deg.items() if d==min_deg) + min_cut = G.edges(node) + nodes = G.nodes() + n = len(nodes) + for i in range(n): + try: + this_cut = minimum_st_edge_cut(H, nodes[i], nodes[i+1]) + if len(this_cut) <= len(min_cut): + min_cut = this_cut + except IndexError: # Last node! + this_cut = minimum_st_edge_cut(H, nodes[i], nodes[0]) + if len(this_cut) <= len(min_cut): + min_cut = this_cut + return min_cut + else: # undirected + # Based on algorithm 6 in [1] + if not nx.is_connected(G): + raise nx.NetworkXError('Input graph is not connected') + # Initial cutset is all edges of a node with minimum degree + deg = G.degree() + min_deg = min(deg.values()) + node = next(n for n,d in deg.items() if d==min_deg) + min_cut = G.edges(node) + # A dominating set is \lambda-covering + # We need a dominating set with at least two nodes + for node in G: + D = dominating_set(G, start_with=node) + v = D.pop() + if D: break + else: + # in complete graphs the dominating set will always be of one node + # thus we return min_cut, which now contains the edges of a node + # with minimum degree + return min_cut + for w in D: + this_cut = minimum_st_edge_cut(H, v, w) + if len(this_cut) <= len(min_cut): + min_cut = this_cut + return min_cut diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_connectivity.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_connectivity.py new file mode 100644 index 0000000..2745504 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_connectivity.py @@ -0,0 +1,145 @@ +from nose.tools import assert_equal, assert_true, assert_false +import networkx as nx + +# helper functions for tests +def _generate_no_biconnected(max_attempts=50): + attempts = 0 + while True: + G = nx.fast_gnp_random_graph(100,0.0575) + if nx.is_connected(G) and not nx.is_biconnected(G): + attempts = 0 + yield G + else: + if attempts >= max_attempts: + msg = "Tried %d times: no suitable Graph." + raise Exception(msg % max_attempts) + else: + attempts += 1 + +def is_dominating_set(G, nbunch): + # Proposed by Dan on the mailing list + allnodes=set(G) + testset=set(n for n in nbunch if n in G) + nbrs=set() + for n in testset: + nbrs.update(G[n]) + if nbrs - allnodes: # some nodes left--not dominating + return False + else: + return True + +# Tests for node and edge connectivity +def test_average_connectivity(): + # figure 1 from: + # Beineke, L., O. Oellermann, and R. Pippert (2002). The average + # connectivity of a graph. Discrete mathematics 252(1-3), 31-45 + # http://www.sciencedirect.com/science/article/pii/S0012365X01001807 + G1 = nx.path_graph(3) + G1.add_edges_from([(1,3),(1,4)]) + assert_equal(nx.average_node_connectivity(G1),1) + G2 = nx.path_graph(3) + G2.add_edges_from([(1,3),(1,4),(0,3),(0,4),(3,4)]) + assert_equal(nx.average_node_connectivity(G2),2.2) + G3 = nx.Graph() + assert_equal(nx.average_node_connectivity(G3),0) + +def test_articulation_points(): + Ggen = _generate_no_biconnected() + for i in range(5): + G = next(Ggen) + assert_equal(nx.node_connectivity(G), 1) + +def test_brandes_erlebach(): + # Figure 1 chapter 7: Connectivity + # http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf + G = nx.Graph() + G.add_edges_from([(1,2),(1,3),(1,4),(1,5),(2,3),(2,6),(3,4), + (3,6),(4,6),(4,7),(5,7),(6,8),(6,9),(7,8), + (7,10),(8,11),(9,10),(9,11),(10,11)]) + assert_equal(3,nx.local_edge_connectivity(G,1,11)) + assert_equal(3,nx.edge_connectivity(G,1,11)) + assert_equal(2,nx.local_node_connectivity(G,1,11)) + assert_equal(2,nx.node_connectivity(G,1,11)) + assert_equal(2,nx.edge_connectivity(G)) # node 5 has degree 2 + assert_equal(2,nx.node_connectivity(G)) + +def test_white_harary_1(): + # Figure 1b white and harary (2001) + # # http://eclectic.ss.uci.edu/~drwhite/sm-w23.PDF + # A graph with high adhesion (edge connectivity) and low cohesion + # (vertex connectivity) + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.remove_node(7) + for i in range(4,7): + G.add_edge(0,i) + G = nx.disjoint_union(G, nx.complete_graph(4)) + G.remove_node(G.order()-1) + for i in range(7,10): + G.add_edge(0,i) + assert_equal(1, nx.node_connectivity(G)) + assert_equal(3, nx.edge_connectivity(G)) + +def test_white_harary_2(): + # Figure 8 white and harary (2001) + # # http://eclectic.ss.uci.edu/~drwhite/sm-w23.PDF + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.add_edge(0,4) + # kappa <= lambda <= delta + assert_equal(3, min(nx.core_number(G).values())) + assert_equal(1, nx.node_connectivity(G)) + assert_equal(1, nx.edge_connectivity(G)) + +def test_complete_graphs(): + for n in range(5, 25, 5): + G = nx.complete_graph(n) + assert_equal(n-1, nx.node_connectivity(G)) + assert_equal(n-1, nx.node_connectivity(G.to_directed())) + assert_equal(n-1, nx.edge_connectivity(G)) + assert_equal(n-1, nx.edge_connectivity(G.to_directed())) + +def test_empty_graphs(): + for k in range(5, 25, 5): + G = nx.empty_graph(k) + assert_equal(0, nx.node_connectivity(G)) + assert_equal(0, nx.edge_connectivity(G)) + +def test_petersen(): + G = nx.petersen_graph() + assert_equal(3, nx.node_connectivity(G)) + assert_equal(3, nx.edge_connectivity(G)) + +def test_tutte(): + G = nx.tutte_graph() + assert_equal(3, nx.node_connectivity(G)) + assert_equal(3, nx.edge_connectivity(G)) + +def test_dodecahedral(): + G = nx.dodecahedral_graph() + assert_equal(3, nx.node_connectivity(G)) + assert_equal(3, nx.edge_connectivity(G)) + +def test_octahedral(): + G=nx.octahedral_graph() + assert_equal(4, nx.node_connectivity(G)) + assert_equal(4, nx.edge_connectivity(G)) + +def test_icosahedral(): + G=nx.icosahedral_graph() + assert_equal(5, nx.node_connectivity(G)) + assert_equal(5, nx.edge_connectivity(G)) + +def test_directed_edge_connectivity(): + G = nx.cycle_graph(10,create_using=nx.DiGraph()) # only one direction + D = nx.cycle_graph(10).to_directed() # 2 reciprocal edges + assert_equal(1, nx.edge_connectivity(G)) + assert_equal(1, nx.local_edge_connectivity(G,1,4)) + assert_equal(1, nx.edge_connectivity(G,1,4)) + assert_equal(2, nx.edge_connectivity(D)) + assert_equal(2, nx.local_edge_connectivity(D,1,4)) + assert_equal(2, nx.edge_connectivity(D,1,4)) + +def test_dominating_set(): + for i in range(5): + G = nx.gnp_random_graph(100,0.1) + D = nx.dominating_set(G) + assert_true(is_dominating_set(G,D)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_cuts.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_cuts.py new file mode 100644 index 0000000..0570494 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/connectivity/tests/test_cuts.py @@ -0,0 +1,157 @@ +from nose.tools import assert_equal, assert_true, assert_false, assert_raises +import networkx as nx + +# Tests for node and edge cutsets +def _generate_no_biconnected(max_attempts=50): + attempts = 0 + while True: + G = nx.fast_gnp_random_graph(100,0.0575) + if nx.is_connected(G) and not nx.is_biconnected(G): + attempts = 0 + yield G + else: + if attempts >= max_attempts: + msg = "Tried %d times: no suitable Graph."%attempts + raise Exception(msg % max_attempts) + else: + attempts += 1 + +def test_articulation_points(): + Ggen = _generate_no_biconnected() + for i in range(5): + G = next(Ggen) + cut = nx.minimum_node_cut(G) + assert_true(len(cut) == 1) + assert_true(cut.pop() in set(nx.articulation_points(G))) + +def test_brandes_erlebach_book(): + # Figure 1 chapter 7: Connectivity + # http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf + G = nx.Graph() + G.add_edges_from([(1,2),(1,3),(1,4),(1,5),(2,3),(2,6),(3,4), + (3,6),(4,6),(4,7),(5,7),(6,8),(6,9),(7,8), + (7,10),(8,11),(9,10),(9,11),(10,11)]) + # edge cutsets + assert_equal(3, len(nx.minimum_edge_cut(G,1,11))) + edge_cut = nx.minimum_edge_cut(G) + assert_equal(2, len(edge_cut)) # Node 5 has only two edges + H = G.copy() + H.remove_edges_from(edge_cut) + assert_false(nx.is_connected(H)) + # node cuts + assert_equal(set([6,7]), nx.minimum_st_node_cut(G,1,11)) + assert_equal(set([6,7]), nx.minimum_node_cut(G,1,11)) + node_cut = nx.minimum_node_cut(G) + assert_equal(2,len(node_cut)) + H = G.copy() + H.remove_nodes_from(node_cut) + assert_false(nx.is_connected(H)) + +def test_white_harary_paper(): + # Figure 1b white and harary (2001) + # http://eclectic.ss.uci.edu/~drwhite/sm-w23.PDF + # A graph with high adhesion (edge connectivity) and low cohesion + # (node connectivity) + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.remove_node(7) + for i in range(4,7): + G.add_edge(0,i) + G = nx.disjoint_union(G, nx.complete_graph(4)) + G.remove_node(G.order()-1) + for i in range(7,10): + G.add_edge(0,i) + # edge cuts + edge_cut = nx.minimum_edge_cut(G) + assert_equal(3, len(edge_cut)) + H = G.copy() + H.remove_edges_from(edge_cut) + assert_false(nx.is_connected(H)) + # node cuts + node_cut = nx.minimum_node_cut(G) + assert_equal(set([0]), node_cut) + H = G.copy() + H.remove_nodes_from(node_cut) + assert_false(nx.is_connected(H)) + +def test_petersen_cutset(): + G = nx.petersen_graph() + # edge cuts + edge_cut = nx.minimum_edge_cut(G) + assert_equal(3, len(edge_cut)) + H = G.copy() + H.remove_edges_from(edge_cut) + assert_false(nx.is_connected(H)) + # node cuts + node_cut = nx.minimum_node_cut(G) + assert_equal(3,len(node_cut)) + H = G.copy() + H.remove_nodes_from(node_cut) + assert_false(nx.is_connected(H)) + +def test_octahedral_cutset(): + G=nx.octahedral_graph() + # edge cuts + edge_cut = nx.minimum_edge_cut(G) + assert_equal(4, len(edge_cut)) + H = G.copy() + H.remove_edges_from(edge_cut) + assert_false(nx.is_connected(H)) + # node cuts + node_cut = nx.minimum_node_cut(G) + assert_equal(4,len(node_cut)) + H = G.copy() + H.remove_nodes_from(node_cut) + assert_false(nx.is_connected(H)) + +def test_icosahedral_cutset(): + G=nx.icosahedral_graph() + # edge cuts + edge_cut = nx.minimum_edge_cut(G) + assert_equal(5, len(edge_cut)) + H = G.copy() + H.remove_edges_from(edge_cut) + assert_false(nx.is_connected(H)) + # node cuts + node_cut = nx.minimum_node_cut(G) + assert_equal(5,len(node_cut)) + H = G.copy() + H.remove_nodes_from(node_cut) + assert_false(nx.is_connected(H)) + +def test_node_cutset_exception(): + G=nx.Graph() + G.add_edges_from([(1,2),(3,4)]) + assert_raises(nx.NetworkXError, nx.minimum_node_cut,G) + +def test_node_cutset_random_graphs(): + for i in range(5): + G = nx.fast_gnp_random_graph(50,0.2) + if not nx.is_connected(G): + ccs = iter(nx.connected_components(G)) + start = next(ccs)[0] + G.add_edges_from( (start,c[0]) for c in ccs ) + cutset = nx.minimum_node_cut(G) + assert_equal(nx.node_connectivity(G), len(cutset)) + G.remove_nodes_from(cutset) + assert_false(nx.is_connected(G)) + +def test_edge_cutset_random_graphs(): + for i in range(5): + G = nx.fast_gnp_random_graph(50,0.2) + if not nx.is_connected(G): + ccs = iter(nx.connected_components(G)) + start = next(ccs)[0] + G.add_edges_from( (start,c[0]) for c in ccs ) + cutset = nx.minimum_edge_cut(G) + assert_equal(nx.edge_connectivity(G), len(cutset)) + G.remove_edges_from(cutset) + assert_false(nx.is_connected(G)) + +# Test empty graphs +def test_empty_graphs(): + G = nx.Graph() + D = nx.DiGraph() + assert_raises(nx.NetworkXPointlessConcept, nx.minimum_node_cut, G) + assert_raises(nx.NetworkXPointlessConcept, nx.minimum_node_cut, D) + assert_raises(nx.NetworkXPointlessConcept, nx.minimum_edge_cut, G) + assert_raises(nx.NetworkXPointlessConcept, nx.minimum_edge_cut, D) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/core.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/core.py new file mode 100644 index 0000000..4daba25 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/core.py @@ -0,0 +1,324 @@ +""" +Find the k-cores of a graph. + +The k-core is found by recursively pruning nodes with degrees less than k. + +See the following reference for details: + +An O(m) Algorithm for Cores Decomposition of Networks +Vladimir Batagelj and Matjaz Zaversnik, 2003. +http://arxiv.org/abs/cs.DS/0310049 + +""" +__author__ = "\n".join(['Dan Schult (dschult@colgate.edu)', + 'Jason Grout (jason-sage@creativetrax.com)', + 'Aric Hagberg (hagberg@lanl.gov)']) + +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__all__ = ['core_number','k_core','k_shell','k_crust','k_corona','find_cores'] + +import networkx as nx + +def core_number(G): + """Return the core number for each vertex. + + A k-core is a maximal subgraph that contains nodes of degree k or more. + + The core number of a node is the largest value k of a k-core containing + that node. + + Parameters + ---------- + G : NetworkX graph + A graph or directed graph + + Returns + ------- + core_number : dictionary + A dictionary keyed by node to the core number. + + Raises + ------ + NetworkXError + The k-core is not defined for graphs with self loops or parallel edges. + + Notes + ----- + Not implemented for graphs with parallel edges or self loops. + + For directed graphs the node degree is defined to be the + in-degree + out-degree. + + References + ---------- + .. [1] An O(m) Algorithm for Cores Decomposition of Networks + Vladimir Batagelj and Matjaz Zaversnik, 2003. + http://arxiv.org/abs/cs.DS/0310049 + """ + if G.is_multigraph(): + raise nx.NetworkXError( + 'MultiGraph and MultiDiGraph types not supported.') + + if G.number_of_selfloops()>0: + raise nx.NetworkXError( + 'Input graph has self loops; the core number is not defined.', + 'Consider using G.remove_edges_from(G.selfloop_edges()).') + + if G.is_directed(): + import itertools + def neighbors(v): + return itertools.chain.from_iterable([G.predecessors_iter(v), + G.successors_iter(v)]) + else: + neighbors=G.neighbors_iter + degrees=G.degree() + # sort nodes by degree + nodes=sorted(degrees,key=degrees.get) + bin_boundaries=[0] + curr_degree=0 + for i,v in enumerate(nodes): + if degrees[v]>curr_degree: + bin_boundaries.extend([i]*(degrees[v]-curr_degree)) + curr_degree=degrees[v] + node_pos = dict((v,pos) for pos,v in enumerate(nodes)) + # initial guesses for core is degree + core=degrees + nbrs=dict((v,set(neighbors(v))) for v in G) + for v in nodes: + for u in nbrs[v]: + if core[u] > core[v]: + nbrs[u].remove(v) + pos=node_pos[u] + bin_start=bin_boundaries[core[u]] + node_pos[u]=bin_start + node_pos[nodes[bin_start]]=pos + nodes[bin_start],nodes[pos]=nodes[pos],nodes[bin_start] + bin_boundaries[core[u]]+=1 + core[u]-=1 + return core + +find_cores=core_number + +def k_core(G,k=None,core_number=None): + """Return the k-core of G. + + A k-core is a maximal subgraph that contains nodes of degree k or more. + + Parameters + ---------- + G : NetworkX graph + A graph or directed graph + k : int, optional + The order of the core. If not specified return the main core. + core_number : dictionary, optional + Precomputed core numbers for the graph G. + + Returns + ------- + G : NetworkX graph + The k-core subgraph + + Raises + ------ + NetworkXError + The k-core is not defined for graphs with self loops or parallel edges. + + Notes + ----- + The main core is the core with the largest degree. + + Not implemented for graphs with parallel edges or self loops. + + For directed graphs the node degree is defined to be the + in-degree + out-degree. + + Graph, node, and edge attributes are copied to the subgraph. + + See Also + -------- + core_number + + References + ---------- + .. [1] An O(m) Algorithm for Cores Decomposition of Networks + Vladimir Batagelj and Matjaz Zaversnik, 2003. + http://arxiv.org/abs/cs.DS/0310049 + """ + if core_number is None: + core_number=nx.core_number(G) + if k is None: + k=max(core_number.values()) # max core + nodes=(n for n in core_number if core_number[n]>=k) + return G.subgraph(nodes).copy() + +def k_shell(G,k=None,core_number=None): + """Return the k-shell of G. + + The k-shell is the subgraph of nodes in the k-core containing + nodes of exactly degree k. + + Parameters + ---------- + G : NetworkX graph + A graph or directed graph. + k : int, optional + The order of the shell. If not specified return the main shell. + core_number : dictionary, optional + Precomputed core numbers for the graph G. + + + Returns + ------- + G : NetworkX graph + The k-shell subgraph + + Raises + ------ + NetworkXError + The k-shell is not defined for graphs with self loops or parallel edges. + + Notes + ----- + This is similar to k_corona but in that case only neighbors in the + k-core are considered. + + Not implemented for graphs with parallel edges or self loops. + + For directed graphs the node degree is defined to be the + in-degree + out-degree. + + Graph, node, and edge attributes are copied to the subgraph. + + See Also + -------- + core_number + k_corona + + + ---------- + .. [1] A model of Internet topology using k-shell decomposition + Shai Carmi, Shlomo Havlin, Scott Kirkpatrick, Yuval Shavitt, + and Eran Shir, PNAS July 3, 2007 vol. 104 no. 27 11150-11154 + http://www.pnas.org/content/104/27/11150.full + """ + if core_number is None: + core_number=nx.core_number(G) + if k is None: + k=max(core_number.values()) # max core + nodes=(n for n in core_number if core_number[n]==k) + return G.subgraph(nodes).copy() + +def k_crust(G,k=None,core_number=None): + """Return the k-crust of G. + + The k-crust is the graph G with the k-core removed. + + Parameters + ---------- + G : NetworkX graph + A graph or directed graph. + k : int, optional + The order of the shell. If not specified return the main crust. + core_number : dictionary, optional + Precomputed core numbers for the graph G. + + Returns + ------- + G : NetworkX graph + The k-crust subgraph + + Raises + ------ + NetworkXError + The k-crust is not defined for graphs with self loops or parallel edges. + + Notes + ----- + This definition of k-crust is different than the definition in [1]_. + The k-crust in [1]_ is equivalent to the k+1 crust of this algorithm. + + Not implemented for graphs with parallel edges or self loops. + + For directed graphs the node degree is defined to be the + in-degree + out-degree. + + Graph, node, and edge attributes are copied to the subgraph. + + See Also + -------- + core_number + + References + ---------- + .. [1] A model of Internet topology using k-shell decomposition + Shai Carmi, Shlomo Havlin, Scott Kirkpatrick, Yuval Shavitt, + and Eran Shir, PNAS July 3, 2007 vol. 104 no. 27 11150-11154 + http://www.pnas.org/content/104/27/11150.full + """ + if core_number is None: + core_number=nx.core_number(G) + if k is None: + k=max(core_number.values())-1 + nodes=(n for n in core_number if core_number[n]<=k) + return G.subgraph(nodes).copy() + + +def k_corona(G, k, core_number=None): + """Return the k-crust of G. + + The k-corona is the subset of vertices in the k-core which have + exactly k neighbours in the k-core. + + Parameters + ---------- + G : NetworkX graph + A graph or directed graph + k : int + The order of the corona. + core_number : dictionary, optional + Precomputed core numbers for the graph G. + + Returns + ------- + G : NetworkX graph + The k-corona subgraph + + Raises + ------ + NetworkXError + The k-cornoa is not defined for graphs with self loops or + parallel edges. + + Notes + ----- + Not implemented for graphs with parallel edges or self loops. + + For directed graphs the node degree is defined to be the + in-degree + out-degree. + + Graph, node, and edge attributes are copied to the subgraph. + + See Also + -------- + core_number + + References + ---------- + .. [1] k -core (bootstrap) percolation on complex networks: + Critical phenomena and nonlocal effects, + A. V. Goltsev, S. N. Dorogovtsev, and J. F. F. Mendes, + Phys. Rev. E 73, 056101 (2006) + http://link.aps.org/doi/10.1103/PhysRevE.73.056101 + """ + + if core_number is None: + core_number = nx.core_number(G) + nodes = (n for n in core_number + if core_number[n] >= k + and len([v for v in G[n] if core_number[v] >= k]) == k) + return G.subgraph(nodes).copy() diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cycles.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cycles.py new file mode 100644 index 0000000..4955538 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/cycles.py @@ -0,0 +1,317 @@ +""" +======================== +Cycle finding algorithms +======================== +""" +# Copyright (C) 2010-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import * +from collections import defaultdict + +__all__ = ['cycle_basis','simple_cycles','recursive_simple_cycles'] +__author__ = "\n".join(['Jon Olav Vik ', + 'Dan Schult ', + 'Aric Hagberg ']) + +@not_implemented_for('directed') +@not_implemented_for('multigraph') +def cycle_basis(G,root=None): + """ Returns a list of cycles which form a basis for cycles of G. + + A basis for cycles of a network is a minimal collection of + cycles such that any cycle in the network can be written + as a sum of cycles in the basis. Here summation of cycles + is defined as "exclusive or" of the edges. Cycle bases are + useful, e.g. when deriving equations for electric circuits + using Kirchhoff's Laws. + + Parameters + ---------- + G : NetworkX Graph + root : node, optional + Specify starting node for basis. + + Returns + ------- + A list of cycle lists. Each cycle list is a list of nodes + which forms a cycle (loop) in G. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_cycle([0,1,2,3]) + >>> G.add_cycle([0,3,4,5]) + >>> print(nx.cycle_basis(G,0)) + [[3, 4, 5, 0], [1, 2, 3, 0]] + + Notes + ----- + This is adapted from algorithm CACM 491 [1]_. + + References + ---------- + .. [1] Paton, K. An algorithm for finding a fundamental set of + cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. + + See Also + -------- + simple_cycles + """ + gnodes=set(G.nodes()) + cycles=[] + while gnodes: # loop over connected components + if root is None: + root=gnodes.pop() + stack=[root] + pred={root:root} + used={root:set()} + while stack: # walk the spanning tree finding cycles + z=stack.pop() # use last-in so cycles easier to find + zused=used[z] + for nbr in G[z]: + if nbr not in used: # new node + pred[nbr]=z + stack.append(nbr) + used[nbr]=set([z]) + elif nbr == z: # self loops + cycles.append([z]) + elif nbr not in zused:# found a cycle + pn=used[nbr] + cycle=[nbr,z] + p=pred[z] + while p not in pn: + cycle.append(p) + p=pred[p] + cycle.append(p) + cycles.append(cycle) + used[nbr].add(z) + gnodes-=set(pred) + root=None + return cycles + + +@not_implemented_for('undirected') +def simple_cycles(G): + """Find simple cycles (elementary circuits) of a directed graph. + + An simple cycle, or elementary circuit, is a closed path where no + node appears twice, except that the first and last node are the same. + Two elementary circuits are distinct if they are not cyclic permutations + of each other. + + This is a nonrecursive, iterator/generator version of Johnson's + algorithm [1]_. There may be better algorithms for some cases [2]_ [3]_. + + Parameters + ---------- + G : NetworkX DiGraph + A directed graph + + Returns + ------- + cycle_generator: generator + A generator that produces elementary cycles of the graph. Each cycle is + a list of nodes with the first and last nodes being the same. + + Examples + -------- + >>> G = nx.DiGraph([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]) + >>> list(nx.simple_cycles(G)) + [[2], [2, 1], [2, 0], [2, 0, 1], [0]] + + Notes + ----- + The implementation follows pp. 79-80 in [1]_. + + The time complexity is O((n+e)(c+1)) for n nodes, e edges and c + elementary circuits. + + To filter the cycles so that they don't include certain nodes or edges, + copy your graph and eliminate those nodes or edges before calling. + >>> copyG = G.copy() + >>> copyG.remove_nodes_from([1]) + >>> copyG.remove_edges_from([(0,1)]) + >>> list(nx.simple_cycles(copyG)) + [[2], [2, 0], [0]] + + References + ---------- + .. [1] Finding all the elementary circuits of a directed graph. + D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975. + http://dx.doi.org/10.1137/0204007 + + .. [2] Enumerating the cycles of a digraph: a new preprocessing strategy. + G. Loizou and P. Thanish, Information Sciences, v. 27, 163-182, 1982. + + .. [3] A search strategy for the elementary cycles of a directed graph. + J.L. Szwarcfiter and P.E. Lauer, BIT NUMERICAL MATHEMATICS, + v. 16, no. 2, 192-204, 1976. + + See Also + -------- + cycle_basis + """ + def _unblock(thisnode,blocked,B): + stack=set([thisnode]) + while stack: + node=stack.pop() + if node in blocked: + blocked.remove(node) + stack.update(B[node]) + B[node].clear() + + # Johnson's algorithm requires some ordering of the nodes. + # We assign the arbitrary ordering given by the strongly connected comps + # There is no need to track the ordering as each node removed as processed. + subG=G.copy() # save the actual graph so we can mutate it here + sccs = nx.strongly_connected_components(subG) + while sccs: + scc=sccs.pop() + # order of scc determines ordering of nodes + startnode = scc.pop() + # Processing node runs "circuit" routine from recursive version + path=[startnode] + blocked = set() # vertex: blocked from search? + closed = set() # nodes involved in a cycle + blocked.add(startnode) + B=defaultdict(set) # graph portions that yield no elementary circuit + stack=[ (startnode,list(subG[startnode])) ] # subG gives component nbrs + while stack: + thisnode,nbrs = stack[-1] + if nbrs: + nextnode = nbrs.pop() +# print thisnode,nbrs,":",nextnode,blocked,B,path,stack,startnode +# f=raw_input("pause") + if nextnode == startnode: + yield path[:] + closed.update(path) +# print "Found a cycle",path,closed + elif nextnode not in blocked: + path.append(nextnode) + stack.append( (nextnode,list(subG[nextnode])) ) + blocked.add(nextnode) + continue + # done with nextnode... look for more neighbors + if not nbrs: # no more nbrs + if thisnode in closed: + _unblock(thisnode,blocked,B) + else: + for nbr in G[thisnode]: + if thisnode not in B[nbr]: + B[nbr].add(thisnode) + stack.pop() +# assert path[-1]==thisnode + path.pop() + # done processing this node + subG.remove_node(startnode) + H=subG.subgraph(scc) # make smaller to avoid work in SCC routine + sccs.extend(nx.strongly_connected_components(H)) + + +@not_implemented_for('undirected') +def recursive_simple_cycles(G): + """Find simple cycles (elementary circuits) of a directed graph. + + A simple cycle, or elementary circuit, is a closed path where no + node appears twice, except that the first and last node are the same. + Two elementary circuits are distinct if they are not cyclic permutations + of each other. + + This version uses a recursive algorithm to build a list of cycles. + You should probably use the iterator version caled simple_cycles(). + Warning: This recursive version uses lots of RAM! + + Parameters + ---------- + G : NetworkX DiGraph + A directed graph + + Returns + ------- + A list of circuits, where each circuit is a list of nodes, with the first + and last node being the same. + + Example: + >>> G = nx.DiGraph([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]) + >>> nx.recursive_simple_cycles(G) + [[0], [0, 1, 2], [0, 2], [1, 2], [2]] + + See Also + -------- + cycle_basis (for undirected graphs) + + Notes + ----- + The implementation follows pp. 79-80 in [1]_. + + The time complexity is O((n+e)(c+1)) for n nodes, e edges and c + elementary circuits. + + References + ---------- + .. [1] Finding all the elementary circuits of a directed graph. + D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975. + http://dx.doi.org/10.1137/0204007 + + See Also + -------- + simple_cycles, cycle_basis + """ + # Jon Olav Vik, 2010-08-09 + def _unblock(thisnode): + """Recursively unblock and remove nodes from B[thisnode].""" + if blocked[thisnode]: + blocked[thisnode] = False + while B[thisnode]: + _unblock(B[thisnode].pop()) + + def circuit(thisnode, startnode, component): + closed = False # set to True if elementary path is closed + path.append(thisnode) + blocked[thisnode] = True + for nextnode in component[thisnode]: # direct successors of thisnode + if nextnode == startnode: + result.append(path[:]) + closed = True + elif not blocked[nextnode]: + if circuit(nextnode, startnode, component): + closed = True + if closed: + _unblock(thisnode) + else: + for nextnode in component[thisnode]: + if thisnode not in B[nextnode]: # TODO: use set for speedup? + B[nextnode].append(thisnode) + path.pop() # remove thisnode from path + return closed + + path = [] # stack of nodes in current path + blocked = defaultdict(bool) # vertex: blocked from search? + B = defaultdict(list) # graph portions that yield no elementary circuit + result = [] # list to accumulate the circuits found + # Johnson's algorithm requires some ordering of the nodes. + # They might not be sortable so we assign an arbitrary ordering. + ordering=dict(zip(G,range(len(G)))) + for s in ordering: + # Build the subgraph induced by s and following nodes in the ordering + subgraph = G.subgraph(node for node in G + if ordering[node] >= ordering[s]) + # Find the strongly connected component in the subgraph + # that contains the least node according to the ordering + strongcomp = nx.strongly_connected_components(subgraph) + mincomp=min(strongcomp, + key=lambda nodes: min(ordering[n] for n in nodes)) + component = G.subgraph(mincomp) + if component: + # smallest node in the component according to the ordering + startnode = min(component,key=ordering.__getitem__) + for node in component: + blocked[node] = False + B[node][:] = [] + dummy=circuit(startnode, startnode, component) + return result diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/dag.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/dag.py new file mode 100644 index 0000000..4d376e0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/dag.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 -*- +from fractions import gcd +import networkx as nx +"""Algorithms for directed acyclic graphs (DAGs).""" +# Copyright (C) 2006-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """\n""".join(['Aric Hagberg ', + 'Dan Schult (dschult@colgate.edu)', + 'Ben Edwards (bedwards@cs.unm.edu)']) +__all__ = ['descendants', + 'ancestors', + 'topological_sort', + 'topological_sort_recursive', + 'is_directed_acyclic_graph', + 'is_aperiodic'] + +def descendants(G, source): + """Return all nodes reachable from `source` in G. + + Parameters + ---------- + G : NetworkX DiGraph + source : node in G + + Returns + ------- + des : set() + The descendants of source in G + """ + if not G.has_node(source): + raise nx.NetworkXError("The node %s is not in the graph." % source) + des = set(nx.shortest_path_length(G, source=source).keys()) - set([source]) + return des + +def ancestors(G, source): + """Return all nodes having a path to `source` in G. + + Parameters + ---------- + G : NetworkX DiGraph + source : node in G + + Returns + ------- + ancestors : set() + The ancestors of source in G + """ + if not G.has_node(source): + raise nx.NetworkXError("The node %s is not in the graph." % source) + anc = set(nx.shortest_path_length(G, target=source).keys()) - set([source]) + return anc + +def is_directed_acyclic_graph(G): + """Return True if the graph G is a directed acyclic graph (DAG) or + False if not. + + Parameters + ---------- + G : NetworkX graph + A graph + + Returns + ------- + is_dag : bool + True if G is a DAG, false otherwise + """ + if not G.is_directed(): + return False + try: + topological_sort(G) + return True + except nx.NetworkXUnfeasible: + return False + +def topological_sort(G,nbunch=None): + """Return a list of nodes in topological sort order. + + A topological sort is a nonunique permutation of the nodes + such that an edge from u to v implies that u appears before v in the + topological sort order. + + Parameters + ---------- + G : NetworkX digraph + A directed graph + + nbunch : container of nodes (optional) + Explore graph in specified order given in nbunch + + Raises + ------ + NetworkXError + Topological sort is defined for directed graphs only. If the + graph G is undirected, a NetworkXError is raised. + + NetworkXUnfeasible + If G is not a directed acyclic graph (DAG) no topological sort + exists and a NetworkXUnfeasible exception is raised. + + Notes + ----- + This algorithm is based on a description and proof in + The Algorithm Design Manual [1]_ . + + See also + -------- + is_directed_acyclic_graph + + References + ---------- + .. [1] Skiena, S. S. The Algorithm Design Manual (Springer-Verlag, 1998). + http://www.amazon.com/exec/obidos/ASIN/0387948600/ref=ase_thealgorithmrepo/ + """ + if not G.is_directed(): + raise nx.NetworkXError( + "Topological sort not defined on undirected graphs.") + + # nonrecursive version + seen = set() + order = [] + explored = set() + + if nbunch is None: + nbunch = G.nodes_iter() + for v in nbunch: # process all vertices in G + if v in explored: + continue + fringe = [v] # nodes yet to look at + while fringe: + w = fringe[-1] # depth first search + if w in explored: # already looked down this branch + fringe.pop() + continue + seen.add(w) # mark as seen + # Check successors for cycles and for new nodes + new_nodes = [] + for n in G[w]: + if n not in explored: + if n in seen: #CYCLE !! + raise nx.NetworkXUnfeasible("Graph contains a cycle.") + new_nodes.append(n) + if new_nodes: # Add new_nodes to fringe + fringe.extend(new_nodes) + else: # No new nodes so w is fully explored + explored.add(w) + order.append(w) + fringe.pop() # done considering this node + return list(reversed(order)) + +def topological_sort_recursive(G,nbunch=None): + """Return a list of nodes in topological sort order. + + A topological sort is a nonunique permutation of the nodes such + that an edge from u to v implies that u appears before v in the + topological sort order. + + Parameters + ---------- + G : NetworkX digraph + + nbunch : container of nodes (optional) + Explore graph in specified order given in nbunch + + Raises + ------ + NetworkXError + Topological sort is defined for directed graphs only. If the + graph G is undirected, a NetworkXError is raised. + + NetworkXUnfeasible + If G is not a directed acyclic graph (DAG) no topological sort + exists and a NetworkXUnfeasible exception is raised. + + Notes + ----- + This is a recursive version of topological sort. + + See also + -------- + topological_sort + is_directed_acyclic_graph + + """ + if not G.is_directed(): + raise nx.NetworkXError( + "Topological sort not defined on undirected graphs.") + + def _dfs(v): + ancestors.add(v) + + for w in G[v]: + if w in ancestors: + raise nx.NetworkXUnfeasible("Graph contains a cycle.") + + if w not in explored: + _dfs(w) + + ancestors.remove(v) + explored.add(v) + order.append(v) + + ancestors = set() + explored = set() + order = [] + + if nbunch is None: + nbunch = G.nodes_iter() + + for v in nbunch: + if v not in explored: + _dfs(v) + + return list(reversed(order)) + +def is_aperiodic(G): + """Return True if G is aperiodic. + + A directed graph is aperiodic if there is no integer k > 1 that + divides the length of every cycle in the graph. + + Parameters + ---------- + G : NetworkX DiGraph + Graph + + Returns + ------- + aperiodic : boolean + True if the graph is aperiodic False otherwise + + Raises + ------ + NetworkXError + If G is not directed + + Notes + ----- + This uses the method outlined in [1]_, which runs in O(m) time + given m edges in G. Note that a graph is not aperiodic if it is + acyclic as every integer trivial divides length 0 cycles. + + References + ---------- + .. [1] Jarvis, J. P.; Shier, D. R. (1996), + Graph-theoretic analysis of finite Markov chains, + in Shier, D. R.; Wallenius, K. T., Applied Mathematical Modeling: + A Multidisciplinary Approach, CRC Press. + """ + if not G.is_directed(): + raise nx.NetworkXError("is_aperiodic not defined for undirected graphs") + + s = next(G.nodes_iter()) + levels = {s:0} + this_level = [s] + g = 0 + l = 1 + while this_level: + next_level = [] + for u in this_level: + for v in G[u]: + if v in levels: # Non-Tree Edge + g = gcd(g, levels[u]-levels[v] + 1) + else: # Tree Edge + next_level.append(v) + levels[v] = l + this_level = next_level + l += 1 + if len(levels)==len(G): #All nodes in tree + return g==1 + else: + return g==1 and nx.is_aperiodic(G.subgraph(set(G)-set(levels))) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_measures.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_measures.py new file mode 100644 index 0000000..33df686 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_measures.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +""" +Graph diameter, radius, eccentricity and other properties. +""" +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['eccentricity', 'diameter', 'radius', 'periphery', 'center'] + +import networkx + +def eccentricity(G, v=None, sp=None): + """Return the eccentricity of nodes in G. + + The eccentricity of a node v is the maximum distance from v to + all other nodes in G. + + Parameters + ---------- + G : NetworkX graph + A graph + + v : node, optional + Return value of specified node + + sp : dict of dicts, optional + All pairs shortest path lengths as a dictionary of dictionaries + + Returns + ------- + ecc : dictionary + A dictionary of eccentricity values keyed by node. + """ +# nodes= +# nodes=[] +# if v is None: # none, use entire graph +# nodes=G.nodes() +# elif v in G: # is v a single node +# nodes=[v] +# else: # assume v is a container of nodes +# nodes=v + order=G.order() + + e={} + for n in G.nbunch_iter(v): + if sp is None: + length=networkx.single_source_shortest_path_length(G,n) + L = len(length) + else: + try: + length=sp[n] + L = len(length) + except TypeError: + raise networkx.NetworkXError('Format of "sp" is invalid.') + if L != order: + msg = "Graph not connected: infinite path length" + raise networkx.NetworkXError(msg) + + e[n]=max(length.values()) + + if v in G: + return e[v] # return single value + else: + return e + + +def diameter(G, e=None): + """Return the diameter of the graph G. + + The diameter is the maximum eccentricity. + + Parameters + ---------- + G : NetworkX graph + A graph + + e : eccentricity dictionary, optional + A precomputed dictionary of eccentricities. + + Returns + ------- + d : integer + Diameter of graph + + See Also + -------- + eccentricity + """ + if e is None: + e=eccentricity(G) + return max(e.values()) + +def periphery(G, e=None): + """Return the periphery of the graph G. + + The periphery is the set of nodes with eccentricity equal to the diameter. + + Parameters + ---------- + G : NetworkX graph + A graph + + e : eccentricity dictionary, optional + A precomputed dictionary of eccentricities. + + Returns + ------- + p : list + List of nodes in periphery + """ + if e is None: + e=eccentricity(G) + diameter=max(e.values()) + p=[v for v in e if e[v]==diameter] + return p + + +def radius(G, e=None): + """Return the radius of the graph G. + + The radius is the minimum eccentricity. + + Parameters + ---------- + G : NetworkX graph + A graph + + e : eccentricity dictionary, optional + A precomputed dictionary of eccentricities. + + Returns + ------- + r : integer + Radius of graph + """ + if e is None: + e=eccentricity(G) + return min(e.values()) + +def center(G, e=None): + """Return the center of the graph G. + + The center is the set of nodes with eccentricity equal to radius. + + Parameters + ---------- + G : NetworkX graph + A graph + + e : eccentricity dictionary, optional + A precomputed dictionary of eccentricities. + + Returns + ------- + c : list + List of nodes in center + """ + if e is None: + e=eccentricity(G) + # order the nodes by path length + radius=min(e.values()) + p=[v for v in e if e[v]==radius] + return p + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_regular.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_regular.py new file mode 100644 index 0000000..3fbcdbc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/distance_regular.py @@ -0,0 +1,179 @@ +""" +======================= +Distance-regular graphs +======================= +""" +# Copyright (C) 2011 by +# Dheeraj M R +# Aric Hagberg +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Dheeraj M R ', + 'Aric Hagberg ']) + +__all__ = ['is_distance_regular','intersection_array','global_parameters'] + +def is_distance_regular(G): + """Returns True if the graph is distance regular, False otherwise. + + A connected graph G is distance-regular if for any nodes x,y + and any integers i,j=0,1,...,d (where d is the graph + diameter), the number of vertices at distance i from x and + distance j from y depends only on i,j and the graph distance + between x and y, independently of the choice of x and y. + + Parameters + ---------- + G: Networkx graph (undirected) + + Returns + ------- + bool + True if the graph is Distance Regular, False otherwise + + Examples + -------- + >>> G=nx.hypercube_graph(6) + >>> nx.is_distance_regular(G) + True + + See Also + -------- + intersection_array, global_parameters + + Notes + ----- + For undirected and simple graphs only + + References + ---------- + .. [1] Brouwer, A. E.; Cohen, A. M.; and Neumaier, A. + Distance-Regular Graphs. New York: Springer-Verlag, 1989. + .. [2] Weisstein, Eric W. "Distance-Regular Graph." + http://mathworld.wolfram.com/Distance-RegularGraph.html + + """ + try: + a=intersection_array(G) + return True + except nx.NetworkXError: + return False + +def global_parameters(b,c): + """Return global parameters for a given intersection array. + + Given a distance-regular graph G with integers b_i, c_i,i = 0,....,d + such that for any 2 vertices x,y in G at a distance i=d(x,y), there + are exactly c_i neighbors of y at a distance of i-1 from x and b_i + neighbors of y at a distance of i+1 from x. + + Thus, a distance regular graph has the global parameters, + [[c_0,a_0,b_0],[c_1,a_1,b_1],......,[c_d,a_d,b_d]] for the + intersection array [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d] + where a_i+b_i+c_i=k , k= degree of every vertex. + + Parameters + ---------- + b,c: tuple of lists + + Returns + ------- + p : list of three-tuples + + Examples + -------- + >>> G=nx.dodecahedral_graph() + >>> b,c=nx.intersection_array(G) + >>> list(nx.global_parameters(b,c)) + [(0, 0, 3), (1, 0, 2), (1, 1, 1), (1, 1, 1), (2, 0, 1), (3, 0, 0)] + + References + ---------- + .. [1] Weisstein, Eric W. "Global Parameters." + From MathWorld--A Wolfram Web Resource. + http://mathworld.wolfram.com/GlobalParameters.html + + See Also + -------- + intersection_array + """ + d=len(b) + ba=b[:] + ca=c[:] + ba.append(0) + ca.insert(0,0) + k = ba[0] + aa = [k-x-y for x,y in zip(ba,ca)] + return zip(*[ca,aa,ba]) + + +def intersection_array(G): + """Returns the intersection array of a distance-regular graph. + + Given a distance-regular graph G with integers b_i, c_i,i = 0,....,d + such that for any 2 vertices x,y in G at a distance i=d(x,y), there + are exactly c_i neighbors of y at a distance of i-1 from x and b_i + neighbors of y at a distance of i+1 from x. + + A distance regular graph'sintersection array is given by, + [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d] + + Parameters + ---------- + G: Networkx graph (undirected) + + Returns + ------- + b,c: tuple of lists + + Examples + -------- + >>> G=nx.icosahedral_graph() + >>> nx.intersection_array(G) + ([5, 2, 1], [1, 2, 5]) + + References + ---------- + .. [1] Weisstein, Eric W. "Intersection Array." + From MathWorld--A Wolfram Web Resource. + http://mathworld.wolfram.com/IntersectionArray.html + + + See Also + -------- + global_parameters + """ + if G.is_multigraph() or G.is_directed(): + raise nx.NetworkxException('Not implemented for directed ', + 'or multiedge graphs.') + # test for regular graph (all degrees must be equal) + degree = G.degree_iter() + (_,k) = next(degree) + for _,knext in degree: + if knext != k: + raise nx.NetworkXError('Graph is not distance regular.') + k = knext + path_length = nx.all_pairs_shortest_path_length(G) + diameter = max([max(path_length[n].values()) for n in path_length]) + bint = {} # 'b' intersection array + cint = {} # 'c' intersection array + for u in G: + for v in G: + try: + i = path_length[u][v] + except KeyError: # graph must be connected + raise nx.NetworkXError('Graph is not distance regular.') + # number of neighbors of v at a distance of i-1 from u + c = len([n for n in G[v] if path_length[n][u]==i-1]) + # number of neighbors of v at a distance of i+1 from u + b = len([n for n in G[v] if path_length[n][u]==i+1]) + # b,c are independent of u and v + if cint.get(i,c) != c or bint.get(i,b) != b: + raise nx.NetworkXError('Graph is not distance regular') + bint[i] = b + cint[i] = c + return ([bint.get(i,0) for i in range(diameter)], + [cint.get(i+1,0) for i in range(diameter)]) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/euler.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/euler.py new file mode 100644 index 0000000..4e834c7 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/euler.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +""" +Eulerian circuits and graphs. +""" +import networkx as nx + +__author__ = """\n""".join(['Nima Mohammadi (nima.irt[AT]gmail.com)', + 'Aric Hagberg ']) +# Copyright (C) 2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['is_eulerian', 'eulerian_circuit'] + +def is_eulerian(G): + """Return True if G is an Eulerian graph, False otherwise. + + An Eulerian graph is a graph with an Eulerian circuit. + + Parameters + ---------- + G : graph + A NetworkX Graph + + Examples + -------- + >>> nx.is_eulerian(nx.DiGraph({0:[3], 1:[2], 2:[3], 3:[0, 1]})) + True + >>> nx.is_eulerian(nx.complete_graph(5)) + True + >>> nx.is_eulerian(nx.petersen_graph()) + False + + Notes + ----- + This implementation requires the graph to be connected + (or strongly connected for directed graphs). + """ + if G.is_directed(): + # Every node must have equal in degree and out degree + for n in G.nodes_iter(): + if G.in_degree(n) != G.out_degree(n): + return False + # Must be strongly connected + if not nx.is_strongly_connected(G): + return False + else: + # An undirected Eulerian graph has no vertices of odd degrees + for v,d in G.degree_iter(): + if d % 2 != 0: + return False + # Must be connected + if not nx.is_connected(G): + return False + return True + + +def eulerian_circuit(G, source=None): + """Return the edges of an Eulerian circuit in G. + + An Eulerian circuit is a path that crosses every edge in G exactly once + and finishes at the starting node. + + Parameters + ---------- + G : graph + A NetworkX Graph + source : node, optional + Starting node for circuit. + + Returns + ------- + edges : generator + A generator that produces edges in the Eulerian circuit. + + Raises + ------ + NetworkXError + If the graph is not Eulerian. + + See Also + -------- + is_eulerian + + Notes + ----- + Uses Fleury's algorithm [1]_,[2]_ + + References + ---------- + .. [1] Fleury, "Deux problemes de geometrie de situation", + Journal de mathematiques elementaires (1883), 257-261. + .. [2] http://en.wikipedia.org/wiki/Eulerian_path + + Examples + -------- + >>> G=nx.complete_graph(3) + >>> list(nx.eulerian_circuit(G)) + [(0, 1), (1, 2), (2, 0)] + >>> list(nx.eulerian_circuit(G,source=1)) + [(1, 0), (0, 2), (2, 1)] + >>> [u for u,v in nx.eulerian_circuit(G)] # nodes in circuit + [0, 1, 2] + """ + if not is_eulerian(G): + raise nx.NetworkXError("G is not Eulerian.") + + g = G.__class__(G) # copy graph structure (not attributes) + + # set starting node + if source is None: + v = next(g.nodes_iter()) + else: + v = source + + while g.size() > 0: + n = v + # sort nbrs here to provide stable ordering of alternate cycles + nbrs = sorted([v for u,v in g.edges(n)]) + for v in nbrs: + g.remove_edge(n,v) + bridge = not nx.is_connected(g.to_undirected()) + if bridge: + g.add_edge(n,v) # add this edge back and try another + else: + break # this edge is good, break the for loop + if bridge: + g.remove_edge(n,v) + g.remove_node(n) + yield (n,v) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/__init__.py new file mode 100644 index 0000000..438ab5f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/__init__.py @@ -0,0 +1,3 @@ +from networkx.algorithms.flow.maxflow import * +from networkx.algorithms.flow.mincost import * + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/maxflow.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/maxflow.py new file mode 100644 index 0000000..ee71528 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/maxflow.py @@ -0,0 +1,477 @@ +# -*- coding: utf-8 -*- +""" +Maximum flow (and minimum cut) algorithms on capacitated graphs. +""" + +__author__ = """Loïc Séguin-C. """ +# Copyright (C) 2010 Loïc Séguin-C. +# All rights reserved. +# BSD license. + +import networkx as nx + +__all__ = ['ford_fulkerson', + 'ford_fulkerson_flow', + 'ford_fulkerson_flow_and_auxiliary', + 'max_flow', + 'min_cut'] + +def ford_fulkerson_flow_and_auxiliary(G, s, t, capacity='capacity'): + """Find a maximum single-commodity flow using the Ford-Fulkerson + algorithm. + + This function returns both the value of the maximum flow and the + auxiliary network resulting after finding the maximum flow, which + is also named residual network in the literature. The + auxiliary network has edges with capacity equal to the capacity + of the edge in the original network minus the flow that went + throught that edge. Notice that it can happen that a flow + from v to u is allowed in the auxiliary network, though disallowed + in the original network. A dictionary with infinite capacity edges + can be found as an attribute of the auxiliary network. + + Parameters + ---------- + G : NetworkX graph + Edges of the graph are expected to have an attribute called + 'capacity'. If this attribute is not present, the edge is + considered to have infinite capacity. + + s : node + Source node for the flow. + + t : node + Sink node for the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + Returns + ------- + flow_value : integer, float + Value of the maximum flow, i.e., net outflow from the source. + + auxiliary : DiGraph + Residual/auxiliary network after finding the maximum flow. + A dictionary with infinite capacity edges can be found as + an attribute of this network: auxiliary.graph['inf_capacity_flows'] + + Raises + ------ + NetworkXError + The algorithm does not support MultiGraph and MultiDiGraph. If + the input graph is an instance of one of these two classes, a + NetworkXError is raised. + + NetworkXUnbounded + If the graph has a path of infinite capacity, the value of a + feasible flow on the graph is unbounded above and the function + raises a NetworkXUnbounded. + + Notes + ----- + This algorithm uses Edmonds-Karp-Dinitz path selection rule which + guarantees a running time of `O(nm^2)` for `n` nodes and `m` edges. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_edge('x','a', capacity=3.0) + >>> G.add_edge('x','b', capacity=1.0) + >>> G.add_edge('a','c', capacity=3.0) + >>> G.add_edge('b','c', capacity=5.0) + >>> G.add_edge('b','d', capacity=4.0) + >>> G.add_edge('d','e', capacity=2.0) + >>> G.add_edge('c','y', capacity=2.0) + >>> G.add_edge('e','y', capacity=3.0) + >>> flow, auxiliary = nx.ford_fulkerson_flow_and_auxiliary(G, 'x', 'y') + >>> flow + 3.0 + >>> # A dictionary with infinite capacity flows can be found as an + >>> # attribute of the auxiliary network + >>> inf_capacity_flows = auxiliary.graph['inf_capacity_flows'] + + """ + if G.is_multigraph(): + raise nx.NetworkXError( + 'MultiGraph and MultiDiGraph not supported (yet).') + + if s not in G: + raise nx.NetworkXError('node %s not in graph' % str(s)) + if t not in G: + raise nx.NetworkXError('node %s not in graph' % str(t)) + + auxiliary = _create_auxiliary_digraph(G, capacity=capacity) + inf_capacity_flows = auxiliary.graph['inf_capacity_flows'] + + flow_value = 0 # Initial feasible flow. + + # As long as there is an (s, t)-path in the auxiliary digraph, find + # the shortest (with respect to the number of arcs) such path and + # augment the flow on this path. + while True: + try: + path_nodes = nx.bidirectional_shortest_path(auxiliary, s, t) + except nx.NetworkXNoPath: + break + + # Get the list of edges in the shortest path. + path_edges = list(zip(path_nodes[:-1], path_nodes[1:])) + + # Find the minimum capacity of an edge in the path. + try: + path_capacity = min([auxiliary[u][v][capacity] + for u, v in path_edges + if capacity in auxiliary[u][v]]) + except ValueError: + # path of infinite capacity implies no max flow + raise nx.NetworkXUnbounded( + "Infinite capacity path, flow unbounded above.") + + flow_value += path_capacity + + # Augment the flow along the path. + for u, v in path_edges: + edge_attr = auxiliary[u][v] + if capacity in edge_attr: + edge_attr[capacity] -= path_capacity + if edge_attr[capacity] == 0: + auxiliary.remove_edge(u, v) + else: + inf_capacity_flows[(u, v)] += path_capacity + + if auxiliary.has_edge(v, u): + if capacity in auxiliary[v][u]: + auxiliary[v][u][capacity] += path_capacity + else: + auxiliary.add_edge(v, u, {capacity: path_capacity}) + + auxiliary.graph['inf_capacity_flows'] = inf_capacity_flows + return flow_value, auxiliary + +def _create_auxiliary_digraph(G, capacity='capacity'): + """Initialize an auxiliary digraph and dict of infinite capacity + edges for a given graph G. + Ignore edges with capacity <= 0. + """ + auxiliary = nx.DiGraph() + auxiliary.add_nodes_from(G) + inf_capacity_flows = {} + if nx.is_directed(G): + for edge in G.edges(data = True): + if capacity in edge[2]: + if edge[2][capacity] > 0: + auxiliary.add_edge(*edge) + else: + auxiliary.add_edge(*edge) + inf_capacity_flows[(edge[0], edge[1])] = 0 + else: + for edge in G.edges(data = True): + if capacity in edge[2]: + if edge[2][capacity] > 0: + auxiliary.add_edge(*edge) + auxiliary.add_edge(edge[1], edge[0], edge[2]) + else: + auxiliary.add_edge(*edge) + auxiliary.add_edge(edge[1], edge[0], edge[2]) + inf_capacity_flows[(edge[0], edge[1])] = 0 + inf_capacity_flows[(edge[1], edge[0])] = 0 + + auxiliary.graph['inf_capacity_flows'] = inf_capacity_flows + return auxiliary + + +def _create_flow_dict(G, H, capacity='capacity'): + """Creates the flow dict of dicts on G corresponding to the + auxiliary digraph H and infinite capacity edges flows + inf_capacity_flows. + """ + inf_capacity_flows = H.graph['inf_capacity_flows'] + flow = dict([(u, {}) for u in G]) + + if G.is_directed(): + for u, v in G.edges_iter(): + if H.has_edge(u, v): + if capacity in G[u][v]: + flow[u][v] = max(0, G[u][v][capacity] - H[u][v][capacity]) + elif G.has_edge(v, u) and not capacity in G[v][u]: + flow[u][v] = max(0, inf_capacity_flows[(u, v)] - + inf_capacity_flows[(v, u)]) + else: + flow[u][v] = max(0, H[v].get(u, {}).get(capacity, 0) - + G[v].get(u, {}).get(capacity, 0)) + else: + flow[u][v] = G[u][v][capacity] + + else: # undirected + for u, v in G.edges_iter(): + if H.has_edge(u, v): + if capacity in G[u][v]: + flow[u][v] = abs(G[u][v][capacity] - H[u][v][capacity]) + else: + flow[u][v] = abs(inf_capacity_flows[(u, v)] - + inf_capacity_flows[(v, u)]) + else: + flow[u][v] = G[u][v][capacity] + flow[v][u] = flow[u][v] + + return flow + +def ford_fulkerson(G, s, t, capacity='capacity'): + """Find a maximum single-commodity flow using the Ford-Fulkerson + algorithm. + + This algorithm uses Edmonds-Karp-Dinitz path selection rule which + guarantees a running time of `O(nm^2)` for `n` nodes and `m` edges. + + + Parameters + ---------- + G : NetworkX graph + Edges of the graph are expected to have an attribute called + 'capacity'. If this attribute is not present, the edge is + considered to have infinite capacity. + + s : node + Source node for the flow. + + t : node + Sink node for the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + Returns + ------- + flow_value : integer, float + Value of the maximum flow, i.e., net outflow from the source. + + flow_dict : dictionary + Dictionary of dictionaries keyed by nodes such that + flow_dict[u][v] is the flow edge (u, v). + + Raises + ------ + NetworkXError + The algorithm does not support MultiGraph and MultiDiGraph. If + the input graph is an instance of one of these two classes, a + NetworkXError is raised. + + NetworkXUnbounded + If the graph has a path of infinite capacity, the value of a + feasible flow on the graph is unbounded above and the function + raises a NetworkXUnbounded. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_edge('x','a', capacity=3.0) + >>> G.add_edge('x','b', capacity=1.0) + >>> G.add_edge('a','c', capacity=3.0) + >>> G.add_edge('b','c', capacity=5.0) + >>> G.add_edge('b','d', capacity=4.0) + >>> G.add_edge('d','e', capacity=2.0) + >>> G.add_edge('c','y', capacity=2.0) + >>> G.add_edge('e','y', capacity=3.0) + >>> flow, F = nx.ford_fulkerson(G, 'x', 'y') + >>> flow + 3.0 + """ + flow_value, auxiliary = ford_fulkerson_flow_and_auxiliary(G, + s, t, capacity=capacity) + flow_dict = _create_flow_dict(G, auxiliary, capacity=capacity) + return flow_value, flow_dict + +def ford_fulkerson_flow(G, s, t, capacity='capacity'): + """Return a maximum flow for a single-commodity flow problem. + + Parameters + ---------- + G : NetworkX graph + Edges of the graph are expected to have an attribute called + 'capacity'. If this attribute is not present, the edge is + considered to have infinite capacity. + + s : node + Source node for the flow. + + t : node + Sink node for the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + Returns + ------- + flow_dict : dictionary + Dictionary of dictionaries keyed by nodes such that + flow_dict[u][v] is the flow edge (u, v). + + Raises + ------ + NetworkXError + The algorithm does not support MultiGraph and MultiDiGraph. If + the input graph is an instance of one of these two classes, a + NetworkXError is raised. + + NetworkXUnbounded + If the graph has a path of infinite capacity, the value of a + feasible flow on the graph is unbounded above and the function + raises a NetworkXUnbounded. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_edge('x','a', capacity=3.0) + >>> G.add_edge('x','b', capacity=1.0) + >>> G.add_edge('a','c', capacity=3.0) + >>> G.add_edge('b','c', capacity=5.0) + >>> G.add_edge('b','d', capacity=4.0) + >>> G.add_edge('d','e', capacity=2.0) + >>> G.add_edge('c','y', capacity=2.0) + >>> G.add_edge('e','y', capacity=3.0) + >>> F = nx.ford_fulkerson_flow(G, 'x', 'y') + >>> for u, v in sorted(G.edges_iter()): + ... print('(%s, %s) %.2f' % (u, v, F[u][v])) + ... + (a, c) 2.00 + (b, c) 0.00 + (b, d) 1.00 + (c, y) 2.00 + (d, e) 1.00 + (e, y) 1.00 + (x, a) 2.00 + (x, b) 1.00 + """ + flow_value, auxiliary = ford_fulkerson_flow_and_auxiliary(G, + s, t, capacity=capacity) + return _create_flow_dict(G, auxiliary, capacity=capacity) + +def max_flow(G, s, t, capacity='capacity'): + """Find the value of a maximum single-commodity flow. + + Parameters + ---------- + G : NetworkX graph + Edges of the graph are expected to have an attribute called + 'capacity'. If this attribute is not present, the edge is + considered to have infinite capacity. + + s : node + Source node for the flow. + + t : node + Sink node for the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + Returns + ------- + flow_value : integer, float + Value of the maximum flow, i.e., net outflow from the source. + + Raises + ------ + NetworkXError + The algorithm does not support MultiGraph and MultiDiGraph. If + the input graph is an instance of one of these two classes, a + NetworkXError is raised. + + NetworkXUnbounded + If the graph has a path of infinite capacity, the value of a + feasible flow on the graph is unbounded above and the function + raises a NetworkXUnbounded. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_edge('x','a', capacity=3.0) + >>> G.add_edge('x','b', capacity=1.0) + >>> G.add_edge('a','c', capacity=3.0) + >>> G.add_edge('b','c', capacity=5.0) + >>> G.add_edge('b','d', capacity=4.0) + >>> G.add_edge('d','e', capacity=2.0) + >>> G.add_edge('c','y', capacity=2.0) + >>> G.add_edge('e','y', capacity=3.0) + >>> flow = nx.max_flow(G, 'x', 'y') + >>> flow + 3.0 + """ + return ford_fulkerson_flow_and_auxiliary(G, s, t, capacity=capacity)[0] + + +def min_cut(G, s, t, capacity='capacity'): + """Compute the value of a minimum (s, t)-cut. + + Use the max-flow min-cut theorem, i.e., the capacity of a minimum + capacity cut is equal to the flow value of a maximum flow. + + Parameters + ---------- + G : NetworkX graph + Edges of the graph are expected to have an attribute called + 'capacity'. If this attribute is not present, the edge is + considered to have infinite capacity. + + s : node + Source node for the flow. + + t : node + Sink node for the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + Returns + ------- + cutValue : integer, float + Value of the minimum cut. + + Raises + ------ + NetworkXUnbounded + If the graph has a path of infinite capacity, all cuts have + infinite capacity and the function raises a NetworkXError. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_edge('x','a', capacity = 3.0) + >>> G.add_edge('x','b', capacity = 1.0) + >>> G.add_edge('a','c', capacity = 3.0) + >>> G.add_edge('b','c', capacity = 5.0) + >>> G.add_edge('b','d', capacity = 4.0) + >>> G.add_edge('d','e', capacity = 2.0) + >>> G.add_edge('c','y', capacity = 2.0) + >>> G.add_edge('e','y', capacity = 3.0) + >>> nx.min_cut(G, 'x', 'y') + 3.0 + """ + + try: + return ford_fulkerson_flow_and_auxiliary(G, s, t, capacity=capacity)[0] + except nx.NetworkXUnbounded: + raise nx.NetworkXUnbounded( + "Infinite capacity path, no minimum cut.") + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/mincost.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/mincost.py new file mode 100644 index 0000000..9e5f574 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/mincost.py @@ -0,0 +1,802 @@ +# -*- coding: utf-8 -*- +""" +Minimum cost flow algorithms on directed connected graphs. +""" + +__author__ = """Loïc Séguin-C. """ +# Copyright (C) 2010 Loïc Séguin-C. +# All rights reserved. +# BSD license. + + +__all__ = ['network_simplex', + 'min_cost_flow_cost', + 'min_cost_flow', + 'cost_of_flow', + 'max_flow_min_cost'] + +import networkx as nx +from networkx.utils import generate_unique_node + +def _initial_tree_solution(G, demand = 'demand', capacity = 'capacity', + weight = 'weight'): + """Find a initial tree solution rooted at r. + + The initial tree solution is obtained by considering edges (r, v) + for all nodes v with non-negative demand and (v, r) for all nodes + with negative demand. If these edges do not exist, we add them to + the graph and call them artificial edges. + """ + H = nx.DiGraph((edge for edge in G.edges(data=True) if + edge[2].get(capacity, 1) > 0)) + demand_nodes = (node for node in G.nodes_iter(data=True) if + node[1].get(demand, 0) != 0) + H.add_nodes_from(demand_nodes) + r = H.nodes()[0] + + T = nx.DiGraph() + y = {r: 0} + artificialEdges = [] + flowCost = 0 + + n = H.number_of_nodes() + try: + maxWeight = max(abs(d[weight]) for u, v, d in H.edges(data = True) + if weight in d) + except ValueError: + maxWeight = 0 + hugeWeight = 1 + n * maxWeight + + for v, d in H.nodes(data = True)[1:]: + vDemand = d.get(demand, 0) + if vDemand >= 0: + if not (r, v) in H.edges(): + H.add_edge(r, v, {weight: hugeWeight, 'flow': vDemand}) + artificialEdges.append((r, v)) + y[v] = H[r][v].get(weight, 0) + T.add_edge(r, v) + flowCost += vDemand * H[r][v].get(weight, 0) + + else: # (r, v) in H.edges() + if (not capacity in H[r][v] + or vDemand <= H[r][v][capacity]): + H[r][v]['flow'] = vDemand + y[v] = H[r][v].get(weight, 0) + T.add_edge(r, v) + flowCost += vDemand * H[r][v].get(weight, 0) + + else: # existing edge does not have enough capacity + newLabel = generate_unique_node() + H.add_edge(r, newLabel, {weight: hugeWeight, 'flow': vDemand}) + H.add_edge(newLabel, v, {weight: hugeWeight, 'flow': vDemand}) + artificialEdges.append((r, newLabel)) + artificialEdges.append((newLabel, v)) + y[v] = 2 * hugeWeight + y[newLabel] = hugeWeight + T.add_edge(r, newLabel) + T.add_edge(newLabel, v) + flowCost += 2 * vDemand * hugeWeight + + else: # vDemand < 0 + if not (v, r) in H.edges(): + H.add_edge(v, r, {weight: hugeWeight, 'flow': -vDemand}) + artificialEdges.append((v, r)) + y[v] = -H[v][r].get(weight, 0) + T.add_edge(v, r) + flowCost += -vDemand * H[v][r].get(weight, 0) + + else: + if (not capacity in H[v][r] + or -vDemand <= H[v][r][capacity]): + H[v][r]['flow'] = -vDemand + y[v] = -H[v][r].get(weight, 0) + T.add_edge(v, r) + flowCost += -vDemand * H[v][r].get(weight, 0) + else: # existing edge does not have enough capacity + newLabel = generate_unique_node() + H.add_edge(v, newLabel, + {weight: hugeWeight, 'flow': -vDemand}) + H.add_edge(newLabel, r, + {weight: hugeWeight, 'flow': -vDemand}) + artificialEdges.append((v, newLabel)) + artificialEdges.append((newLabel, r)) + y[v] = -2 * hugeWeight + y[newLabel] = -hugeWeight + T.add_edge(v, newLabel) + T.add_edge(newLabel, r) + flowCost += 2 * -vDemand * hugeWeight + + return H, T, y, artificialEdges, flowCost, r + + +def _find_entering_edge(H, c, capacity = 'capacity'): + """Find an edge which creates a negative cost cycle in the actual + tree solution. + + The reduced cost of every edge gives the value of the cycle + obtained by adding that edge to the tree solution. If that value is + negative, we will augment the flow in the direction indicated by + the edge. Otherwise, we will augment the flow in the reverse + direction. + + If no edge is found, return and empty tuple. This will cause the + main loop of the algorithm to terminate. + """ + newEdge = () + for u, v, d in H.edges_iter(data = True): + if d.get('flow', 0) == 0: + if c[(u, v)] < 0: + newEdge = (u, v) + break + else: + if capacity in d: + if (d.get('flow', 0) == d[capacity] + and c[(u, v)] > 0): + newEdge = (u, v) + break + return newEdge + + +def _find_leaving_edge(H, T, cycle, newEdge, capacity = 'capacity', + reverse=False): + """Find an edge that will leave the basis and the value by which we + can increase or decrease the flow on that edge. + + The leaving arc rule is used to prevent cycling. + + If cycle has no reverse edge and no forward edge of finite + capacity, it means that cycle is a negative cost infinite capacity + cycle. This implies that the cost of a flow satisfying all demands + is unbounded below. An exception is raised in this case. + """ + eps = False + leavingEdge = () + + # If cycle is a digon. + if len(cycle) == 3: + u, v = newEdge + if capacity not in H[u][v] and capacity not in H[v][u]: + raise nx.NetworkXUnbounded( + "Negative cost cycle of infinite capacity found. " + + "Min cost flow unbounded below.") + + if reverse: + if H[u][v].get('flow', 0) > H[v][u].get('flow', 0): + return (v, u), H[v][u].get('flow', 0) + else: + return (u, v), H[u][v].get('flow', 0) + else: + uv_residual = H[u][v].get(capacity, 0) - H[u][v].get('flow', 0) + vu_residual = H[v][u].get(capacity, 0) - H[v][u].get('flow', 0) + if (uv_residual > vu_residual): + return (v, u), vu_residual + else: + return (u, v), uv_residual + + # Find the forward edge with the minimum value for capacity - 'flow' + # and the reverse edge with the minimum value for 'flow'. + for index, u in enumerate(cycle[:-1]): + edgeCapacity = False + edge = () + v = cycle[index + 1] + if (u, v) in T.edges() + [newEdge]: #forward edge + if capacity in H[u][v]: # edge (u, v) has finite capacity + edgeCapacity = H[u][v][capacity] - H[u][v].get('flow', 0) + edge = (u, v) + else: #reverse edge + edgeCapacity = H[v][u].get('flow', 0) + edge = (v, u) + + # Determine if edge might be the leaving edge. + if edge: + if leavingEdge: + if edgeCapacity < eps: + eps = edgeCapacity + leavingEdge = edge + else: + eps = edgeCapacity + leavingEdge = edge + + if not leavingEdge: + raise nx.NetworkXUnbounded( + "Negative cost cycle of infinite capacity found. " + + "Min cost flow unbounded below.") + + return leavingEdge, eps + + +def _create_flow_dict(G, H): + """Creates the flow dict of dicts of graph G with auxiliary graph H.""" + flowDict = dict([(u, {}) for u in G]) + + for u in G.nodes_iter(): + for v in G.neighbors(u): + if H.has_edge(u, v): + flowDict[u][v] = H[u][v].get('flow', 0) + else: + flowDict[u][v] = 0 + return flowDict + + +def network_simplex(G, demand = 'demand', capacity = 'capacity', + weight = 'weight'): + """Find a minimum cost flow satisfying all demands in digraph G. + + This is a primal network simplex algorithm that uses the leaving + arc rule to prevent cycling. + + G is a digraph with edge costs and capacities and in which nodes + have demand, i.e., they want to send or receive some amount of + flow. A negative demand means that the node wants to send flow, a + positive demand means that the node want to receive flow. A flow on + the digraph G satisfies all demand if the net flow into each node + is equal to the demand of that node. + + Parameters + ---------- + G : NetworkX graph + DiGraph on which a minimum cost flow satisfying all demands is + to be found. + + demand: string + Nodes of the graph G are expected to have an attribute demand + that indicates how much flow a node wants to send (negative + demand) or receive (positive demand). Note that the sum of the + demands should be 0 otherwise the problem in not feasible. If + this attribute is not present, a node is considered to have 0 + demand. Default value: 'demand'. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + weight: string + Edges of the graph G are expected to have an attribute weight + that indicates the cost incurred by sending one unit of flow on + that edge. If not present, the weight is considered to be 0. + Default value: 'weight'. + + Returns + ------- + flowCost: integer, float + Cost of a minimum cost flow satisfying all demands. + + flowDict: dictionary + Dictionary of dictionaries keyed by nodes such that + flowDict[u][v] is the flow edge (u, v). + + Raises + ------ + NetworkXError + This exception is raised if the input graph is not directed, + not connected or is a multigraph. + + NetworkXUnfeasible + This exception is raised in the following situations: + * The sum of the demands is not zero. Then, there is no + flow satisfying all demands. + * There is no flow satisfying all demand. + + NetworkXUnbounded + This exception is raised if the digraph G has a cycle of + negative cost and infinite capacity. Then, the cost of a flow + satisfying all demands is unbounded below. + + Notes + ----- + This algorithm is not guaranteed to work if edge weights + are floating point numbers (overflows and roundoff errors can + cause problems). + + See also + -------- + cost_of_flow, max_flow_min_cost, min_cost_flow, min_cost_flow_cost + + Examples + -------- + A simple example of a min cost flow problem. + + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_node('a', demand = -5) + >>> G.add_node('d', demand = 5) + >>> G.add_edge('a', 'b', weight = 3, capacity = 4) + >>> G.add_edge('a', 'c', weight = 6, capacity = 10) + >>> G.add_edge('b', 'd', weight = 1, capacity = 9) + >>> G.add_edge('c', 'd', weight = 2, capacity = 5) + >>> flowCost, flowDict = nx.network_simplex(G) + >>> flowCost + 24 + >>> flowDict # doctest: +SKIP + {'a': {'c': 1, 'b': 4}, 'c': {'d': 1}, 'b': {'d': 4}, 'd': {}} + + The mincost flow algorithm can also be used to solve shortest path + problems. To find the shortest path between two nodes u and v, + give all edges an infinite capacity, give node u a demand of -1 and + node v a demand a 1. Then run the network simplex. The value of a + min cost flow will be the distance between u and v and edges + carrying positive flow will indicate the path. + + >>> G=nx.DiGraph() + >>> G.add_weighted_edges_from([('s','u',10), ('s','x',5), + ... ('u','v',1), ('u','x',2), + ... ('v','y',1), ('x','u',3), + ... ('x','v',5), ('x','y',2), + ... ('y','s',7), ('y','v',6)]) + >>> G.add_node('s', demand = -1) + >>> G.add_node('v', demand = 1) + >>> flowCost, flowDict = nx.network_simplex(G) + >>> flowCost == nx.shortest_path_length(G, 's', 'v', weight = 'weight') + True + >>> sorted([(u, v) for u in flowDict for v in flowDict[u] if flowDict[u][v] > 0]) + [('s', 'x'), ('u', 'v'), ('x', 'u')] + >>> nx.shortest_path(G, 's', 'v', weight = 'weight') + ['s', 'x', 'u', 'v'] + + It is possible to change the name of the attributes used for the + algorithm. + + >>> G = nx.DiGraph() + >>> G.add_node('p', spam = -4) + >>> G.add_node('q', spam = 2) + >>> G.add_node('a', spam = -2) + >>> G.add_node('d', spam = -1) + >>> G.add_node('t', spam = 2) + >>> G.add_node('w', spam = 3) + >>> G.add_edge('p', 'q', cost = 7, vacancies = 5) + >>> G.add_edge('p', 'a', cost = 1, vacancies = 4) + >>> G.add_edge('q', 'd', cost = 2, vacancies = 3) + >>> G.add_edge('t', 'q', cost = 1, vacancies = 2) + >>> G.add_edge('a', 't', cost = 2, vacancies = 4) + >>> G.add_edge('d', 'w', cost = 3, vacancies = 4) + >>> G.add_edge('t', 'w', cost = 4, vacancies = 1) + >>> flowCost, flowDict = nx.network_simplex(G, demand = 'spam', + ... capacity = 'vacancies', + ... weight = 'cost') + >>> flowCost + 37 + >>> flowDict # doctest: +SKIP + {'a': {'t': 4}, 'd': {'w': 2}, 'q': {'d': 1}, 'p': {'q': 2, 'a': 2}, 't': {'q': 1, 'w': 1}, 'w': {}} + + References + ---------- + W. J. Cook, W. H. Cunningham, W. R. Pulleyblank and A. Schrijver. + Combinatorial Optimization. Wiley-Interscience, 1998. + + """ + + if not G.is_directed(): + raise nx.NetworkXError("Undirected graph not supported.") + if not nx.is_connected(G.to_undirected()): + raise nx.NetworkXError("Not connected graph not supported.") + if G.is_multigraph(): + raise nx.NetworkXError("MultiDiGraph not supported.") + if sum(d[demand] for v, d in G.nodes(data = True) + if demand in d) != 0: + raise nx.NetworkXUnfeasible("Sum of the demands should be 0.") + + # Fix an arbitrarily chosen root node and find an initial tree solution. + H, T, y, artificialEdges, flowCost, r = \ + _initial_tree_solution(G, demand = demand, capacity = capacity, + weight = weight) + + # Initialize the reduced costs. + c = {} + for u, v, d in H.edges_iter(data = True): + c[(u, v)] = d.get(weight, 0) + y[u] - y[v] + + # Print stuff for debugging. + # print('-' * 78) + # nbIter = 0 + # print('Iteration %d' % nbIter) + # nbIter += 1 + # print('Tree solution: %s' % T.edges()) + # print(' Edge %11s%10s' % ('Flow', 'Red Cost')) + # for u, v, d in H.edges(data = True): + # flag = '' + # if (u, v) in artificialEdges: + # flag = '*' + # print('(%s, %s)%1s%10d%10d' % (u, v, flag, d.get('flow', 0), + # c[(u, v)])) + # print('Distances: %s' % y) + + # Main loop. + while True: + newEdge = _find_entering_edge(H, c, capacity = capacity) + if not newEdge: + break # Optimal basis found. Main loop is over. + cycleCost = abs(c[newEdge]) + + # Find the cycle created by adding newEdge to T. + path1 = nx.shortest_path(T.to_undirected(), r, newEdge[0]) + path2 = nx.shortest_path(T.to_undirected(), r, newEdge[1]) + join = r + for index, node in enumerate(path1[1:]): + if index + 1 < len(path2) and node == path2[index + 1]: + join = node + else: + break + path1 = path1[path1.index(join):] + path2 = path2[path2.index(join):] + cycle = [] + if H[newEdge[0]][newEdge[1]].get('flow', 0) == 0: + reverse = False + path2.reverse() + cycle = path1 + path2 + else: # newEdge is at capacity + reverse = True + path1.reverse() + cycle = path2 + path1 + + # Find the leaving edge. Will stop here if cycle is an infinite + # capacity negative cost cycle. + leavingEdge, eps = _find_leaving_edge(H, T, cycle, newEdge, + capacity=capacity, + reverse=reverse) + + # Actual augmentation happens here. If eps = 0, don't bother. + if eps: + flowCost -= cycleCost * eps + if len(cycle) == 3: + if reverse: + eps = -eps + u, v = newEdge + H[u][v]['flow'] = H[u][v].get('flow', 0) + eps + H[v][u]['flow'] = H[v][u].get('flow', 0) + eps + else: + for index, u in enumerate(cycle[:-1]): + v = cycle[index + 1] + if (u, v) in T.edges() + [newEdge]: + H[u][v]['flow'] = H[u][v].get('flow', 0) + eps + else: # (v, u) in T.edges(): + H[v][u]['flow'] -= eps + + # Update tree solution. + T.add_edge(*newEdge) + T.remove_edge(*leavingEdge) + + # Update distances and reduced costs. + if newEdge != leavingEdge: + forest = nx.DiGraph(T) + forest.remove_edge(*newEdge) + R, notR = nx.connected_component_subgraphs(forest.to_undirected()) + if r in notR.nodes(): # make sure r is in R + R, notR = notR, R + if newEdge[0] in R.nodes(): + for v in notR.nodes(): + y[v] += c[newEdge] + else: + for v in notR.nodes(): + y[v] -= c[newEdge] + for u, v in H.edges(): + if u in notR.nodes() or v in notR.nodes(): + c[(u, v)] = H[u][v].get(weight, 0) + y[u] - y[v] + + # Print stuff for debugging. + # print('-' * 78) + # print('Iteration %d' % nbIter) + # nbIter += 1 + # print('Tree solution: %s' % T.edges()) + # print('New edge: (%s, %s)' % (newEdge[0], newEdge[1])) + # print('Leaving edge: (%s, %s)' % (leavingEdge[0], leavingEdge[1])) + # print('Cycle: %s' % cycle) + # print('eps: %d' % eps) + # print(' Edge %11s%10s' % ('Flow', 'Red Cost')) + # for u, v, d in H.edges(data = True): + # flag = '' + # if (u, v) in artificialEdges: + # flag = '*' + # print('(%s, %s)%1s%10d%10d' % (u, v, flag, d.get('flow', 0), + # c[(u, v)])) + # print('Distances: %s' % y) + + + # If an artificial edge has positive flow, the initial problem was + # not feasible. + for u, v in artificialEdges: + if H[u][v]['flow'] != 0: + raise nx.NetworkXUnfeasible("No flow satisfying all demands.") + H.remove_edge(u, v) + + for u in H.nodes(): + if not u in G: + H.remove_node(u) + + flowDict = _create_flow_dict(G, H) + + return flowCost, flowDict + + +def min_cost_flow_cost(G, demand = 'demand', capacity = 'capacity', + weight = 'weight'): + """Find the cost of a minimum cost flow satisfying all demands in digraph G. + + G is a digraph with edge costs and capacities and in which nodes + have demand, i.e., they want to send or receive some amount of + flow. A negative demand means that the node wants to send flow, a + positive demand means that the node want to receive flow. A flow on + the digraph G satisfies all demand if the net flow into each node + is equal to the demand of that node. + + Parameters + ---------- + G : NetworkX graph + DiGraph on which a minimum cost flow satisfying all demands is + to be found. + + demand: string + Nodes of the graph G are expected to have an attribute demand + that indicates how much flow a node wants to send (negative + demand) or receive (positive demand). Note that the sum of the + demands should be 0 otherwise the problem in not feasible. If + this attribute is not present, a node is considered to have 0 + demand. Default value: 'demand'. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + weight: string + Edges of the graph G are expected to have an attribute weight + that indicates the cost incurred by sending one unit of flow on + that edge. If not present, the weight is considered to be 0. + Default value: 'weight'. + + Returns + ------- + flowCost: integer, float + Cost of a minimum cost flow satisfying all demands. + + Raises + ------ + NetworkXError + This exception is raised if the input graph is not directed or + not connected. + + NetworkXUnfeasible + This exception is raised in the following situations: + * The sum of the demands is not zero. Then, there is no + flow satisfying all demands. + * There is no flow satisfying all demand. + + NetworkXUnbounded + This exception is raised if the digraph G has a cycle of + negative cost and infinite capacity. Then, the cost of a flow + satisfying all demands is unbounded below. + + See also + -------- + cost_of_flow, max_flow_min_cost, min_cost_flow, network_simplex + + Examples + -------- + A simple example of a min cost flow problem. + + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_node('a', demand = -5) + >>> G.add_node('d', demand = 5) + >>> G.add_edge('a', 'b', weight = 3, capacity = 4) + >>> G.add_edge('a', 'c', weight = 6, capacity = 10) + >>> G.add_edge('b', 'd', weight = 1, capacity = 9) + >>> G.add_edge('c', 'd', weight = 2, capacity = 5) + >>> flowCost = nx.min_cost_flow_cost(G) + >>> flowCost + 24 + """ + return network_simplex(G, demand = demand, capacity = capacity, + weight = weight)[0] + + +def min_cost_flow(G, demand = 'demand', capacity = 'capacity', + weight = 'weight'): + """Return a minimum cost flow satisfying all demands in digraph G. + + G is a digraph with edge costs and capacities and in which nodes + have demand, i.e., they want to send or receive some amount of + flow. A negative demand means that the node wants to send flow, a + positive demand means that the node want to receive flow. A flow on + the digraph G satisfies all demand if the net flow into each node + is equal to the demand of that node. + + Parameters + ---------- + G : NetworkX graph + DiGraph on which a minimum cost flow satisfying all demands is + to be found. + + demand: string + Nodes of the graph G are expected to have an attribute demand + that indicates how much flow a node wants to send (negative + demand) or receive (positive demand). Note that the sum of the + demands should be 0 otherwise the problem in not feasible. If + this attribute is not present, a node is considered to have 0 + demand. Default value: 'demand'. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + weight: string + Edges of the graph G are expected to have an attribute weight + that indicates the cost incurred by sending one unit of flow on + that edge. If not present, the weight is considered to be 0. + Default value: 'weight'. + + Returns + ------- + flowDict: dictionary + Dictionary of dictionaries keyed by nodes such that + flowDict[u][v] is the flow edge (u, v). + + Raises + ------ + NetworkXError + This exception is raised if the input graph is not directed or + not connected. + + NetworkXUnfeasible + This exception is raised in the following situations: + * The sum of the demands is not zero. Then, there is no + flow satisfying all demands. + * There is no flow satisfying all demand. + + NetworkXUnbounded + This exception is raised if the digraph G has a cycle of + negative cost and infinite capacity. Then, the cost of a flow + satisfying all demands is unbounded below. + + See also + -------- + cost_of_flow, max_flow_min_cost, min_cost_flow_cost, network_simplex + + Examples + -------- + A simple example of a min cost flow problem. + + >>> import networkx as nx + >>> G = nx.DiGraph() + >>> G.add_node('a', demand = -5) + >>> G.add_node('d', demand = 5) + >>> G.add_edge('a', 'b', weight = 3, capacity = 4) + >>> G.add_edge('a', 'c', weight = 6, capacity = 10) + >>> G.add_edge('b', 'd', weight = 1, capacity = 9) + >>> G.add_edge('c', 'd', weight = 2, capacity = 5) + >>> flowDict = nx.min_cost_flow(G) + """ + return network_simplex(G, demand = demand, capacity = capacity, + weight = weight)[1] + + +def cost_of_flow(G, flowDict, weight = 'weight'): + """Compute the cost of the flow given by flowDict on graph G. + + Note that this function does not check for the validity of the + flow flowDict. This function will fail if the graph G and the + flow don't have the same edge set. + + Parameters + ---------- + G : NetworkX graph + DiGraph on which a minimum cost flow satisfying all demands is + to be found. + + weight: string + Edges of the graph G are expected to have an attribute weight + that indicates the cost incurred by sending one unit of flow on + that edge. If not present, the weight is considered to be 0. + Default value: 'weight'. + + flowDict: dictionary + Dictionary of dictionaries keyed by nodes such that + flowDict[u][v] is the flow edge (u, v). + + Returns + ------- + cost: Integer, float + The total cost of the flow. This is given by the sum over all + edges of the product of the edge's flow and the edge's weight. + + See also + -------- + max_flow_min_cost, min_cost_flow, min_cost_flow_cost, network_simplex + """ + return sum((flowDict[u][v] * d.get(weight, 0) + for u, v, d in G.edges_iter(data = True))) + + +def max_flow_min_cost(G, s, t, capacity = 'capacity', weight = 'weight'): + """Return a maximum (s, t)-flow of minimum cost. + + G is a digraph with edge costs and capacities. There is a source + node s and a sink node t. This function finds a maximum flow from + s to t whose total cost is minimized. + + Parameters + ---------- + G : NetworkX graph + DiGraph on which a minimum cost flow satisfying all demands is + to be found. + + s: node label + Source of the flow. + + t: node label + Destination of the flow. + + capacity: string + Edges of the graph G are expected to have an attribute capacity + that indicates how much flow the edge can support. If this + attribute is not present, the edge is considered to have + infinite capacity. Default value: 'capacity'. + + weight: string + Edges of the graph G are expected to have an attribute weight + that indicates the cost incurred by sending one unit of flow on + that edge. If not present, the weight is considered to be 0. + Default value: 'weight'. + + Returns + ------- + flowDict: dictionary + Dictionary of dictionaries keyed by nodes such that + flowDict[u][v] is the flow edge (u, v). + + Raises + ------ + NetworkXError + This exception is raised if the input graph is not directed or + not connected. + + NetworkXUnbounded + This exception is raised if there is an infinite capacity path + from s to t in G. In this case there is no maximum flow. This + exception is also raised if the digraph G has a cycle of + negative cost and infinite capacity. Then, the cost of a flow + is unbounded below. + + See also + -------- + cost_of_flow, ford_fulkerson, min_cost_flow, min_cost_flow_cost, + network_simplex + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_edges_from([(1, 2, {'capacity': 12, 'weight': 4}), + ... (1, 3, {'capacity': 20, 'weight': 6}), + ... (2, 3, {'capacity': 6, 'weight': -3}), + ... (2, 6, {'capacity': 14, 'weight': 1}), + ... (3, 4, {'weight': 9}), + ... (3, 5, {'capacity': 10, 'weight': 5}), + ... (4, 2, {'capacity': 19, 'weight': 13}), + ... (4, 5, {'capacity': 4, 'weight': 0}), + ... (5, 7, {'capacity': 28, 'weight': 2}), + ... (6, 5, {'capacity': 11, 'weight': 1}), + ... (6, 7, {'weight': 8}), + ... (7, 4, {'capacity': 6, 'weight': 6})]) + >>> mincostFlow = nx.max_flow_min_cost(G, 1, 7) + >>> nx.cost_of_flow(G, mincostFlow) + 373 + >>> maxFlow = nx.ford_fulkerson_flow(G, 1, 7) + >>> nx.cost_of_flow(G, maxFlow) + 428 + >>> mincostFlowValue = (sum((mincostFlow[u][7] for u in G.predecessors(7))) + ... - sum((mincostFlow[7][v] for v in G.successors(7)))) + >>> mincostFlowValue == nx.max_flow(G, 1, 7) + True + + + """ + maxFlow = nx.max_flow(G, s, t, capacity = capacity) + H = nx.DiGraph(G) + H.add_node(s, demand = -maxFlow) + H.add_node(t, demand = maxFlow) + return min_cost_flow(H, capacity = capacity, weight = weight) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow.py new file mode 100644 index 0000000..41393c2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +"""Max flow algorithm test suite. + +Run with nose: nosetests -v test_max_flow.py +""" + +__author__ = """Loïc Séguin-C. """ +# Copyright (C) 2010 Loïc Séguin-C. +# All rights reserved. +# BSD license. + + +import networkx as nx +from nose.tools import * + +def compare_flows(G, s, t, solnFlows, solnValue): + flowValue, flowDict = nx.ford_fulkerson(G, s, t) + assert_equal(flowValue, solnValue) + assert_equal(flowDict, solnFlows) + assert_equal(nx.min_cut(G, s, t), solnValue) + assert_equal(nx.max_flow(G, s, t), solnValue) + assert_equal(nx.ford_fulkerson_flow(G, s, t), solnFlows) + + +class TestMaxflow: + def test_graph1(self): + # Trivial undirected graph + G = nx.Graph() + G.add_edge(1,2, capacity = 1.0) + + solnFlows = {1: {2: 1.0}, + 2: {1: 1.0}} + + compare_flows(G, 1, 2, solnFlows, 1.0) + + def test_graph2(self): + # A more complex undirected graph + # adapted from www.topcoder.com/tc?module=Statc&d1=tutorials&d2=maxFlow + G = nx.Graph() + G.add_edge('x','a', capacity = 3.0) + G.add_edge('x','b', capacity = 1.0) + G.add_edge('a','c', capacity = 3.0) + G.add_edge('b','c', capacity = 5.0) + G.add_edge('b','d', capacity = 4.0) + G.add_edge('d','e', capacity = 2.0) + G.add_edge('c','y', capacity = 2.0) + G.add_edge('e','y', capacity = 3.0) + + H = {'x': {'a': 3, 'b': 1}, + 'a': {'c': 3, 'x': 3}, + 'b': {'c': 1, 'd': 2, 'x': 1}, + 'c': {'a': 3, 'b': 1, 'y': 2}, + 'd': {'b': 2, 'e': 2}, + 'e': {'d': 2, 'y': 2}, + 'y': {'c': 2, 'e': 2}} + + compare_flows(G, 'x', 'y', H, 4.0) + + def test_digraph1(self): + # The classic directed graph example + G = nx.DiGraph() + G.add_edge('a','b', capacity = 1000.0) + G.add_edge('a','c', capacity = 1000.0) + G.add_edge('b','c', capacity = 1.0) + G.add_edge('b','d', capacity = 1000.0) + G.add_edge('c','d', capacity = 1000.0) + + H = {'a': {'b': 1000.0, 'c': 1000.0}, + 'b': {'c': 0, 'd': 1000.0}, + 'c': {'d': 1000.0}, + 'd': {}} + + compare_flows(G, 'a', 'd', H, 2000.0) + + # An example in which some edges end up with zero flow. + G = nx.DiGraph() + G.add_edge('s', 'b', capacity = 2) + G.add_edge('s', 'c', capacity = 1) + G.add_edge('c', 'd', capacity = 1) + G.add_edge('d', 'a', capacity = 1) + G.add_edge('b', 'a', capacity = 2) + G.add_edge('a', 't', capacity = 2) + + H = {'s': {'b': 2, 'c': 0}, + 'c': {'d': 0}, + 'd': {'a': 0}, + 'b': {'a': 2}, + 'a': {'t': 2}, + 't': {}} + + compare_flows(G, 's', 't', H, 2) + + def test_digraph2(self): + # A directed graph example from Cormen et al. + G = nx.DiGraph() + G.add_edge('s','v1', capacity = 16.0) + G.add_edge('s','v2', capacity = 13.0) + G.add_edge('v1','v2', capacity = 10.0) + G.add_edge('v2','v1', capacity = 4.0) + G.add_edge('v1','v3', capacity = 12.0) + G.add_edge('v3','v2', capacity = 9.0) + G.add_edge('v2','v4', capacity = 14.0) + G.add_edge('v4','v3', capacity = 7.0) + G.add_edge('v3','t', capacity = 20.0) + G.add_edge('v4','t', capacity = 4.0) + + H = {'s': {'v1': 12.0, 'v2': 11.0}, + 'v2': {'v1': 0, 'v4': 11.0}, + 'v1': {'v2': 0, 'v3': 12.0}, + 'v3': {'v2': 0, 't': 19.0}, + 'v4': {'v3': 7.0, 't': 4.0}, + 't': {}} + + compare_flows(G, 's', 't', H, 23.0) + + def test_digraph3(self): + # A more complex directed graph + # from www.topcoder.com/tc?module=Statc&d1=tutorials&d2=maxFlow + G = nx.DiGraph() + G.add_edge('x','a', capacity = 3.0) + G.add_edge('x','b', capacity = 1.0) + G.add_edge('a','c', capacity = 3.0) + G.add_edge('b','c', capacity = 5.0) + G.add_edge('b','d', capacity = 4.0) + G.add_edge('d','e', capacity = 2.0) + G.add_edge('c','y', capacity = 2.0) + G.add_edge('e','y', capacity = 3.0) + + H = {'x': {'a': 2.0, 'b': 1.0}, + 'a': {'c': 2.0}, + 'b': {'c': 0, 'd': 1.0}, + 'c': {'y': 2.0}, + 'd': {'e': 1.0}, + 'e': {'y': 1.0}, + 'y': {}} + + compare_flows(G, 'x', 'y', H, 3.0) + + def test_optional_capacity(self): + # Test optional capacity parameter. + G = nx.DiGraph() + G.add_edge('x','a', spam = 3.0) + G.add_edge('x','b', spam = 1.0) + G.add_edge('a','c', spam = 3.0) + G.add_edge('b','c', spam = 5.0) + G.add_edge('b','d', spam = 4.0) + G.add_edge('d','e', spam = 2.0) + G.add_edge('c','y', spam = 2.0) + G.add_edge('e','y', spam = 3.0) + + solnFlows = {'x': {'a': 2.0, 'b': 1.0}, + 'a': {'c': 2.0}, + 'b': {'c': 0, 'd': 1.0}, + 'c': {'y': 2.0}, + 'd': {'e': 1.0}, + 'e': {'y': 1.0}, + 'y': {}} + solnValue = 3.0 + s = 'x' + t = 'y' + + flowValue, flowDict = nx.ford_fulkerson(G, s, t, capacity = 'spam') + assert_equal(flowValue, solnValue) + assert_equal(flowDict, solnFlows) + assert_equal(nx.min_cut(G, s, t, capacity = 'spam'), solnValue) + assert_equal(nx.max_flow(G, s, t, capacity = 'spam'), solnValue) + assert_equal(nx.ford_fulkerson_flow(G, s, t, capacity = 'spam'), + solnFlows) + + def test_digraph_infcap_edges(self): + # DiGraph with infinite capacity edges + G = nx.DiGraph() + G.add_edge('s', 'a') + G.add_edge('s', 'b', capacity = 30) + G.add_edge('a', 'c', capacity = 25) + G.add_edge('b', 'c', capacity = 12) + G.add_edge('a', 't', capacity = 60) + G.add_edge('c', 't') + + H = {'s': {'a': 85, 'b': 12}, + 'a': {'c': 25, 't': 60}, + 'b': {'c': 12}, + 'c': {'t': 37}, + 't': {}} + + compare_flows(G, 's', 't', H, 97) + + # DiGraph with infinite capacity digon + G = nx.DiGraph() + G.add_edge('s', 'a', capacity = 85) + G.add_edge('s', 'b', capacity = 30) + G.add_edge('a', 'c') + G.add_edge('c', 'a') + G.add_edge('b', 'c', capacity = 12) + G.add_edge('a', 't', capacity = 60) + G.add_edge('c', 't', capacity = 37) + + H = {'s': {'a': 85, 'b': 12}, + 'a': {'c': 25, 't': 60}, + 'c': {'a': 0, 't': 37}, + 'b': {'c': 12}, + 't': {}} + + compare_flows(G, 's', 't', H, 97) + + + def test_digraph_infcap_path(self): + # Graph with infinite capacity (s, t)-path + G = nx.DiGraph() + G.add_edge('s', 'a') + G.add_edge('s', 'b', capacity = 30) + G.add_edge('a', 'c') + G.add_edge('b', 'c', capacity = 12) + G.add_edge('a', 't', capacity = 60) + G.add_edge('c', 't') + + assert_raises(nx.NetworkXUnbounded, + nx.ford_fulkerson, G, 's', 't') + assert_raises(nx.NetworkXUnbounded, + nx.max_flow, G, 's', 't') + assert_raises(nx.NetworkXUnbounded, + nx.ford_fulkerson_flow, G, 's', 't') + assert_raises(nx.NetworkXUnbounded, + nx.min_cut, G, 's', 't') + + def test_graph_infcap_edges(self): + # Undirected graph with infinite capacity edges + G = nx.Graph() + G.add_edge('s', 'a') + G.add_edge('s', 'b', capacity = 30) + G.add_edge('a', 'c', capacity = 25) + G.add_edge('b', 'c', capacity = 12) + G.add_edge('a', 't', capacity = 60) + G.add_edge('c', 't') + + H = {'s': {'a': 85, 'b': 12}, + 'a': {'c': 25, 's': 85, 't': 60}, + 'b': {'c': 12, 's': 12}, + 'c': {'a': 25, 'b': 12, 't': 37}, + 't': {'a': 60, 'c': 37}} + + compare_flows(G, 's', 't', H, 97) + + def test_digraph4(self): + # From ticket #429 by mfrasca. + G = nx.DiGraph() + G.add_edge('s', 'a', capacity = 2) + G.add_edge('s', 'b', capacity = 2) + G.add_edge('a', 'b', capacity = 5) + G.add_edge('a', 't', capacity = 1) + G.add_edge('b', 'a', capacity = 1) + G.add_edge('b', 't', capacity = 3) + flowSoln = {'a': {'b': 1, 't': 1}, + 'b': {'a': 0, 't': 3}, + 's': {'a': 2, 'b': 2}, + 't': {}} + compare_flows(G, 's', 't', flowSoln, 4) + + + def test_disconnected(self): + G = nx.Graph() + G.add_weighted_edges_from([(0,1,1),(1,2,1),(2,3,1)],weight='capacity') + G.remove_node(1) + assert_equal(nx.max_flow(G,0,3),0) + + def test_source_target_not_in_graph(self): + G = nx.Graph() + G.add_weighted_edges_from([(0,1,1),(1,2,1),(2,3,1)],weight='capacity') + G.remove_node(0) + assert_raises(nx.NetworkXError,nx.max_flow,G,0,3) + G.add_weighted_edges_from([(0,1,1),(1,2,1),(2,3,1)],weight='capacity') + G.remove_node(3) + assert_raises(nx.NetworkXError,nx.max_flow,G,0,3) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow_large_graph.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow_large_graph.py new file mode 100644 index 0000000..578e2df --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_maxflow_large_graph.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +"""Max flow algorithm test suite on large graphs. + +Run with nose: nosetests -v test_max_flow.py +""" + +__author__ = """Loïc Séguin-C. """ +# Copyright (C) 2010 Loïc Séguin-C. +# All rights reserved. +# BSD license. + + +import networkx as nx +from nose.tools import * + +def gen_pyramid(N): + # This graph admits a flow of value 1 for which every arc is at + # capacity (except the arcs incident to the sink which have + # infinite capacity). + G = nx.DiGraph() + + for i in range(N - 1): + cap = 1. / (i + 2) + for j in range(i + 1): + G.add_edge((i, j), (i + 1, j), + capacity = cap) + cap = 1. / (i + 1) - cap + G.add_edge((i, j), (i + 1, j + 1), + capacity = cap) + cap = 1. / (i + 2) - cap + + for j in range(N): + G.add_edge((N - 1, j), 't') + + return G + + +class TestMaxflowLargeGraph: + def test_complete_graph(self): + N = 50 + G = nx.complete_graph(N) + for (u, v) in G.edges(): + G[u][v]['capacity'] = 5 + assert_equal(nx.ford_fulkerson(G, 1, 2)[0], 5 * (N - 1)) + + def test_pyramid(self): + N = 10 +# N = 100 # this gives a graph with 5051 nodes + G = gen_pyramid(N) + assert_almost_equal(nx.ford_fulkerson(G, (0, 0), 't')[0], 1.) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_mincost.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_mincost.py new file mode 100644 index 0000000..72df2f0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/flow/tests/test_mincost.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- + +import networkx as nx +from nose.tools import assert_equal, assert_raises + +class TestNetworkSimplex: + def test_simple_digraph(self): + G = nx.DiGraph() + G.add_node('a', demand = -5) + G.add_node('d', demand = 5) + G.add_edge('a', 'b', weight = 3, capacity = 4) + G.add_edge('a', 'c', weight = 6, capacity = 10) + G.add_edge('b', 'd', weight = 1, capacity = 9) + G.add_edge('c', 'd', weight = 2, capacity = 5) + flowCost, H = nx.network_simplex(G) + soln = {'a': {'b': 4, 'c': 1}, + 'b': {'d': 4}, + 'c': {'d': 1}, + 'd': {}} + assert_equal(flowCost, 24) + assert_equal(nx.min_cost_flow_cost(G), 24) + assert_equal(H, soln) + assert_equal(nx.min_cost_flow(G), soln) + assert_equal(nx.cost_of_flow(G, H), 24) + + def test_negcycle_infcap(self): + G = nx.DiGraph() + G.add_node('s', demand = -5) + G.add_node('t', demand = 5) + G.add_edge('s', 'a', weight = 1, capacity = 3) + G.add_edge('a', 'b', weight = 3) + G.add_edge('c', 'a', weight = -6) + G.add_edge('b', 'd', weight = 1) + G.add_edge('d', 'c', weight = -2) + G.add_edge('d', 't', weight = 1, capacity = 3) + assert_raises(nx.NetworkXUnbounded, nx.network_simplex, G) + + def test_sum_demands_not_zero(self): + G = nx.DiGraph() + G.add_node('s', demand = -5) + G.add_node('t', demand = 4) + G.add_edge('s', 'a', weight = 1, capacity = 3) + G.add_edge('a', 'b', weight = 3) + G.add_edge('a', 'c', weight = -6) + G.add_edge('b', 'd', weight = 1) + G.add_edge('c', 'd', weight = -2) + G.add_edge('d', 't', weight = 1, capacity = 3) + assert_raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + + def test_no_flow_satisfying_demands(self): + G = nx.DiGraph() + G.add_node('s', demand = -5) + G.add_node('t', demand = 5) + G.add_edge('s', 'a', weight = 1, capacity = 3) + G.add_edge('a', 'b', weight = 3) + G.add_edge('a', 'c', weight = -6) + G.add_edge('b', 'd', weight = 1) + G.add_edge('c', 'd', weight = -2) + G.add_edge('d', 't', weight = 1, capacity = 3) + assert_raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + + def test_transshipment(self): + G = nx.DiGraph() + G.add_node('a', demand = 1) + G.add_node('b', demand = -2) + G.add_node('c', demand = -2) + G.add_node('d', demand = 3) + G.add_node('e', demand = -4) + G.add_node('f', demand = -4) + G.add_node('g', demand = 3) + G.add_node('h', demand = 2) + G.add_node('r', demand = 3) + G.add_edge('a', 'c', weight = 3) + G.add_edge('r', 'a', weight = 2) + G.add_edge('b', 'a', weight = 9) + G.add_edge('r', 'c', weight = 0) + G.add_edge('b', 'r', weight = -6) + G.add_edge('c', 'd', weight = 5) + G.add_edge('e', 'r', weight = 4) + G.add_edge('e', 'f', weight = 3) + G.add_edge('h', 'b', weight = 4) + G.add_edge('f', 'd', weight = 7) + G.add_edge('f', 'h', weight = 12) + G.add_edge('g', 'd', weight = 12) + G.add_edge('f', 'g', weight = -1) + G.add_edge('h', 'g', weight = -10) + flowCost, H = nx.network_simplex(G) + soln = {'a': {'c': 0}, + 'b': {'a': 0, 'r': 2}, + 'c': {'d': 3}, + 'd': {}, + 'e': {'r': 3, 'f': 1}, + 'f': {'d': 0, 'g': 3, 'h': 2}, + 'g': {'d': 0}, + 'h': {'b': 0, 'g': 0}, + 'r': {'a': 1, 'c': 1}} + assert_equal(flowCost, 41) + assert_equal(nx.min_cost_flow_cost(G), 41) + assert_equal(H, soln) + assert_equal(nx.min_cost_flow(G), soln) + assert_equal(nx.cost_of_flow(G, H), 41) + + def test_max_flow_min_cost(self): + G = nx.DiGraph() + G.add_edge('s', 'a', bandwidth = 6) + G.add_edge('s', 'c', bandwidth = 10, cost = 10) + G.add_edge('a', 'b', cost = 6) + G.add_edge('b', 'd', bandwidth = 8, cost = 7) + G.add_edge('c', 'd', cost = 10) + G.add_edge('d', 't', bandwidth = 5, cost = 5) + soln = {'s': {'a': 5, 'c': 0}, + 'a': {'b': 5}, + 'b': {'d': 5}, + 'c': {'d': 0}, + 'd': {'t': 5}, + 't': {}} + flow = nx.max_flow_min_cost(G, 's', 't', capacity = 'bandwidth', + weight = 'cost') + assert_equal(flow, soln) + assert_equal(nx.cost_of_flow(G, flow, weight = 'cost'), 90) + + def test_digraph1(self): + # From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied + # Mathematical Programming. Addison-Wesley, 1977. + G = nx.DiGraph() + G.add_node(1, demand = -20) + G.add_node(4, demand = 5) + G.add_node(5, demand = 15) + G.add_edges_from([(1, 2, {'capacity': 15, 'weight': 4}), + (1, 3, {'capacity': 8, 'weight': 4}), + (2, 3, {'weight': 2}), + (2, 4, {'capacity': 4, 'weight': 2}), + (2, 5, {'capacity': 10, 'weight': 6}), + (3, 4, {'capacity': 15, 'weight': 1}), + (3, 5, {'capacity': 5, 'weight': 3}), + (4, 5, {'weight': 2}), + (5, 3, {'capacity': 4, 'weight': 1})]) + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 12, 3: 8}, + 2: {3: 8, 4: 4, 5: 0}, + 3: {4: 11, 5: 5}, + 4: {5: 10}, + 5: {3: 0}} + assert_equal(flowCost, 150) + assert_equal(nx.min_cost_flow_cost(G), 150) + assert_equal(H, soln) + assert_equal(nx.min_cost_flow(G), soln) + assert_equal(nx.cost_of_flow(G, H), 150) + + def test_digraph2(self): + # Example from ticket #430 from mfrasca. Original source: + # http://www.cs.princeton.edu/courses/archive/spr03/cs226/lectures/mincost.4up.pdf, slide 11. + G = nx.DiGraph() + G.add_edge('s', 1, capacity=12) + G.add_edge('s', 2, capacity=6) + G.add_edge('s', 3, capacity=14) + G.add_edge(1, 2, capacity=11, weight=4) + G.add_edge(2, 3, capacity=9, weight=6) + G.add_edge(1, 4, capacity=5, weight=5) + G.add_edge(1, 5, capacity=2, weight=12) + G.add_edge(2, 5, capacity=4, weight=4) + G.add_edge(2, 6, capacity=2, weight=6) + G.add_edge(3, 6, capacity=31, weight=3) + G.add_edge(4, 5, capacity=18, weight=4) + G.add_edge(5, 6, capacity=9, weight=5) + G.add_edge(4, 't', capacity=3) + G.add_edge(5, 't', capacity=7) + G.add_edge(6, 't', capacity=22) + flow = nx.max_flow_min_cost(G, 's', 't') + soln = {1: {2: 6, 4: 5, 5: 1}, + 2: {3: 6, 5: 4, 6: 2}, + 3: {6: 20}, + 4: {5: 2, 't': 3}, + 5: {6: 0, 't': 7}, + 6: {'t': 22}, + 's': {1: 12, 2: 6, 3: 14}, + 't': {}} + assert_equal(flow, soln) + + def test_digraph3(self): + """Combinatorial Optimization: Algorithms and Complexity, + Papadimitriou Steiglitz at page 140 has an example, 7.1, but that + admits multiple solutions, so I alter it a bit. From ticket #430 + by mfrasca.""" + + G = nx.DiGraph() + G.add_edge('s', 'a', {0: 2, 1: 4}) + G.add_edge('s', 'b', {0: 2, 1: 1}) + G.add_edge('a', 'b', {0: 5, 1: 2}) + G.add_edge('a', 't', {0: 1, 1: 5}) + G.add_edge('b', 'a', {0: 1, 1: 3}) + G.add_edge('b', 't', {0: 3, 1: 2}) + + "PS.ex.7.1: testing main function" + sol = nx.max_flow_min_cost(G, 's', 't', capacity=0, weight=1) + flow = sum(v for v in sol['s'].values()) + assert_equal(4, flow) + assert_equal(23, nx.cost_of_flow(G, sol, weight=1)) + assert_equal(sol['s'], {'a': 2, 'b': 2}) + assert_equal(sol['a'], {'b': 1, 't': 1}) + assert_equal(sol['b'], {'a': 0, 't': 3}) + assert_equal(sol['t'], {}) + + def test_zero_capacity_edges(self): + """Address issue raised in ticket #617 by arv.""" + G = nx.DiGraph() + G.add_edges_from([(1, 2, {'capacity': 1, 'weight': 1}), + (1, 5, {'capacity': 1, 'weight': 1}), + (2, 3, {'capacity': 0, 'weight': 1}), + (2, 5, {'capacity': 1, 'weight': 1}), + (5, 3, {'capacity': 2, 'weight': 1}), + (5, 4, {'capacity': 0, 'weight': 1}), + (3, 4, {'capacity': 2, 'weight': 1})]) + G.node[1]['demand'] = -1 + G.node[2]['demand'] = -1 + G.node[4]['demand'] = 2 + + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 0, 5: 1}, + 2: {3: 0, 5: 1}, + 3: {4: 2}, + 4: {}, + 5: {3: 2, 4: 0}} + assert_equal(flowCost, 6) + assert_equal(nx.min_cost_flow_cost(G), 6) + assert_equal(H, soln) + assert_equal(nx.min_cost_flow(G), soln) + assert_equal(nx.cost_of_flow(G, H), 6) + + def test_digon(self): + """Check if digons are handled properly. Taken from ticket + #618 by arv.""" + nodes = [(1, {}), + (2, {'demand': -4}), + (3, {'demand': 4}), + ] + edges = [(1, 2, {'capacity': 3, 'weight': 600000}), + (2, 1, {'capacity': 2, 'weight': 0}), + (2, 3, {'capacity': 5, 'weight': 714285}), + (3, 2, {'capacity': 2, 'weight': 0}), + ] + G = nx.DiGraph(edges) + G.add_nodes_from(nodes) + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 0}, + 2: {1: 0, 3: 4}, + 3: {2: 0}} + assert_equal(flowCost, 2857140) + assert_equal(nx.min_cost_flow_cost(G), 2857140) + assert_equal(H, soln) + assert_equal(nx.min_cost_flow(G), soln) + assert_equal(nx.cost_of_flow(G, H), 2857140) + + def test_infinite_capacity_neg_digon(self): + """An infinite capacity negative cost digon results in an unbounded + instance.""" + nodes = [(1, {}), + (2, {'demand': -4}), + (3, {'demand': 4}), + ] + edges = [(1, 2, {'weight': -600}), + (2, 1, {'weight': 0}), + (2, 3, {'capacity': 5, 'weight': 714285}), + (3, 2, {'capacity': 2, 'weight': 0}), + ] + G = nx.DiGraph(edges) + G.add_nodes_from(nodes) + assert_raises(nx.NetworkXUnbounded, nx.network_simplex, G) + + def test_finite_capacity_neg_digon(self): + """The digon should receive the maximum amount of flow it can handle. + Taken from ticket #749 by @chuongdo.""" + G = nx.DiGraph() + G.add_edge('a', 'b', capacity=1, weight=-1) + G.add_edge('b', 'a', capacity=1, weight=-1) + min_cost = -2 + assert_equal(nx.min_cost_flow_cost(G), min_cost) + + def test_multidigraph(self): + """Raise an exception for multidigraph.""" + G = nx.MultiDiGraph() + G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight='capacity') + assert_raises(nx.NetworkXError, nx.network_simplex, G) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/graphical.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/graphical.py new file mode 100644 index 0000000..5c82761 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/graphical.py @@ -0,0 +1,405 @@ +# -*- coding: utf-8 -*- +"""Test sequences for graphiness. +""" +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from collections import defaultdict +import heapq +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult (dschult@colgate.edu)' + 'Joel Miller (joel.c.miller.research@gmail.com)' + 'Ben Edwards' + 'Brian Cloteaux ']) + +__all__ = ['is_graphical', + 'is_multigraphical', + 'is_pseudographical', + 'is_digraphical', + 'is_valid_degree_sequence_erdos_gallai', + 'is_valid_degree_sequence_havel_hakimi', + 'is_valid_degree_sequence', # deprecated + ] + +def is_graphical(sequence, method='eg'): + """Returns True if sequence is a valid degree sequence. + + A degree sequence is valid if some graph can realize it. + + Parameters + ---------- + sequence : list or iterable container + A sequence of integer node degrees + + + method : "eg" | "hh" + The method used to validate the degree sequence. + "eg" corresponds to the Erdős-Gallai algorithm, and + "hh" to the Havel-Hakimi algorithm. + + Returns + ------- + valid : bool + True if the sequence is a valid degree sequence and False if not. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> sequence = G.degree().values() + >>> nx.is_valid_degree_sequence(sequence) + True + + References + ---------- + Erdős-Gallai + [EG1960]_, [choudum1986]_ + + Havel-Hakimi + [havel1955]_, [hakimi1962]_, [CL1996]_ + """ + if method == 'eg': + valid = is_valid_degree_sequence_erdos_gallai(list(sequence)) + elif method == 'hh': + valid = is_valid_degree_sequence_havel_hakimi(list(sequence)) + else: + msg = "`method` must be 'eg' or 'hh'" + raise nx.NetworkXException(msg) + return valid + +is_valid_degree_sequence = is_graphical + +def _basic_graphical_tests(deg_sequence): + # Sort and perform some simple tests on the sequence + if not nx.utils.is_list_of_ints(deg_sequence): + raise nx.NetworkXUnfeasible + p = len(deg_sequence) + num_degs = [0]*p + dmax, dmin, dsum, n = 0, p, 0, 0 + for d in deg_sequence: + # Reject if degree is negative or larger than the sequence length + if d<0 or d>=p: + raise nx.NetworkXUnfeasible + # Process only the non-zero integers + elif d>0: + dmax, dmin, dsum, n = max(dmax,d), min(dmin,d), dsum+d, n+1 + num_degs[d] += 1 + # Reject sequence if it has odd sum or is oversaturated + if dsum%2 or dsum>n*(n-1): + raise nx.NetworkXUnfeasible + return dmax,dmin,dsum,n,num_degs + +def is_valid_degree_sequence_havel_hakimi(deg_sequence): + r"""Returns True if deg_sequence can be realized by a simple graph. + + The validation proceeds using the Havel-Hakimi theorem. + Worst-case run time is: O(s) where s is the sum of the sequence. + + Parameters + ---------- + deg_sequence : list + A list of integers where each element specifies the degree of a node + in a graph. + + Returns + ------- + valid : bool + True if deg_sequence is graphical and False if not. + + Notes + ----- + The ZZ condition says that for the sequence d if + + .. math:: + |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)} + + then d is graphical. This was shown in Theorem 6 in [1]_. + + References + ---------- + .. [1] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory + of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992). + + [havel1955]_, [hakimi1962]_, [CL1996]_ + + """ + try: + dmax,dmin,dsum,n,num_degs = _basic_graphical_tests(deg_sequence) + except nx.NetworkXUnfeasible: + return False + # Accept if sequence has no non-zero degrees or passes the ZZ condition + if n==0 or 4*dmin*n >= (dmax+dmin+1) * (dmax+dmin+1): + return True + + modstubs = [0]*(dmax+1) + # Successively reduce degree sequence by removing the maximum degree + while n > 0: + # Retrieve the maximum degree in the sequence + while num_degs[dmax] == 0: + dmax -= 1; + # If there are not enough stubs to connect to, then the sequence is + # not graphical + if dmax > n-1: + return False + + # Remove largest stub in list + num_degs[dmax], n = num_degs[dmax]-1, n-1 + # Reduce the next dmax largest stubs + mslen = 0 + k = dmax + for i in range(dmax): + while num_degs[k] == 0: + k -= 1 + num_degs[k], n = num_degs[k]-1, n-1 + if k > 1: + modstubs[mslen] = k-1 + mslen += 1 + # Add back to the list any non-zero stubs that were removed + for i in range(mslen): + stub = modstubs[i] + num_degs[stub], n = num_degs[stub]+1, n+1 + return True + + +def is_valid_degree_sequence_erdos_gallai(deg_sequence): + r"""Returns True if deg_sequence can be realized by a simple graph. + + The validation is done using the Erdős-Gallai theorem [EG1960]_. + + Parameters + ---------- + deg_sequence : list + A list of integers + + Returns + ------- + valid : bool + True if deg_sequence is graphical and False if not. + + Notes + ----- + + This implementation uses an equivalent form of the Erdős-Gallai criterion. + Worst-case run time is: O(n) where n is the length of the sequence. + + Specifically, a sequence d is graphical if and only if the + sum of the sequence is even and for all strong indices k in the sequence, + + .. math:: + + \sum_{i=1}^{k} d_i \leq k(k-1) + \sum_{j=k+1}^{n} \min(d_i,k) + = k(n-1) - ( k \sum_{j=0}^{k-1} n_j - \sum_{j=0}^{k-1} j n_j ) + + A strong index k is any index where `d_k \geq k` and the value `n_j` is the + number of occurrences of j in d. The maximal strong index is called the + Durfee index. + + This particular rearrangement comes from the proof of Theorem 3 in [2]_. + + The ZZ condition says that for the sequence d if + + .. math:: + |d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)} + + then d is graphical. This was shown in Theorem 6 in [2]_. + + References + ---------- + .. [1] A. Tripathi and S. Vijay. "A note on a theorem of Erdős & Gallai", + Discrete Mathematics, 265, pp. 417-420 (2003). + .. [2] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory + of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992). + + [EG1960]_, [choudum1986]_ + """ + try: + dmax,dmin,dsum,n,num_degs = _basic_graphical_tests(deg_sequence) + except nx.NetworkXUnfeasible: + return False + # Accept if sequence has no non-zero degrees or passes the ZZ condition + if n==0 or 4*dmin*n >= (dmax+dmin+1) * (dmax+dmin+1): + return True + + # Perform the EG checks using the reformulation of Zverovich and Zverovich + k, sum_deg, sum_nj, sum_jnj = 0, 0, 0, 0 + for dk in range(dmax, dmin-1, -1): + if dk < k+1: # Check if already past Durfee index + return True + if num_degs[dk] > 0: + run_size = num_degs[dk] # Process a run of identical-valued degrees + if dk < k+run_size: # Check if end of run is past Durfee index + run_size = dk-k # Adjust back to Durfee index + sum_deg += run_size * dk + for v in range(run_size): + sum_nj += num_degs[k+v] + sum_jnj += (k+v) * num_degs[k+v] + k += run_size + if sum_deg > k*(n-1) - k*sum_nj + sum_jnj: + return False + return True + +def is_multigraphical(sequence): + """Returns True if some multigraph can realize the sequence. + + Parameters + ---------- + deg_sequence : list + A list of integers + + Returns + ------- + valid : bool + True if deg_sequence is a multigraphic degree sequence and False if not. + + Notes + ----- + The worst-case run time is O(n) where n is the length of the sequence. + + References + ---------- + .. [1] S. L. Hakimi. "On the realizability of a set of integers as + degrees of the vertices of a linear graph", J. SIAM, 10, pp. 496-506 + (1962). + """ + deg_sequence = list(sequence) + if not nx.utils.is_list_of_ints(deg_sequence): + return False + dsum, dmax = 0, 0 + for d in deg_sequence: + if d<0: + return False + dsum, dmax = dsum+d, max(dmax,d) + if dsum%2 or dsum<2*dmax: + return False + return True + +def is_pseudographical(sequence): + """Returns True if some pseudograph can realize the sequence. + + Every nonnegative integer sequence with an even sum is pseudographical + (see [1]_). + + Parameters + ---------- + sequence : list or iterable container + A sequence of integer node degrees + + Returns + ------- + valid : bool + True if the sequence is a pseudographic degree sequence and False if not. + + Notes + ----- + The worst-case run time is O(n) where n is the length of the sequence. + + References + ---------- + .. [1] F. Boesch and F. Harary. "Line removal algorithms for graphs + and their degree lists", IEEE Trans. Circuits and Systems, CAS-23(12), + pp. 778-782 (1976). + """ + s = list(sequence) + if not nx.utils.is_list_of_ints(s): + return False + return sum(s)%2 == 0 and min(s) >= 0 + +def is_digraphical(in_sequence, out_sequence): + r"""Returns True if some directed graph can realize the in- and out-degree + sequences. + + Parameters + ---------- + in_sequence : list or iterable container + A sequence of integer node in-degrees + + out_sequence : list or iterable container + A sequence of integer node out-degrees + + Returns + ------- + valid : bool + True if in and out-sequences are digraphic False if not. + + Notes + ----- + This algorithm is from Kleitman and Wang [1]_. + The worst case runtime is O(s * log n) where s and n are the sum and length + of the sequences respectively. + + References + ---------- + .. [1] D.J. Kleitman and D.L. Wang + Algorithms for Constructing Graphs and Digraphs with Given Valences + and Factors, Discrete Mathematics, 6(1), pp. 79-88 (1973) + """ + in_deg_sequence = list(in_sequence) + out_deg_sequence = list(out_sequence) + if not nx.utils.is_list_of_ints(in_deg_sequence): + return False + if not nx.utils.is_list_of_ints(out_deg_sequence): + return False + # Process the sequences and form two heaps to store degree pairs with + # either zero or non-zero out degrees + sumin, sumout, nin, nout = 0, 0, len(in_deg_sequence), len(out_deg_sequence) + maxn = max(nin, nout) + maxin = 0 + if maxn==0: + return True + stubheap, zeroheap = [ ], [ ] + for n in range(maxn): + in_deg, out_deg = 0, 0 + if n 0: + stubheap.append((-1*out_deg, -1*in_deg)) + elif out_deg > 0: + zeroheap.append(-1*out_deg) + if sumin != sumout: + return False + heapq.heapify(stubheap) + heapq.heapify(zeroheap) + + modstubs = [(0,0)]*(maxin+1) + # Successively reduce degree sequence by removing the maximum out degree + while stubheap: + # Take the first value in the sequence with non-zero in degree + (freeout, freein) = heapq.heappop( stubheap ) + freein *= -1 + if freein > len(stubheap)+len(zeroheap): + return False + + # Attach out stubs to the nodes with the most in stubs + mslen = 0 + for i in range(freein): + if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0]): + stubout = heapq.heappop(zeroheap) + stubin = 0 + else: + (stubout, stubin) = heapq.heappop(stubheap) + if stubout == 0: + return False + # Check if target is now totally connected + if stubout+1<0 or stubin<0: + modstubs[mslen] = (stubout+1, stubin) + mslen += 1 + + # Add back the nodes to the heap that still have available stubs + for i in range(mslen): + stub = modstubs[i] + if stub[1] < 0: + heapq.heappush(stubheap, stub) + else: + heapq.heappush(zeroheap, stub[0]) + if freeout<0: + heapq.heappush(zeroheap, freeout) + return True diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/hierarchy.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/hierarchy.py new file mode 100644 index 0000000..c38337b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/hierarchy.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +""" +Flow Hierarchy. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__authors__ = "\n".join(['Ben Edwards (bedwards@cs.unm.edu)']) +__all__ = ['flow_hierarchy'] + +def flow_hierarchy(G, weight=None): + """Returns the flow hierarchy of a directed network. + + Flow hierarchy is defined as the fraction of edges not participating + in cycles in a directed graph [1]_. + + Parameters + ---------- + G : DiGraph or MultiDiGraph + A directed graph + + weight : key,optional (default=None) + Attribute to use for node weights. If None the weight defaults to 1. + + Returns + ------- + h : float + Flow heirarchy value + + Notes + ----- + The algorithm described in [1]_ computes the flow hierarchy through + exponentiation of the adjacency matrix. This function implements an + alternative approach that finds strongly connected components. + An edge is in a cycle if and only if it is in a strongly connected + component, which can be found in `O(m)` time using Tarjan's algorithm. + + References + ---------- + .. [1] Luo, J.; Magee, C.L. (2011), + Detecting evolving patterns of self-organizing networks by flow + hierarchy measurement, Complexity, Volume 16 Issue 6 53-61. + DOI: 10.1002/cplx.20368 + http://web.mit.edu/~cmagee/www/documents/28-DetectingEvolvingPatterns_FlowHierarchy.pdf + """ + if not G.is_directed(): + raise nx.NetworkXError("G must be a digraph in flow_heirarchy") + scc = nx.strongly_connected_components(G) + return 1.-sum(G.subgraph(c).size(weight) for c in scc)/float(G.size(weight)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isolate.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isolate.py new file mode 100644 index 0000000..a14178b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isolate.py @@ -0,0 +1,77 @@ +# encoding: utf-8 +""" +Functions for identifying isolate (degree zero) nodes. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Drew Conway ', + 'Aric Hagberg ']) +__all__=['is_isolate','isolates'] + +def is_isolate(G,n): + """Determine of node n is an isolate (degree zero). + + Parameters + ---------- + G : graph + A networkx graph + n : node + A node in G + + Returns + ------- + isolate : bool + True if n has no neighbors, False otherwise. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_edge(1,2) + >>> G.add_node(3) + >>> nx.is_isolate(G,2) + False + >>> nx.is_isolate(G,3) + True + """ + return G.degree(n)==0 + +def isolates(G): + """Return list of isolates in the graph. + + Isolates are nodes with no neighbors (degree zero). + + Parameters + ---------- + G : graph + A networkx graph + + Returns + ------- + isolates : list + List of isolate nodes. + + Examples + -------- + >>> G = nx.Graph() + >>> G.add_edge(1,2) + >>> G.add_node(3) + >>> nx.isolates(G) + [3] + + To remove all isolates in the graph use + >>> G.remove_nodes_from(nx.isolates(G)) + >>> G.nodes() + [1, 2] + + For digraphs isolates have zero in-degree and zero out_degre + >>> G = nx.DiGraph([(0,1),(1,2)]) + >>> G.add_node(3) + >>> nx.isolates(G) + [3] + """ + return [n for (n,d) in G.degree_iter() if d==0] diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/__init__.py new file mode 100644 index 0000000..7821bc2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/__init__.py @@ -0,0 +1,4 @@ +from networkx.algorithms.isomorphism.isomorph import * +from networkx.algorithms.isomorphism.vf2userfunc import * +from networkx.algorithms.isomorphism.matchhelpers import * + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorph.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorph.py new file mode 100644 index 0000000..7de3308 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorph.py @@ -0,0 +1,227 @@ +""" +Graph isomorphism functions. +""" +import networkx as nx +from networkx.exception import NetworkXError +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Christopher Ellison cellison@cse.ucdavis.edu)']) +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__all__ = ['could_be_isomorphic', + 'fast_could_be_isomorphic', + 'faster_could_be_isomorphic', + 'is_isomorphic'] + +def could_be_isomorphic(G1,G2): + """Returns False if graphs are definitely not isomorphic. + True does NOT guarantee isomorphism. + + Parameters + ---------- + G1, G2 : graphs + The two graphs G1 and G2 must be the same type. + + Notes + ----- + Checks for matching degree, triangle, and number of cliques sequences. + """ + + # Check global properties + if G1.order() != G2.order(): return False + + # Check local properties + d1=G1.degree() + t1=nx.triangles(G1) + c1=nx.number_of_cliques(G1) + props1=[ [d1[v], t1[v], c1[v]] for v in d1 ] + props1.sort() + + d2=G2.degree() + t2=nx.triangles(G2) + c2=nx.number_of_cliques(G2) + props2=[ [d2[v], t2[v], c2[v]] for v in d2 ] + props2.sort() + + if props1 != props2: + return False + + # OK... + return True + +graph_could_be_isomorphic=could_be_isomorphic + +def fast_could_be_isomorphic(G1,G2): + """Returns False if graphs are definitely not isomorphic. + + True does NOT guarantee isomorphism. + + Parameters + ---------- + G1, G2 : graphs + The two graphs G1 and G2 must be the same type. + + Notes + ----- + Checks for matching degree and triangle sequences. + """ + # Check global properties + if G1.order() != G2.order(): return False + + # Check local properties + d1=G1.degree() + t1=nx.triangles(G1) + props1=[ [d1[v], t1[v]] for v in d1 ] + props1.sort() + + d2=G2.degree() + t2=nx.triangles(G2) + props2=[ [d2[v], t2[v]] for v in d2 ] + props2.sort() + + if props1 != props2: return False + + # OK... + return True + +fast_graph_could_be_isomorphic=fast_could_be_isomorphic + +def faster_could_be_isomorphic(G1,G2): + """Returns False if graphs are definitely not isomorphic. + + True does NOT guarantee isomorphism. + + Parameters + ---------- + G1, G2 : graphs + The two graphs G1 and G2 must be the same type. + + Notes + ----- + Checks for matching degree sequences. + """ + # Check global properties + if G1.order() != G2.order(): return False + + # Check local properties + d1=list(G1.degree().values()) + d1.sort() + d2=list(G2.degree().values()) + d2.sort() + + if d1 != d2: return False + + # OK... + return True + +faster_graph_could_be_isomorphic=faster_could_be_isomorphic + +def is_isomorphic(G1, G2, node_match=None, edge_match=None): + """Returns True if the graphs G1 and G2 are isomorphic and False otherwise. + + Parameters + ---------- + G1, G2: graphs + The two graphs G1 and G2 must be the same type. + + node_match : callable + A function that returns True if node n1 in G1 and n2 in G2 should + be considered equal during the isomorphism test. + If node_match is not specified then node attributes are not considered. + + The function will be called like + + node_match(G1.node[n1], G2.node[n2]). + + That is, the function will receive the node attribute dictionaries + for n1 and n2 as inputs. + + edge_match : callable + A function that returns True if the edge attribute dictionary + for the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should + be considered equal during the isomorphism test. If edge_match is + not specified then edge attributes are not considered. + + The function will be called like + + edge_match(G1[u1][v1], G2[u2][v2]). + + That is, the function will receive the edge attribute dictionaries + of the edges under consideration. + + Notes + ----- + Uses the vf2 algorithm [1]_. + + Examples + -------- + >>> import networkx.algorithms.isomorphism as iso + + For digraphs G1 and G2, using 'weight' edge attribute (default: 1) + + >>> G1 = nx.DiGraph() + >>> G2 = nx.DiGraph() + >>> G1.add_path([1,2,3,4],weight=1) + >>> G2.add_path([10,20,30,40],weight=2) + >>> em = iso.numerical_edge_match('weight', 1) + >>> nx.is_isomorphic(G1, G2) # no weights considered + True + >>> nx.is_isomorphic(G1, G2, edge_match=em) # match weights + False + + For multidigraphs G1 and G2, using 'fill' node attribute (default: '') + + >>> G1 = nx.MultiDiGraph() + >>> G2 = nx.MultiDiGraph() + >>> G1.add_nodes_from([1,2,3],fill='red') + >>> G2.add_nodes_from([10,20,30,40],fill='red') + >>> G1.add_path([1,2,3,4],weight=3, linewidth=2.5) + >>> G2.add_path([10,20,30,40],weight=3) + >>> nm = iso.categorical_node_match('fill', 'red') + >>> nx.is_isomorphic(G1, G2, node_match=nm) + True + + For multidigraphs G1 and G2, using 'weight' edge attribute (default: 7) + + >>> G1.add_edge(1,2, weight=7) + >>> G2.add_edge(10,20) + >>> em = iso.numerical_multiedge_match('weight', 7, rtol=1e-6) + >>> nx.is_isomorphic(G1, G2, edge_match=em) + True + + For multigraphs G1 and G2, using 'weight' and 'linewidth' edge attributes + with default values 7 and 2.5. Also using 'fill' node attribute with + default value 'red'. + + >>> em = iso.numerical_multiedge_match(['weight', 'linewidth'], [7, 2.5]) + >>> nm = iso.categorical_node_match('fill', 'red') + >>> nx.is_isomorphic(G1, G2, edge_match=em, node_match=nm) + True + + See Also + -------- + numerical_node_match, numerical_edge_match, numerical_multiedge_match + categorical_node_match, categorical_edge_match, categorical_multiedge_match + + References + ---------- + .. [1] L. P. Cordella, P. Foggia, C. Sansone, M. Vento, + "An Improved Algorithm for Matching Large Graphs", + 3rd IAPR-TC15 Workshop on Graph-based Representations in + Pattern Recognition, Cuen, pp. 149-159, 2001. + http://amalfi.dis.unina.it/graph/db/papers/vf-algorithm.pdf + """ + if G1.is_directed() and G2.is_directed(): + GM = nx.algorithms.isomorphism.DiGraphMatcher + elif (not G1.is_directed()) and (not G2.is_directed()): + GM = nx.algorithms.isomorphism.GraphMatcher + else: + raise NetworkXError("Graphs G1 and G2 are not of the same type.") + + gm = GM(G1, G2, node_match=node_match, edge_match=edge_match) + + return gm.is_isomorphic() diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py new file mode 100644 index 0000000..1efe74d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/isomorphvf2.py @@ -0,0 +1,965 @@ +# -*- coding: utf-8 -*- +""" +************* +VF2 Algorithm +************* + +An implementation of VF2 algorithm for graph ismorphism testing. + +The simplest interface to use this module is to call networkx.is_isomorphic(). + +Introduction +------------ + +The GraphMatcher and DiGraphMatcher are responsible for matching +graphs or directed graphs in a predetermined manner. This +usually means a check for an isomorphism, though other checks +are also possible. For example, a subgraph of one graph +can be checked for isomorphism to a second graph. + +Matching is done via syntactic feasibility. It is also possible +to check for semantic feasibility. Feasibility, then, is defined +as the logical AND of the two functions. + +To include a semantic check, the (Di)GraphMatcher class should be +subclassed, and the semantic_feasibility() function should be +redefined. By default, the semantic feasibility function always +returns True. The effect of this is that semantics are not +considered in the matching of G1 and G2. + +Examples +-------- + +Suppose G1 and G2 are isomorphic graphs. Verification is as follows: + +>>> from networkx.algorithms import isomorphism +>>> G1 = nx.path_graph(4) +>>> G2 = nx.path_graph(4) +>>> GM = isomorphism.GraphMatcher(G1,G2) +>>> GM.is_isomorphic() +True + +GM.mapping stores the isomorphism mapping from G1 to G2. + +>>> GM.mapping +{0: 0, 1: 1, 2: 2, 3: 3} + + +Suppose G1 and G2 are isomorphic directed graphs +graphs. Verification is as follows: + +>>> G1 = nx.path_graph(4, create_using=nx.DiGraph()) +>>> G2 = nx.path_graph(4, create_using=nx.DiGraph()) +>>> DiGM = isomorphism.DiGraphMatcher(G1,G2) +>>> DiGM.is_isomorphic() +True + +DiGM.mapping stores the isomorphism mapping from G1 to G2. + +>>> DiGM.mapping +{0: 0, 1: 1, 2: 2, 3: 3} + + + +Subgraph Isomorphism +-------------------- +Graph theory literature can be ambiguious about the meaning of the +above statement, and we seek to clarify it now. + +In the VF2 literature, a mapping M is said to be a graph-subgraph +isomorphism iff M is an isomorphism between G2 and a subgraph of G1. +Thus, to say that G1 and G2 are graph-subgraph isomorphic is to say +that a subgraph of G1 is isomorphic to G2. + +Other literature uses the phrase 'subgraph isomorphic' as in 'G1 does +not have a subgraph isomorphic to G2'. Another use is as an in adverb +for isomorphic. Thus, to say that G1 and G2 are subgraph isomorphic +is to say that a subgraph of G1 is isomorphic to G2. + +Finally, the term 'subgraph' can have multiple meanings. In this +context, 'subgraph' always means a 'node-induced subgraph'. Edge-induced +subgraph isomorphisms are not directly supported, but one should be +able to perform the check by making use of nx.line_graph(). For +subgraphs which are not induced, the term 'monomorphism' is preferred +over 'isomorphism'. Currently, it is not possible to check for +monomorphisms. + +Let G=(N,E) be a graph with a set of nodes N and set of edges E. + +If G'=(N',E') is a subgraph, then: + N' is a subset of N + E' is a subset of E + +If G'=(N',E') is a node-induced subgraph, then: + N' is a subset of N + E' is the subset of edges in E relating nodes in N' + +If G'=(N',E') is an edge-induced subgrpah, then: + N' is the subset of nodes in N related by edges in E' + E' is a subset of E + +References +---------- +[1] Luigi P. Cordella, Pasquale Foggia, Carlo Sansone, Mario Vento, + "A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs", + IEEE Transactions on Pattern Analysis and Machine Intelligence, + vol. 26, no. 10, pp. 1367-1372, Oct., 2004. + http://ieeexplore.ieee.org/iel5/34/29305/01323804.pdf + +[2] L. P. Cordella, P. Foggia, C. Sansone, M. Vento, "An Improved + Algorithm for Matching Large Graphs", 3rd IAPR-TC15 Workshop + on Graph-based Representations in Pattern Recognition, Cuen, + pp. 149-159, 2001. + http://amalfi.dis.unina.it/graph/db/papers/vf-algorithm.pdf + +See Also +-------- +syntactic_feasibliity(), semantic_feasibility() + +Notes +----- +Modified to handle undirected graphs. +Modified to handle multiple edges. + + +In general, this problem is NP-Complete. + + + +""" + +# Copyright (C) 2007-2009 by the NetworkX maintainers +# All rights reserved. +# BSD license. + +# This work was originally coded by Christopher Ellison +# as part of the Computational Mechanics Python (CMPy) project. +# James P. Crutchfield, principal investigator. +# Complexity Sciences Center and Physics Department, UC Davis. + +import sys +import networkx as nx + +__all__ = ['GraphMatcher', + 'DiGraphMatcher'] + +class GraphMatcher(object): + """Implementation of VF2 algorithm for matching undirected graphs. + + Suitable for Graph and MultiGraph instances. + """ + def __init__(self, G1, G2): + """Initialize GraphMatcher. + + Parameters + ---------- + G1,G2: NetworkX Graph or MultiGraph instances. + The two graphs to check for isomorphism. + + Examples + -------- + To create a GraphMatcher which checks for syntactic feasibility: + + >>> from networkx.algorithms import isomorphism + >>> G1 = nx.path_graph(4) + >>> G2 = nx.path_graph(4) + >>> GM = isomorphism.GraphMatcher(G1,G2) + """ + self.G1 = G1 + self.G2 = G2 + self.G1_nodes = set(G1.nodes()) + self.G2_nodes = set(G2.nodes()) + + # Set recursion limit. + self.old_recursion_limit = sys.getrecursionlimit() + expected_max_recursion_level = len(self.G2) + if self.old_recursion_limit < 1.5 * expected_max_recursion_level: + # Give some breathing room. + sys.setrecursionlimit(int(1.5 * expected_max_recursion_level)) + + # Declare that we will be searching for a graph-graph isomorphism. + self.test = 'graph' + + # Initialize state + self.initialize() + + def reset_recursion_limit(self): + """Restores the recursion limit.""" + ### TODO: + ### Currently, we use recursion and set the recursion level higher. + ### It would be nice to restore the level, but because the + ### (Di)GraphMatcher classes make use of cyclic references, garbage + ### collection will never happen when we define __del__() to + ### restore the recursion level. The result is a memory leak. + ### So for now, we do not automatically restore the recursion level, + ### and instead provide a method to do this manually. Eventually, + ### we should turn this into a non-recursive implementation. + sys.setrecursionlimit(self.old_recursion_limit) + + def candidate_pairs_iter(self): + """Iterator over candidate pairs of nodes in G1 and G2.""" + + # All computations are done using the current state! + + G1_nodes = self.G1_nodes + G2_nodes = self.G2_nodes + + # First we compute the inout-terminal sets. + T1_inout = [node for node in G1_nodes if (node in self.inout_1) and (node not in self.core_1)] + T2_inout = [node for node in G2_nodes if (node in self.inout_2) and (node not in self.core_2)] + + # If T1_inout and T2_inout are both nonempty. + # P(s) = T1_inout x {min T2_inout} + if T1_inout and T2_inout: + for node in T1_inout: + yield node, min(T2_inout) + + else: + # If T1_inout and T2_inout were both empty.... + # P(s) = (N_1 - M_1) x {min (N_2 - M_2)} + ##if not (T1_inout or T2_inout): # as suggested by [2], incorrect + if 1: # as inferred from [1], correct + # First we determine the candidate node for G2 + other_node = min(G2_nodes - set(self.core_2)) + for node in self.G1: + if node not in self.core_1: + yield node, other_node + + # For all other cases, we don't have any candidate pairs. + + def initialize(self): + """Reinitializes the state of the algorithm. + + This method should be redefined if using something other than GMState. + If only subclassing GraphMatcher, a redefinition is not necessary. + + """ + + # core_1[n] contains the index of the node paired with n, which is m, + # provided n is in the mapping. + # core_2[m] contains the index of the node paired with m, which is n, + # provided m is in the mapping. + self.core_1 = {} + self.core_2 = {} + + # See the paper for definitions of M_x and T_x^{y} + + # inout_1[n] is non-zero if n is in M_1 or in T_1^{inout} + # inout_2[m] is non-zero if m is in M_2 or in T_2^{inout} + # + # The value stored is the depth of the SSR tree when the node became + # part of the corresponding set. + self.inout_1 = {} + self.inout_2 = {} + # Practically, these sets simply store the nodes in the subgraph. + + self.state = GMState(self) + + # Provide a convienient way to access the isomorphism mapping. + self.mapping = self.core_1.copy() + + def is_isomorphic(self): + """Returns True if G1 and G2 are isomorphic graphs.""" + + # Let's do two very quick checks! + # QUESTION: Should we call faster_graph_could_be_isomorphic(G1,G2)? + # For now, I just copy the code. + + # Check global properties + if self.G1.order() != self.G2.order(): return False + + # Check local properties + d1=sorted(self.G1.degree().values()) + d2=sorted(self.G2.degree().values()) + if d1 != d2: return False + + try: + x = next(self.isomorphisms_iter()) + return True + except StopIteration: + return False + + def isomorphisms_iter(self): + """Generator over isomorphisms between G1 and G2.""" + # Declare that we are looking for a graph-graph isomorphism. + self.test = 'graph' + self.initialize() + for mapping in self.match(): + yield mapping + + def match(self): + """Extends the isomorphism mapping. + + This function is called recursively to determine if a complete + isomorphism can be found between G1 and G2. It cleans up the class + variables after each recursive call. If an isomorphism is found, + we yield the mapping. + + """ + if len(self.core_1) == len(self.G2): + # Save the final mapping, otherwise garbage collection deletes it. + self.mapping = self.core_1.copy() + # The mapping is complete. + yield self.mapping + else: + for G1_node, G2_node in self.candidate_pairs_iter(): + if self.syntactic_feasibility(G1_node, G2_node): + if self.semantic_feasibility(G1_node, G2_node): + # Recursive call, adding the feasible state. + newstate = self.state.__class__(self, G1_node, G2_node) + for mapping in self.match(): + yield mapping + + # restore data structures + newstate.restore() + + def semantic_feasibility(self, G1_node, G2_node): + """Returns True if adding (G1_node, G2_node) is symantically feasible. + + The semantic feasibility function should return True if it is + acceptable to add the candidate pair (G1_node, G2_node) to the current + partial isomorphism mapping. The logic should focus on semantic + information contained in the edge data or a formalized node class. + + By acceptable, we mean that the subsequent mapping can still become a + complete isomorphism mapping. Thus, if adding the candidate pair + definitely makes it so that the subsequent mapping cannot become a + complete isomorphism mapping, then this function must return False. + + The default semantic feasibility function always returns True. The + effect is that semantics are not considered in the matching of G1 + and G2. + + The semantic checks might differ based on the what type of test is + being performed. A keyword description of the test is stored in + self.test. Here is a quick description of the currently implemented + tests:: + + test='graph' + Indicates that the graph matcher is looking for a graph-graph + isomorphism. + + test='subgraph' + Indicates that the graph matcher is looking for a subgraph-graph + isomorphism such that a subgraph of G1 is isomorphic to G2. + + Any subclass which redefines semantic_feasibility() must maintain + the above form to keep the match() method functional. Implementations + should consider multigraphs. + """ + return True + + def subgraph_is_isomorphic(self): + """Returns True if a subgraph of G1 is isomorphic to G2.""" + try: + x = next(self.subgraph_isomorphisms_iter()) + return True + except StopIteration: + return False + +# subgraph_is_isomorphic.__doc__ += "\n" + subgraph.replace('\n','\n'+indent) + + def subgraph_isomorphisms_iter(self): + """Generator over isomorphisms between a subgraph of G1 and G2.""" + # Declare that we are looking for graph-subgraph isomorphism. + self.test = 'subgraph' + self.initialize() + for mapping in self.match(): + yield mapping + +# subgraph_isomorphisms_iter.__doc__ += "\n" + subgraph.replace('\n','\n'+indent) + + def syntactic_feasibility(self, G1_node, G2_node): + """Returns True if adding (G1_node, G2_node) is syntactically feasible. + + This function returns True if it is adding the candidate pair + to the current partial isomorphism mapping is allowable. The addition + is allowable if the inclusion of the candidate pair does not make it + impossible for an isomorphism to be found. + """ + + # The VF2 algorithm was designed to work with graphs having, at most, + # one edge connecting any two nodes. This is not the case when + # dealing with an MultiGraphs. + # + # Basically, when we test the look-ahead rules R_neighbor, we will + # make sure that the number of edges are checked. We also add + # a R_self check to verify that the number of selfloops is acceptable. + # + # Users might be comparing Graph instances with MultiGraph instances. + # So the generic GraphMatcher class must work with MultiGraphs. + # Care must be taken since the value in the innermost dictionary is a + # singlet for Graph instances. For MultiGraphs, the value in the + # innermost dictionary is a list. + + + ### + ### Test at each step to get a return value as soon as possible. + ### + + ### Look ahead 0 + + # R_self + + # The number of selfloops for G1_node must equal the number of + # self-loops for G2_node. Without this check, we would fail on + # R_neighbor at the next recursion level. But it is good to prune the + # search tree now. + if self.G1.number_of_edges(G1_node,G1_node) != self.G2.number_of_edges(G2_node,G2_node): + return False + + + # R_neighbor + + # For each neighbor n' of n in the partial mapping, the corresponding + # node m' is a neighbor of m, and vice versa. Also, the number of + # edges must be equal. + for neighbor in self.G1[G1_node]: + if neighbor in self.core_1: + if not (self.core_1[neighbor] in self.G2[G2_node]): + return False + elif self.G1.number_of_edges(neighbor, G1_node) != self.G2.number_of_edges(self.core_1[neighbor], G2_node): + return False + for neighbor in self.G2[G2_node]: + if neighbor in self.core_2: + if not (self.core_2[neighbor] in self.G1[G1_node]): + return False + elif self.G1.number_of_edges(self.core_2[neighbor], G1_node) != self.G2.number_of_edges(neighbor, G2_node): + return False + + ### Look ahead 1 + + # R_terminout + # The number of neighbors of n that are in T_1^{inout} is equal to the + # number of neighbors of m that are in T_2^{inout}, and vice versa. + num1 = 0 + for neighbor in self.G1[G1_node]: + if (neighbor in self.inout_1) and (neighbor not in self.core_1): + num1 += 1 + num2 = 0 + for neighbor in self.G2[G2_node]: + if (neighbor in self.inout_2) and (neighbor not in self.core_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + + ### Look ahead 2 + + # R_new + + # The number of neighbors of n that are neither in the core_1 nor + # T_1^{inout} is equal to the number of neighbors of m + # that are neither in core_2 nor T_2^{inout}. + num1 = 0 + for neighbor in self.G1[G1_node]: + if neighbor not in self.inout_1: + num1 += 1 + num2 = 0 + for neighbor in self.G2[G2_node]: + if neighbor not in self.inout_2: + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + # Otherwise, this node pair is syntactically feasible! + return True + + +class DiGraphMatcher(GraphMatcher): + """Implementation of VF2 algorithm for matching directed graphs. + + Suitable for DiGraph and MultiDiGraph instances. + """ +# __doc__ += "Notes\n%s-----" % (indent,) + sources.replace('\n','\n'+indent) + + def __init__(self, G1, G2): + """Initialize DiGraphMatcher. + + G1 and G2 should be nx.Graph or nx.MultiGraph instances. + + Examples + -------- + To create a GraphMatcher which checks for syntactic feasibility: + + >>> from networkx.algorithms import isomorphism + >>> G1 = nx.DiGraph(nx.path_graph(4, create_using=nx.DiGraph())) + >>> G2 = nx.DiGraph(nx.path_graph(4, create_using=nx.DiGraph())) + >>> DiGM = isomorphism.DiGraphMatcher(G1,G2) + """ + super(DiGraphMatcher, self).__init__(G1, G2) + + def candidate_pairs_iter(self): + """Iterator over candidate pairs of nodes in G1 and G2.""" + + # All computations are done using the current state! + + G1_nodes = self.G1_nodes + G2_nodes = self.G2_nodes + + # First we compute the out-terminal sets. + T1_out = [node for node in G1_nodes if (node in self.out_1) and (node not in self.core_1)] + T2_out = [node for node in G2_nodes if (node in self.out_2) and (node not in self.core_2)] + + # If T1_out and T2_out are both nonempty. + # P(s) = T1_out x {min T2_out} + if T1_out and T2_out: + node_2 = min(T2_out) + for node_1 in T1_out: + yield node_1, node_2 + + # If T1_out and T2_out were both empty.... + # We compute the in-terminal sets. + + ##elif not (T1_out or T2_out): # as suggested by [2], incorrect + else: # as suggested by [1], correct + T1_in = [node for node in G1_nodes if (node in self.in_1) and (node not in self.core_1)] + T2_in = [node for node in G2_nodes if (node in self.in_2) and (node not in self.core_2)] + + # If T1_in and T2_in are both nonempty. + # P(s) = T1_out x {min T2_out} + if T1_in and T2_in: + node_2 = min(T2_in) + for node_1 in T1_in: + yield node_1, node_2 + + # If all terminal sets are empty... + # P(s) = (N_1 - M_1) x {min (N_2 - M_2)} + + ##elif not (T1_in or T2_in): # as suggested by [2], incorrect + else: # as inferred from [1], correct + node_2 = min(G2_nodes - set(self.core_2)) + for node_1 in G1_nodes: + if node_1 not in self.core_1: + yield node_1, node_2 + + # For all other cases, we don't have any candidate pairs. + + def initialize(self): + """Reinitializes the state of the algorithm. + + This method should be redefined if using something other than DiGMState. + If only subclassing GraphMatcher, a redefinition is not necessary. + """ + + # core_1[n] contains the index of the node paired with n, which is m, + # provided n is in the mapping. + # core_2[m] contains the index of the node paired with m, which is n, + # provided m is in the mapping. + self.core_1 = {} + self.core_2 = {} + + # See the paper for definitions of M_x and T_x^{y} + + # in_1[n] is non-zero if n is in M_1 or in T_1^{in} + # out_1[n] is non-zero if n is in M_1 or in T_1^{out} + # + # in_2[m] is non-zero if m is in M_2 or in T_2^{in} + # out_2[m] is non-zero if m is in M_2 or in T_2^{out} + # + # The value stored is the depth of the search tree when the node became + # part of the corresponding set. + self.in_1 = {} + self.in_2 = {} + self.out_1 = {} + self.out_2 = {} + + self.state = DiGMState(self) + + # Provide a convienient way to access the isomorphism mapping. + self.mapping = self.core_1.copy() + + def syntactic_feasibility(self, G1_node, G2_node): + """Returns True if adding (G1_node, G2_node) is syntactically feasible. + + This function returns True if it is adding the candidate pair + to the current partial isomorphism mapping is allowable. The addition + is allowable if the inclusion of the candidate pair does not make it + impossible for an isomorphism to be found. + """ + + # The VF2 algorithm was designed to work with graphs having, at most, + # one edge connecting any two nodes. This is not the case when + # dealing with an MultiGraphs. + # + # Basically, when we test the look-ahead rules R_pred and R_succ, we + # will make sure that the number of edges are checked. We also add + # a R_self check to verify that the number of selfloops is acceptable. + + # Users might be comparing DiGraph instances with MultiDiGraph + # instances. So the generic DiGraphMatcher class must work with + # MultiDiGraphs. Care must be taken since the value in the innermost + # dictionary is a singlet for DiGraph instances. For MultiDiGraphs, + # the value in the innermost dictionary is a list. + + + ### + ### Test at each step to get a return value as soon as possible. + ### + + + + ### Look ahead 0 + + # R_self + + # The number of selfloops for G1_node must equal the number of + # self-loops for G2_node. Without this check, we would fail on R_pred + # at the next recursion level. This should prune the tree even further. + + if self.G1.number_of_edges(G1_node,G1_node) != self.G2.number_of_edges(G2_node,G2_node): + return False + + + # R_pred + + # For each predecessor n' of n in the partial mapping, the + # corresponding node m' is a predecessor of m, and vice versa. Also, + # the number of edges must be equal + for predecessor in self.G1.pred[G1_node]: + if predecessor in self.core_1: + if not (self.core_1[predecessor] in self.G2.pred[G2_node]): + return False + elif self.G1.number_of_edges(predecessor, G1_node) != self.G2.number_of_edges(self.core_1[predecessor], G2_node): + return False + + for predecessor in self.G2.pred[G2_node]: + if predecessor in self.core_2: + if not (self.core_2[predecessor] in self.G1.pred[G1_node]): + return False + elif self.G1.number_of_edges(self.core_2[predecessor], G1_node) != self.G2.number_of_edges(predecessor, G2_node): + return False + + + # R_succ + + # For each successor n' of n in the partial mapping, the corresponding + # node m' is a successor of m, and vice versa. Also, the number of + # edges must be equal. + for successor in self.G1[G1_node]: + if successor in self.core_1: + if not (self.core_1[successor] in self.G2[G2_node]): + return False + elif self.G1.number_of_edges(G1_node, successor) != self.G2.number_of_edges(G2_node, self.core_1[successor]): + return False + + for successor in self.G2[G2_node]: + if successor in self.core_2: + if not (self.core_2[successor] in self.G1[G1_node]): + return False + elif self.G1.number_of_edges(G1_node, self.core_2[successor]) != self.G2.number_of_edges(G2_node, successor): + return False + + + ### Look ahead 1 + + # R_termin + # The number of predecessors of n that are in T_1^{in} is equal to the + # number of predecessors of m that are in T_2^{in}. + num1 = 0 + for predecessor in self.G1.pred[G1_node]: + if (predecessor in self.in_1) and (predecessor not in self.core_1): + num1 += 1 + num2 = 0 + for predecessor in self.G2.pred[G2_node]: + if (predecessor in self.in_2) and (predecessor not in self.core_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + # The number of successors of n that are in T_1^{in} is equal to the + # number of successors of m that are in T_2^{in}. + num1 = 0 + for successor in self.G1[G1_node]: + if (successor in self.in_1) and (successor not in self.core_1): + num1 += 1 + num2 = 0 + for successor in self.G2[G2_node]: + if (successor in self.in_2) and (successor not in self.core_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + # R_termout + + # The number of predecessors of n that are in T_1^{out} is equal to the + # number of predecessors of m that are in T_2^{out}. + num1 = 0 + for predecessor in self.G1.pred[G1_node]: + if (predecessor in self.out_1) and (predecessor not in self.core_1): + num1 += 1 + num2 = 0 + for predecessor in self.G2.pred[G2_node]: + if (predecessor in self.out_2) and (predecessor not in self.core_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + # The number of successors of n that are in T_1^{out} is equal to the + # number of successors of m that are in T_2^{out}. + num1 = 0 + for successor in self.G1[G1_node]: + if (successor in self.out_1) and (successor not in self.core_1): + num1 += 1 + num2 = 0 + for successor in self.G2[G2_node]: + if (successor in self.out_2) and (successor not in self.core_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + ### Look ahead 2 + + # R_new + + # The number of predecessors of n that are neither in the core_1 nor + # T_1^{in} nor T_1^{out} is equal to the number of predecessors of m + # that are neither in core_2 nor T_2^{in} nor T_2^{out}. + num1 = 0 + for predecessor in self.G1.pred[G1_node]: + if (predecessor not in self.in_1) and (predecessor not in self.out_1): + num1 += 1 + num2 = 0 + for predecessor in self.G2.pred[G2_node]: + if (predecessor not in self.in_2) and (predecessor not in self.out_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + # The number of successors of n that are neither in the core_1 nor + # T_1^{in} nor T_1^{out} is equal to the number of successors of m + # that are neither in core_2 nor T_2^{in} nor T_2^{out}. + num1 = 0 + for successor in self.G1[G1_node]: + if (successor not in self.in_1) and (successor not in self.out_1): + num1 += 1 + num2 = 0 + for successor in self.G2[G2_node]: + if (successor not in self.in_2) and (successor not in self.out_2): + num2 += 1 + if self.test == 'graph': + if not (num1 == num2): + return False + else: # self.test == 'subgraph' + if not (num1 >= num2): + return False + + # Otherwise, this node pair is syntactically feasible! + return True + + +class GMState(object): + """Internal representation of state for the GraphMatcher class. + + This class is used internally by the GraphMatcher class. It is used + only to store state specific data. There will be at most G2.order() of + these objects in memory at a time, due to the depth-first search + strategy employed by the VF2 algorithm. + """ + def __init__(self, GM, G1_node=None, G2_node=None): + """Initializes GMState object. + + Pass in the GraphMatcher to which this GMState belongs and the + new node pair that will be added to the GraphMatcher's current + isomorphism mapping. + """ + self.GM = GM + + # Initialize the last stored node pair. + self.G1_node = None + self.G2_node = None + self.depth = len(GM.core_1) + + if G1_node is None or G2_node is None: + # Then we reset the class variables + GM.core_1 = {} + GM.core_2 = {} + GM.inout_1 = {} + GM.inout_2 = {} + + # Watch out! G1_node == 0 should evaluate to True. + if G1_node is not None and G2_node is not None: + # Add the node pair to the isomorphism mapping. + GM.core_1[G1_node] = G2_node + GM.core_2[G2_node] = G1_node + + # Store the node that was added last. + self.G1_node = G1_node + self.G2_node = G2_node + + # Now we must update the other two vectors. + # We will add only if it is not in there already! + self.depth = len(GM.core_1) + + # First we add the new nodes... + if G1_node not in GM.inout_1: + GM.inout_1[G1_node] = self.depth + if G2_node not in GM.inout_2: + GM.inout_2[G2_node] = self.depth + + # Now we add every other node... + + # Updates for T_1^{inout} + new_nodes = set([]) + for node in GM.core_1: + new_nodes.update([neighbor for neighbor in GM.G1[node] if neighbor not in GM.core_1]) + for node in new_nodes: + if node not in GM.inout_1: + GM.inout_1[node] = self.depth + + # Updates for T_2^{inout} + new_nodes = set([]) + for node in GM.core_2: + new_nodes.update([neighbor for neighbor in GM.G2[node] if neighbor not in GM.core_2]) + for node in new_nodes: + if node not in GM.inout_2: + GM.inout_2[node] = self.depth + + def restore(self): + """Deletes the GMState object and restores the class variables.""" + # First we remove the node that was added from the core vectors. + # Watch out! G1_node == 0 should evaluate to True. + if self.G1_node is not None and self.G2_node is not None: + del self.GM.core_1[self.G1_node] + del self.GM.core_2[self.G2_node] + + # Now we revert the other two vectors. + # Thus, we delete all entries which have this depth level. + for vector in (self.GM.inout_1, self.GM.inout_2): + for node in list(vector.keys()): + if vector[node] == self.depth: + del vector[node] + + +class DiGMState(object): + """Internal representation of state for the DiGraphMatcher class. + + This class is used internally by the DiGraphMatcher class. It is used + only to store state specific data. There will be at most G2.order() of + these objects in memory at a time, due to the depth-first search + strategy employed by the VF2 algorithm. + + """ + def __init__(self, GM, G1_node=None, G2_node=None): + """Initializes DiGMState object. + + Pass in the DiGraphMatcher to which this DiGMState belongs and the + new node pair that will be added to the GraphMatcher's current + isomorphism mapping. + """ + self.GM = GM + + # Initialize the last stored node pair. + self.G1_node = None + self.G2_node = None + self.depth = len(GM.core_1) + + if G1_node is None or G2_node is None: + # Then we reset the class variables + GM.core_1 = {} + GM.core_2 = {} + GM.in_1 = {} + GM.in_2 = {} + GM.out_1 = {} + GM.out_2 = {} + + # Watch out! G1_node == 0 should evaluate to True. + if G1_node is not None and G2_node is not None: + # Add the node pair to the isomorphism mapping. + GM.core_1[G1_node] = G2_node + GM.core_2[G2_node] = G1_node + + # Store the node that was added last. + self.G1_node = G1_node + self.G2_node = G2_node + + # Now we must update the other four vectors. + # We will add only if it is not in there already! + self.depth = len(GM.core_1) + + # First we add the new nodes... + for vector in (GM.in_1, GM.out_1): + if G1_node not in vector: + vector[G1_node] = self.depth + for vector in (GM.in_2, GM.out_2): + if G2_node not in vector: + vector[G2_node] = self.depth + + # Now we add every other node... + + # Updates for T_1^{in} + new_nodes = set([]) + for node in GM.core_1: + new_nodes.update([predecessor for predecessor in GM.G1.predecessors(node) if predecessor not in GM.core_1]) + for node in new_nodes: + if node not in GM.in_1: + GM.in_1[node] = self.depth + + # Updates for T_2^{in} + new_nodes = set([]) + for node in GM.core_2: + new_nodes.update([predecessor for predecessor in GM.G2.predecessors(node) if predecessor not in GM.core_2]) + for node in new_nodes: + if node not in GM.in_2: + GM.in_2[node] = self.depth + + # Updates for T_1^{out} + new_nodes = set([]) + for node in GM.core_1: + new_nodes.update([successor for successor in GM.G1.successors(node) if successor not in GM.core_1]) + for node in new_nodes: + if node not in GM.out_1: + GM.out_1[node] = self.depth + + # Updates for T_2^{out} + new_nodes = set([]) + for node in GM.core_2: + new_nodes.update([successor for successor in GM.G2.successors(node) if successor not in GM.core_2]) + for node in new_nodes: + if node not in GM.out_2: + GM.out_2[node] = self.depth + + def restore(self): + """Deletes the DiGMState object and restores the class variables.""" + + # First we remove the node that was added from the core vectors. + # Watch out! G1_node == 0 should evaluate to True. + if self.G1_node is not None and self.G2_node is not None: + del self.GM.core_1[self.G1_node] + del self.GM.core_2[self.G2_node] + + # Now we revert the other four vectors. + # Thus, we delete all entries which have this depth level. + for vector in (self.GM.in_1, self.GM.in_2, self.GM.out_1, self.GM.out_2): + for node in list(vector.keys()): + if vector[node] == self.depth: + del vector[node] + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/matchhelpers.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/matchhelpers.py new file mode 100644 index 0000000..f9af38b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/matchhelpers.py @@ -0,0 +1,346 @@ +"""Functions which help end users define customize node_match and +edge_match functions to use during isomorphism checks. +""" +from itertools import permutations +import types +import networkx as nx + +__all__ = ['categorical_node_match', + 'categorical_edge_match', + 'categorical_multiedge_match', + 'numerical_node_match', + 'numerical_edge_match', + 'numerical_multiedge_match', + 'generic_node_match', + 'generic_edge_match', + 'generic_multiedge_match', + ] + + +def copyfunc(f, name=None): + """Returns a deepcopy of a function.""" + try: + return types.FunctionType(f.func_code, f.func_globals, name or f.name, + f.func_defaults, f.func_closure) + except AttributeError: + return types.FunctionType(f.__code__, f.__globals__, name or f.name, + f.__defaults__, f.__closure__) + +def allclose(x, y, rtol=1.0000000000000001e-05, atol=1e-08): + """Returns True if x and y are sufficiently close, elementwise. + + Parameters + ---------- + rtol : float + The relative error tolerance. + atol : float + The absolute error tolerance. + + """ + # assume finite weights, see numpy.allclose() for reference + for xi, yi in zip(x,y): + if not ( abs(xi-yi) <= atol + rtol * abs(yi) ): + return False + return True + + +def close(x, y, rtol=1.0000000000000001e-05, atol=1e-08): + """Returns True if x and y are sufficiently close. + + Parameters + ---------- + rtol : float + The relative error tolerance. + atol : float + The absolute error tolerance. + + """ + # assume finite weights, see numpy.allclose() for reference + return abs(x-y) <= atol + rtol * abs(y) + + +categorical_doc = """ +Returns a comparison function for a categorical node attribute. + +The value(s) of the attr(s) must be hashable and comparable via the == +operator since they are placed into a set([]) object. If the sets from +G1 and G2 are the same, then the constructed function returns True. + +Parameters +---------- +attr : string | list + The categorical node attribute to compare, or a list of categorical + node attributes to compare. +default : value | list + The default value for the categorical node attribute, or a list of + default values for the categorical node attributes. + +Returns +------- +match : function + The customized, categorical `node_match` function. + +Examples +-------- +>>> import networkx.algorithms.isomorphism as iso +>>> nm = iso.categorical_node_match('size', 1) +>>> nm = iso.categorical_node_match(['color', 'size'], ['red', 2]) + +""" + +def categorical_node_match(attr, default): + if nx.utils.is_string_like(attr): + def match(data1, data2): + return data1.get(attr, default) == data2.get(attr, default) + else: + attrs = list(zip(attr, default)) # Python 3 + def match(data1, data2): + values1 = set([data1.get(attr, d) for attr, d in attrs]) + values2 = set([data2.get(attr, d) for attr, d in attrs]) + return values1 == values2 + return match + +categorical_edge_match = copyfunc(categorical_node_match, 'categorical_edge_match') + +def categorical_multiedge_match(attr, default): + if nx.utils.is_string_like(attr): + def match(datasets1, datasets2): + values1 = set([data.get(attr, default) for data in datasets1.values()]) + values2 = set([data.get(attr, default) for data in datasets2.values()]) + return values1 == values2 + else: + attrs = list(zip(attr, default)) # Python 3 + def match(datasets1, datasets2): + values1 = set([]) + for data1 in datasets1.values(): + x = tuple( data1.get(attr, d) for attr, d in attrs ) + values1.add(x) + values2 = set([]) + for data2 in datasets2.values(): + x = tuple( data2.get(attr, d) for attr, d in attrs ) + values2.add(x) + return values1 == values2 + return match + +# Docstrings for categorical functions. +categorical_node_match.__doc__ = categorical_doc +categorical_edge_match.__doc__ = categorical_doc.replace('node', 'edge') +tmpdoc = categorical_doc.replace('node', 'edge') +tmpdoc = tmpdoc.replace('categorical_edge_match', 'categorical_multiedge_match') +categorical_multiedge_match.__doc__ = tmpdoc + + +numerical_doc = """ +Returns a comparison function for a numerical node attribute. + +The value(s) of the attr(s) must be numerical and sortable. If the +sorted list of values from G1 and G2 are the same within some +tolerance, then the constructed function returns True. + +Parameters +---------- +attr : string | list + The numerical node attribute to compare, or a list of numerical + node attributes to compare. +default : value | list + The default value for the numerical node attribute, or a list of + default values for the numerical node attributes. +rtol : float + The relative error tolerance. +atol : float + The absolute error tolerance. + +Returns +------- +match : function + The customized, numerical `node_match` function. + +Examples +-------- +>>> import networkx.algorithms.isomorphism as iso +>>> nm = iso.numerical_node_match('weight', 1.0) +>>> nm = iso.numerical_node_match(['weight', 'linewidth'], [.25, .5]) + +""" + +def numerical_node_match(attr, default, rtol=1.0000000000000001e-05, atol=1e-08): + if nx.utils.is_string_like(attr): + def match(data1, data2): + return close(data1.get(attr, default), + data2.get(attr, default), + rtol=rtol, atol=atol) + else: + attrs = list(zip(attr, default)) # Python 3 + def match(data1, data2): + values1 = [data1.get(attr, d) for attr, d in attrs] + values2 = [data2.get(attr, d) for attr, d in attrs] + return allclose(values1, values2, rtol=rtol, atol=atol) + return match + +numerical_edge_match = copyfunc(numerical_node_match, 'numerical_edge_match') + +def numerical_multiedge_match(attr, default, rtol=1.0000000000000001e-05, atol=1e-08): + if nx.utils.is_string_like(attr): + def match(datasets1, datasets2): + values1 = sorted([data.get(attr, default) for data in datasets1.values()]) + values2 = sorted([data.get(attr, default) for data in datasets2.values()]) + return allclose(values1, values2, rtol=rtol, atol=atol) + else: + attrs = list(zip(attr, default)) # Python 3 + def match(datasets1, datasets2): + values1 = [] + for data1 in datasets1.values(): + x = tuple( data1.get(attr, d) for attr, d in attrs ) + values1.append(x) + values2 = [] + for data2 in datasets2.values(): + x = tuple( data2.get(attr, d) for attr, d in attrs ) + values2.append(x) + values1.sort() + values2.sort() + for xi, yi in zip(values1, values2): + if not allclose(xi, yi, rtol=rtol, atol=atol): + return False + else: + return True + return match + +# Docstrings for numerical functions. +numerical_node_match.__doc__ = numerical_doc +numerical_edge_match.__doc__ = numerical_doc.replace('node', 'edge') +tmpdoc = numerical_doc.replace('node', 'edge') +tmpdoc = tmpdoc.replace('numerical_edge_match', 'numerical_multiedge_match') +numerical_multiedge_match.__doc__ = tmpdoc + + +generic_doc = """ +Returns a comparison function for a generic attribute. + +The value(s) of the attr(s) are compared using the specified +operators. If all the attributes are equal, then the constructed +function returns True. + +Parameters +---------- +attr : string | list + The node attribute to compare, or a list of node attributes + to compare. +default : value | list + The default value for the node attribute, or a list of + default values for the node attributes. +op : callable | list + The operator to use when comparing attribute values, or a list + of operators to use when comparing values for each attribute. + +Returns +------- +match : function + The customized, generic `node_match` function. + +Examples +-------- +>>> from operator import eq +>>> from networkx.algorithms.isomorphism.matchhelpers import close +>>> from networkx.algorithms.isomorphism import generic_node_match +>>> nm = generic_node_match('weight', 1.0, close) +>>> nm = generic_node_match('color', 'red', eq) +>>> nm = generic_node_match(['weight', 'color'], [1.0, 'red'], [close, eq]) + +""" + +def generic_node_match(attr, default, op): + if nx.utils.is_string_like(attr): + def match(data1, data2): + return op(data1.get(attr, default), data2.get(attr, default)) + else: + attrs = list(zip(attr, default, op)) # Python 3 + def match(data1, data2): + for attr, d, operator in attrs: + if not operator(data1.get(attr, d), data2.get(attr, d)): + return False + else: + return True + return match + +generic_edge_match = copyfunc(generic_node_match, 'generic_edge_match') + +def generic_multiedge_match(attr, default, op): + """Returns a comparison function for a generic attribute. + + The value(s) of the attr(s) are compared using the specified + operators. If all the attributes are equal, then the constructed + function returns True. Potentially, the constructed edge_match + function can be slow since it must verify that no isomorphism + exists between the multiedges before it returns False. + + Parameters + ---------- + attr : string | list + The edge attribute to compare, or a list of node attributes + to compare. + default : value | list + The default value for the edge attribute, or a list of + default values for the dgeattributes. + op : callable | list + The operator to use when comparing attribute values, or a list + of operators to use when comparing values for each attribute. + + Returns + ------- + match : function + The customized, generic `edge_match` function. + + Examples + -------- + >>> from operator import eq + >>> from networkx.algorithms.isomorphism.matchhelpers import close + >>> from networkx.algorithms.isomorphism import generic_node_match + >>> nm = generic_node_match('weight', 1.0, close) + >>> nm = generic_node_match('color', 'red', eq) + >>> nm = generic_node_match(['weight', 'color'], + ... [1.0, 'red'], + ... [close, eq]) + ... + + """ + + # This is slow, but generic. + # We must test every possible isomorphism between the edges. + if nx.utils.is_string_like(attr): + def match(datasets1, datasets2): + values1 = [data.get(attr, default) for data in datasets1.values()] + values2 = [data.get(attr, default) for data in datasets2.values()] + for vals2 in permutations(values2): + for xi, yi in zip(values1, vals2): + if not op(xi, yi): + # This is not an isomorphism, go to next permutation. + break + else: + # Then we found an isomorphism. + return True + else: + # Then there are no isomorphisms between the multiedges. + return False + else: + attrs = list(zip(attr, default)) # Python 3 + def match(datasets1, datasets2): + values1 = [] + for data1 in datasets1.values(): + x = tuple( data1.get(attr, d) for attr, d in attrs ) + values1.append(x) + values2 = [] + for data2 in datasets2.values(): + x = tuple( data2.get(attr, d) for attr, d in attrs ) + values2.append(x) + for vals2 in permutations(values2): + for xi, yi, operator in zip(values1, vals2, op): + if not operator(xi, yi): + return False + else: + return True + return match + +# Docstrings for numerical functions. +generic_node_match.__doc__ = generic_doc +generic_edge_match.__doc__ = generic_doc.replace('node', 'edge') + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 new file mode 100644 index 0000000000000000000000000000000000000000..dac54f0038c70e2d359ffa68de3c7641b46db21a GIT binary patch literal 1442 zcmWGw;9y{6;A0SDP-QS*X zFkmobFk#?k5Mz*FkY`Y2P+`z!FkmoZuwrmx@M7R+U}WH7;AIeG5MmH!kYrF~&|)xU zFk>)huwigy@L^zQU}E58;9}rrkYg}nFk|3kU|`^25N0rBFlKOJaA)vj@Md6TU}n%^ zFk-M|@MU0S5NFV4&}VRC@M2(L;AW6w&}A@W;9_88kYLbY&}T4Xux4;%aAt5}@L=F! z;9%fo5M>Z!P+(AHFl4Y{uxD^!aAfdeU}NB9kY-Q@yWW(61MC8j-}D(g7C!t7#SqNDyP`3>i$pVb1`zMT9|#K?f}6#Nfu@&A`dP$iUAa%pk%b!=TKd4fYui*frt| z$_#1@nhd63zq&JcfkTafL7qX2!4@3ioDAFyA`Ds#CJdGgZVYS;atvw=8sJd1XW(F9 zXV7BMXE0^3V6bCwVenyKW8eeFrwxN0gD(Rc0~dn;g9w8vIBjq;urP=+$bxMIx!0V* z5gb-L41x?|U{j62@$ACD!l2Bc#h}OF!T@q57lSy1I)f&I7K1i}BZC`*2ZJ|*9|JoB z8-qB56oU?fErTP22LlHKA2`fGZq;SbXYglWW?*N~WiVslU=U;wXHa5LVNhexW-w%M zV_;`sVBlkrV6bDb2Zs+E13!ZdgBpVtgCRIn`581Bv>A*TY#BVk;UUZ*#vsFB17>qE z@GvMc7&F*0urV+&@G^)oC^4uq=riy#FoJCarCv}tLsGXsg9(E<0~-S~gEWI0*fvkF zpF|nt7}Obb!SQa+;KAU<;K$(4z`?-4APbItW3ZSv0|x^$0~gfo`V0mPUJRTJybJ;i zVhoZD(hRB$8VsN?V`E@v-~+Rb85|fm7`Peu83e$&z!99UxEUB2m>5_XL>MF()EM*` zoEcofHnK6WFo-e8GAJ-;GgvSPfK#mmIEFPDj2TQA%or>gY#AIFoEV%LJQ)1J{@`Fx zWH4ZGU~p$(W8h}sVUPxgqBA&+v4T^WCOFMoFxY^_m>77#p(h8;aiFva%8R@Vd*Cx z0LnSM43Z4;3`*eC2J$^5t+6mDG8i$KGJtX*BLfoy$d?)n_6$DYywAzN!ypURVb9>t zz|A1QAOtQ&q#5KG6d5!b3>eHAK%oOFwRjoi8B`c_7@Qc~8T=Sn!R3ZLg9U>Z0~Z4q zI7KRe^Og}fT{toDGq5mlFo04Vs5}&95N7~|f(C;zgDHbKgEs>^11kd?xC~HaaANQT Sm&UAM7l<)9GPp9hF#rHxDrGYO literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 new file mode 100644 index 0000000000000000000000000000000000000000..6c6af680031b4f30fc6da946d0344b5c27e5f05e GIT binary patch literal 1442 zcmWGwU}KPHFlVq}uwigwaA)9P;AIeJkYJE!Fk-M`aAWXd;AdcG;9?MF5M_{HkY-S1 zP+?GG&|xrSuwt-daAWXeU}IoqkYg}nFlDe|uxDUpkYJEu&}Xn_uw~$6U||ph>l9;< zV31_cVen?)V_;_BWZ-7tW#9wL$uTH0C^0BAs4^HaI54m=Ffj-+$T3(l*fMxCa4;}3 z$TO%hs596xxH5P%_%rY`Ffp(&uro+7NHIt=$TAo+Sb=SIWN>HjWbk9)WMF2HW>8}= zfV#(y!JC1Lfq{X8fscV7>@O(>4F(eiD+YE31_lKNYX%3fAAJ}&7z7wZ7!(=Q81xvd z7#tY98Q2-57!(;a7_=D77@Qeg7`PeO82A`u7~~jK7}Obb8T1)U7@Qcq8F(2Oz^>w8 z5N42MkYSKzP-4(wuw!swaAx3SU}9irP-IYLuw<}jaAfdc@MB!;9%fn z;AN0u&|)xRaAxpi@L^zQU}BJE&|@%Uuwbxa@MYj)U;~GTJcBNS4TBvxhCCVA8Q2(v z8B`dw8T1&;7+e_G7#JBqwkt6hF?cgDGjM=oh>d}nft$ge!I{C0!GnPt>_#>Q4hAI# zEe1UX2L=xYFL3NJGVm~{GMF&I|L?UJU-=P?KViV^Cr+WUvS8U7lQ;i zd{`KGz^Ou$!5Zueeg+i=V+Ioj3kFxPoA?+67~~nO8N3)+z%i`J;Ksnoz{bGIpvIsJ zR_Vat2=*lh0~a`LDKi*?;}aCFJPa%hVhoZDQVg;Tpi}~KGsrKV4BQON;MkO5P+(AJ z&}7hJFl8_U+sVqn!yv>U&0x*o4-R`FaOx8Wry`ITD6W_p*ukkmm_Y`dmJJz<7|a>G z7+AqNRlq5Un}MByhe3ovl0k*Rgu#@-iop?_{&*NT!7c*Ds2+npIG*eoT%jqCm4TZ< zf=;-W zR2ZBXd>Q<}EC!8~RLMz9z& z0|NsS0}lf;0~-Sm11|#;ST_>`7Xv>7BZB}~Er>0^AjrVXz`?-BAOxm)82A{N7&sV2 z7(i-88CV$j83Y+b8N?Wv82A~)8JHP(8Tc3^ptKMJ3xg1YFoPt66ay23AcHiR6lGun z>tJCJW)NYJWRPQEW)NnOVvu8CVvt~vXJBCvV-RPMWl&^b1+%2VY-R>=21N!X24)5+ z26+Yr24)6H1{DTX1{N?M#A0MnW?*KJVNhgHW?*8FWl(2eVvu7{V_;%XVNhdWWKdvW H0-FE;4I&8u literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 new file mode 100644 index 0000000000000000000000000000000000000000..0236872094d4c73b0bb132165ce3ec4d1054f5f5 GIT binary patch literal 1602 zcmX@Xz{D__VI>0#0|UckhBXXZ7+4wD8FUyL7&bB-U|?ZzW$<9AWZ1;O#308omw}am zlR=0GVl%fQUQ%D~GI%fQGG%fQ0G%;3sU$*_%qnL(bxpJ5pTD+522b)12TA%dZefr){K zp_qZ0A(|nLA)A4PL5@L-!4vEf9tK_pA+SrB8B`gjGb~|XVz6Xb#lXU#!{EfQn&Bwe zPmBy}7GUPEZGjK6DGfZM&W?*LUV2EX4We{f2XNYI$XIRg`%%H%a!7!bHg+ZGk zhG7ZARt8oE5e7R3PliT@W(HOUF@_+97>1b)OBk3LQW!EBb}+DjePG8hk%5^ZkfEDl zD+4owEJFjsd!jR68#lXyv#8ASpoq>s= zj^Q8!GXpn+E<*tW6T=jSr3{P=!3@j{#tiM?@U&$}W~c{;ND~7SgC0X412cmoLoUNk z237_J1~!H!hItGt8JHM~7|I!#8Il>Q7?v?GGKerRGuSXJWLV6=!l20D!Vm@yfdGaa zhMf!y49pCQ3}Fm=7?>FH7%CW;7}^;&F|aTQFc>lTGaO`KVK8E-W0=iwkb#lGm4S)D zj-io(nL&^tfFYlOi6Mj`n}LZTlpzWl7QGD2;BabTU<8M_6+;BWVFqRf4hA)b%?!*8 zVhpMbGZ~l|Oc|$VINMI;rU}8{YSj)i7Aj4qJV9UV5V8!6YkPFsn$`H)3 z4yOFoNy$W(Z-}33jm~LjVIag8@SrLly%ogCIjHLmR_#hQkaj4BQN64DAd@7?>G+ z7!nxz8JHRD85T1fV_;-h0gmMsusb*y#2Gd-Ffr6H><7oyECxn~G6rS_WpKU(Y zeNcDXFw`?lU|?jJ35_dI8kAv>XJBCvWbk6BWoTny0hf0r;L?r}oVP$Z&XR$d!HJ=V z;V1(rG +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# NetworkX:http://networkx.lanl.gov/ +import networkx as nx +from networkx.exception import NetworkXError +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +__all__ = ['hits','hits_numpy','hits_scipy','authority_matrix','hub_matrix'] + +def hits(G,max_iter=100,tol=1.0e-8,nstart=None,normalized=True): + """Return HITS hubs and authorities values for nodes. + + The HITS algorithm computes two numbers for a node. + Authorities estimates the node value based on the incoming links. + Hubs estimates the node value based on outgoing links. + + Parameters + ---------- + G : graph + A NetworkX graph + + max_iter : interger, optional + Maximum number of iterations in power method. + + tol : float, optional + Error tolerance used to check convergence in power method iteration. + + nstart : dictionary, optional + Starting value of each node for power method iteration. + + normalized : bool (default=True) + Normalize results by the sum of all of the values. + + Returns + ------- + (hubs,authorities) : two-tuple of dictionaries + Two dictionaries keyed by node containing the hub and authority + values. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> h,a=nx.hits(G) + + Notes + ----- + The eigenvector calculation is done by the power iteration method + and has no guarantee of convergence. The iteration will stop + after max_iter iterations or an error tolerance of + number_of_nodes(G)*tol has been reached. + + The HITS algorithm was designed for directed graphs but this + algorithm does not check if the input graph is directed and will + execute on undirected graphs. + + References + ---------- + .. [1] A. Langville and C. Meyer, + "A survey of eigenvector methods of web information retrieval." + http://citeseer.ist.psu.edu/713792.html + .. [2] Jon Kleinberg, + Authoritative sources in a hyperlinked environment + Journal of the ACM 46 (5): 604-32, 1999. + doi:10.1145/324133.324140. + http://www.cs.cornell.edu/home/kleinber/auth.pdf. + """ + if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph: + raise Exception("hits() not defined for graphs with multiedges.") + if len(G) == 0: + return {},{} + # choose fixed starting vector if not given + if nstart is None: + h=dict.fromkeys(G,1.0/G.number_of_nodes()) + else: + h=nstart + # normalize starting vector + s=1.0/sum(h.values()) + for k in h: + h[k]*=s + i=0 + while True: # power iteration: make up to max_iter iterations + hlast=h + h=dict.fromkeys(hlast.keys(),0) + a=dict.fromkeys(hlast.keys(),0) + # this "matrix multiply" looks odd because it is + # doing a left multiply a^T=hlast^T*G + for n in h: + for nbr in G[n]: + a[nbr]+=hlast[n]*G[n][nbr].get('weight',1) + # now multiply h=Ga + for n in h: + for nbr in G[n]: + h[n]+=a[nbr]*G[n][nbr].get('weight',1) + # normalize vector + s=1.0/max(h.values()) + for n in h: h[n]*=s + # normalize vector + s=1.0/max(a.values()) + for n in a: a[n]*=s + # check convergence, l1 norm + err=sum([abs(h[n]-hlast[n]) for n in h]) + if err < tol: + break + if i>max_iter: + raise NetworkXError(\ + "HITS: power iteration failed to converge in %d iterations."%(i+1)) + i+=1 + if normalized: + s = 1.0/sum(a.values()) + for n in a: + a[n] *= s + s = 1.0/sum(h.values()) + for n in h: + h[n] *= s + return h,a + +def authority_matrix(G,nodelist=None): + """Return the HITS authority matrix.""" + M=nx.to_numpy_matrix(G,nodelist=nodelist) + return M.T*M + +def hub_matrix(G,nodelist=None): + """Return the HITS hub matrix.""" + M=nx.to_numpy_matrix(G,nodelist=nodelist) + return M*M.T + +def hits_numpy(G,normalized=True): + """Return HITS hubs and authorities values for nodes. + + The HITS algorithm computes two numbers for a node. + Authorities estimates the node value based on the incoming links. + Hubs estimates the node value based on outgoing links. + + Parameters + ----------- + G : graph + A NetworkX graph + + normalized : bool (default=True) + Normalize results by the sum of all of the values. + + Returns + ------- + (hubs,authorities) : two-tuple of dictionaries + Two dictionaries keyed by node containing the hub and authority + values. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> h,a=nx.hits(G) + + Notes + ----- + The eigenvector calculation uses NumPy's interface to LAPACK. + + The HITS algorithm was designed for directed graphs but this + algorithm does not check if the input graph is directed and will + execute on undirected graphs. + + References + ---------- + .. [1] A. Langville and C. Meyer, + "A survey of eigenvector methods of web information retrieval." + http://citeseer.ist.psu.edu/713792.html + .. [2] Jon Kleinberg, + Authoritative sources in a hyperlinked environment + Journal of the ACM 46 (5): 604-32, 1999. + doi:10.1145/324133.324140. + http://www.cs.cornell.edu/home/kleinber/auth.pdf. + """ + try: + import numpy as np + except ImportError: + raise ImportError(\ + "hits_numpy() requires NumPy: http://scipy.org/") + if len(G) == 0: + return {},{} + H=nx.hub_matrix(G,G.nodes()) + e,ev=np.linalg.eig(H) + m=e.argsort()[-1] # index of maximum eigenvalue + h=np.array(ev[:,m]).flatten() + A=nx.authority_matrix(G,G.nodes()) + e,ev=np.linalg.eig(A) + m=e.argsort()[-1] # index of maximum eigenvalue + a=np.array(ev[:,m]).flatten() + if normalized: + h = h/h.sum() + a = a/a.sum() + else: + h = h/h.max() + a = a/a.max() + hubs=dict(zip(G.nodes(),map(float,h))) + authorities=dict(zip(G.nodes(),map(float,a))) + return hubs,authorities + +def hits_scipy(G,max_iter=100,tol=1.0e-6,normalized=True): + """Return HITS hubs and authorities values for nodes. + + The HITS algorithm computes two numbers for a node. + Authorities estimates the node value based on the incoming links. + Hubs estimates the node value based on outgoing links. + + Parameters + ----------- + G : graph + A NetworkX graph + + max_iter : interger, optional + Maximum number of iterations in power method. + + tol : float, optional + Error tolerance used to check convergence in power method iteration. + + nstart : dictionary, optional + Starting value of each node for power method iteration. + + normalized : bool (default=True) + Normalize results by the sum of all of the values. + + Returns + ------- + (hubs,authorities) : two-tuple of dictionaries + Two dictionaries keyed by node containing the hub and authority + values. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> h,a=nx.hits(G) + + Notes + ----- + This implementation uses SciPy sparse matrices. + + The eigenvector calculation is done by the power iteration method + and has no guarantee of convergence. The iteration will stop + after max_iter iterations or an error tolerance of + number_of_nodes(G)*tol has been reached. + + The HITS algorithm was designed for directed graphs but this + algorithm does not check if the input graph is directed and will + execute on undirected graphs. + + References + ---------- + .. [1] A. Langville and C. Meyer, + "A survey of eigenvector methods of web information retrieval." + http://citeseer.ist.psu.edu/713792.html + .. [2] Jon Kleinberg, + Authoritative sources in a hyperlinked environment + Journal of the ACM 46 (5): 604-632, 1999. + doi:10.1145/324133.324140. + http://www.cs.cornell.edu/home/kleinber/auth.pdf. + """ + try: + import scipy.sparse + import numpy as np + except ImportError: + raise ImportError(\ + "hits_scipy() requires SciPy: http://scipy.org/") + if len(G) == 0: + return {},{} + M=nx.to_scipy_sparse_matrix(G,nodelist=G.nodes()) + (n,m)=M.shape # should be square + A=M.T*M # authority matrix + x=scipy.ones((n,1))/n # initial guess + # power iteration on authority matrix + i=0 + while True: + xlast=x + x=A*x + x=x/x.max() + # check convergence, l1 norm + err=scipy.absolute(x-xlast).sum() + if err < tol: + break + if i>max_iter: + raise NetworkXError(\ + "HITS: power iteration failed to converge in %d iterations."%(i+1)) + i+=1 + + a=np.asarray(x).flatten() + # h=M*a + h=np.asarray(M*a).flatten() + if normalized: + h = h/h.sum() + a = a/a.sum() + hubs=dict(zip(G.nodes(),map(float,h))) + authorities=dict(zip(G.nodes(),map(float,a))) + return hubs,authorities + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/pagerank_alg.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/pagerank_alg.py new file mode 100644 index 0000000..244350a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/pagerank_alg.py @@ -0,0 +1,399 @@ +"""PageRank analysis of graph structure. """ +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# NetworkX:http://networkx.lanl.gov/ +import networkx as nx +from networkx.exception import NetworkXError +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +__all__ = ['pagerank','pagerank_numpy','pagerank_scipy','google_matrix'] + +def pagerank(G,alpha=0.85,personalization=None, + max_iter=100,tol=1.0e-8,nstart=None,weight='weight'): + """Return the PageRank of the nodes in the graph. + + PageRank computes a ranking of the nodes in the graph G based on + the structure of the incoming links. It was originally designed as + an algorithm to rank web pages. + + Parameters + ----------- + G : graph + A NetworkX graph + + alpha : float, optional + Damping parameter for PageRank, default=0.85 + + personalization: dict, optional + The "personalization vector" consisting of a dictionary with a + key for every graph node and nonzero personalization value for each node. + + max_iter : integer, optional + Maximum number of iterations in power method eigenvalue solver. + + tol : float, optional + Error tolerance used to check convergence in power method solver. + + nstart : dictionary, optional + Starting value of PageRank iteration for each node. + + weight : key, optional + Edge data key to use as weight. If None weights are set to 1. + + Returns + ------- + pagerank : dictionary + Dictionary of nodes with PageRank as value + + Examples + -------- + >>> G=nx.DiGraph(nx.path_graph(4)) + >>> pr=nx.pagerank(G,alpha=0.9) + + Notes + ----- + The eigenvector calculation is done by the power iteration method + and has no guarantee of convergence. The iteration will stop + after max_iter iterations or an error tolerance of + number_of_nodes(G)*tol has been reached. + + The PageRank algorithm was designed for directed graphs but this + algorithm does not check if the input graph is directed and will + execute on undirected graphs by converting each oriented edge in the + directed graph to two edges. + + See Also + -------- + pagerank_numpy, pagerank_scipy, google_matrix + + References + ---------- + .. [1] A. Langville and C. Meyer, + "A survey of eigenvector methods of web information retrieval." + http://citeseer.ist.psu.edu/713792.html + .. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry, + The PageRank citation ranking: Bringing order to the Web. 1999 + http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf + """ + if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph: + raise Exception("pagerank() not defined for graphs with multiedges.") + + if len(G) == 0: + return {} + + if not G.is_directed(): + D=G.to_directed() + else: + D=G + + # create a copy in (right) stochastic form + W=nx.stochastic_graph(D, weight=weight) + scale=1.0/W.number_of_nodes() + + # choose fixed starting vector if not given + if nstart is None: + x=dict.fromkeys(W,scale) + else: + x=nstart + # normalize starting vector to 1 + s=1.0/sum(x.values()) + for k in x: x[k]*=s + + # assign uniform personalization/teleportation vector if not given + if personalization is None: + p=dict.fromkeys(W,scale) + else: + p=personalization + # normalize starting vector to 1 + s=1.0/sum(p.values()) + for k in p: + p[k]*=s + if set(p)!=set(G): + raise NetworkXError('Personalization vector ' + 'must have a value for every node') + + + # "dangling" nodes, no links out from them + out_degree=W.out_degree() + dangle=[n for n in W if out_degree[n]==0.0] + i=0 + while True: # power iteration: make up to max_iter iterations + xlast=x + x=dict.fromkeys(xlast.keys(),0) + danglesum=alpha*scale*sum(xlast[n] for n in dangle) + for n in x: + # this matrix multiply looks odd because it is + # doing a left multiply x^T=xlast^T*W + for nbr in W[n]: + x[nbr]+=alpha*xlast[n]*W[n][nbr][weight] + x[n]+=danglesum+(1.0-alpha)*p[n] + # normalize vector + s=1.0/sum(x.values()) + for n in x: + x[n]*=s + # check convergence, l1 norm + err=sum([abs(x[n]-xlast[n]) for n in x]) + if err < tol: + break + if i>max_iter: + raise NetworkXError('pagerank: power iteration failed to converge ' + 'in %d iterations.'%(i-1)) + i+=1 + return x + + +def google_matrix(G, alpha=0.85, personalization=None, + nodelist=None, weight='weight'): + """Return the Google matrix of the graph. + + Parameters + ----------- + G : graph + A NetworkX graph + + alpha : float + The damping factor + + personalization: dict, optional + The "personalization vector" consisting of a dictionary with a + key for every graph node and nonzero personalization value for each node. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + weight : key, optional + Edge data key to use as weight. If None weights are set to 1. + + Returns + ------- + A : NumPy matrix + Google matrix of the graph + + See Also + -------- + pagerank, pagerank_numpy, pagerank_scipy + """ + try: + import numpy as np + except ImportError: + raise ImportError(\ + "google_matrix() requires NumPy: http://scipy.org/") + # choose ordering in matrix + if personalization is None: # use G.nodes() ordering + nodelist=G.nodes() + else: # use personalization "vector" ordering + nodelist=personalization.keys() + if set(nodelist)!=set(G): + raise NetworkXError('Personalization vector dictionary' + 'must have a value for every node') + M=nx.to_numpy_matrix(G,nodelist=nodelist,weight=weight) + (n,m)=M.shape # should be square + if n == 0: + return M + # add constant to dangling nodes' row + dangling=np.where(M.sum(axis=1)==0) + for d in dangling[0]: + M[d]=1.0/n + # normalize + M=M/M.sum(axis=1) + # add "teleportation"/personalization + e=np.ones((n)) + if personalization is not None: + v=np.array(list(personalization.values()),dtype=float) + else: + v=e + v=v/v.sum() + P=alpha*M+(1-alpha)*np.outer(e,v) + return P + + +def pagerank_numpy(G, alpha=0.85, personalization=None, weight='weight'): + """Return the PageRank of the nodes in the graph. + + PageRank computes a ranking of the nodes in the graph G based on + the structure of the incoming links. It was originally designed as + an algorithm to rank web pages. + + Parameters + ----------- + G : graph + A NetworkX graph + + alpha : float, optional + Damping parameter for PageRank, default=0.85 + + personalization: dict, optional + The "personalization vector" consisting of a dictionary with a + key for every graph node and nonzero personalization value for each node. + + weight : key, optional + Edge data key to use as weight. If None weights are set to 1. + + Returns + ------- + pagerank : dictionary + Dictionary of nodes with PageRank as value + + Examples + -------- + >>> G=nx.DiGraph(nx.path_graph(4)) + >>> pr=nx.pagerank_numpy(G,alpha=0.9) + + Notes + ----- + The eigenvector calculation uses NumPy's interface to the LAPACK + eigenvalue solvers. This will be the fastest and most accurate + for small graphs. + + This implementation works with Multi(Di)Graphs. + + See Also + -------- + pagerank, pagerank_scipy, google_matrix + + References + ---------- + .. [1] A. Langville and C. Meyer, + "A survey of eigenvector methods of web information retrieval." + http://citeseer.ist.psu.edu/713792.html + .. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry, + The PageRank citation ranking: Bringing order to the Web. 1999 + http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf + """ + try: + import numpy as np + except ImportError: + raise ImportError("pagerank_numpy() requires NumPy: http://scipy.org/") + if len(G) == 0: + return {} + # choose ordering in matrix + if personalization is None: # use G.nodes() ordering + nodelist=G.nodes() + else: # use personalization "vector" ordering + nodelist=personalization.keys() + M=google_matrix(G, alpha, personalization=personalization, + nodelist=nodelist, weight=weight) + # use numpy LAPACK solver + eigenvalues,eigenvectors=np.linalg.eig(M.T) + ind=eigenvalues.argsort() + # eigenvector of largest eigenvalue at ind[-1], normalized + largest=np.array(eigenvectors[:,ind[-1]]).flatten().real + norm=float(largest.sum()) + centrality=dict(zip(nodelist,map(float,largest/norm))) + return centrality + + +def pagerank_scipy(G, alpha=0.85, personalization=None, + max_iter=100, tol=1.0e-6, weight='weight'): + """Return the PageRank of the nodes in the graph. + + PageRank computes a ranking of the nodes in the graph G based on + the structure of the incoming links. It was originally designed as + an algorithm to rank web pages. + + Parameters + ----------- + G : graph + A NetworkX graph + + alpha : float, optional + Damping parameter for PageRank, default=0.85 + + personalization: dict, optional + The "personalization vector" consisting of a dictionary with a + key for every graph node and nonzero personalization value for each node. + + max_iter : integer, optional + Maximum number of iterations in power method eigenvalue solver. + + tol : float, optional + Error tolerance used to check convergence in power method solver. + + weight : key, optional + Edge data key to use as weight. If None weights are set to 1. + + Returns + ------- + pagerank : dictionary + Dictionary of nodes with PageRank as value + + Examples + -------- + >>> G=nx.DiGraph(nx.path_graph(4)) + >>> pr=nx.pagerank_scipy(G,alpha=0.9) + + Notes + ----- + The eigenvector calculation uses power iteration with a SciPy + sparse matrix representation. + + See Also + -------- + pagerank, pagerank_numpy, google_matrix + + References + ---------- + .. [1] A. Langville and C. Meyer, + "A survey of eigenvector methods of web information retrieval." + http://citeseer.ist.psu.edu/713792.html + .. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry, + The PageRank citation ranking: Bringing order to the Web. 1999 + http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf + """ + try: + import scipy.sparse + except ImportError: + raise ImportError("pagerank_scipy() requires SciPy: http://scipy.org/") + if len(G) == 0: + return {} + # choose ordering in matrix + if personalization is None: # use G.nodes() ordering + nodelist=G.nodes() + else: # use personalization "vector" ordering + nodelist=personalization.keys() + M=nx.to_scipy_sparse_matrix(G,nodelist=nodelist,weight=weight,dtype='f') + (n,m)=M.shape # should be square + S=scipy.array(M.sum(axis=1)).flatten() +# for i, j, v in zip( *scipy.sparse.find(M) ): +# M[i,j] = v / S[i] + S[S>0] = 1.0 / S[S>0] + Q = scipy.sparse.spdiags(S.T, 0, *M.shape, format='csr') + M = Q * M + x=scipy.ones((n))/n # initial guess + dangle=scipy.array(scipy.where(M.sum(axis=1)==0,1.0/n,0)).flatten() + # add "teleportation"/personalization + if personalization is not None: + v=scipy.array(list(personalization.values()),dtype=float) + v=v/v.sum() + else: + v=x + i=0 + while i <= max_iter: + # power iteration: make up to max_iter iterations + xlast=x + x=alpha*(x*M+scipy.dot(dangle,xlast))+(1-alpha)*v + x=x/x.sum() + # check convergence, l1 norm + err=scipy.absolute(x-xlast).sum() + if err < n*tol: + return dict(zip(nodelist,map(float,x))) + i+=1 + raise NetworkXError('pagerank_scipy: power iteration failed to converge' + 'in %d iterations.'%(i+1)) + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_hits.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_hits.py new file mode 100644 index 0000000..7092d1d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_hits.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +from nose.plugins.attrib import attr +import networkx + +# Example from +# A. Langville and C. Meyer, "A survey of eigenvector methods of web +# information retrieval." http://citeseer.ist.psu.edu/713792.html + + +class TestHITS: + + def setUp(self): + + G=networkx.DiGraph() + + edges=[(1,3),(1,5),\ + (2,1),\ + (3,5),\ + (5,4),(5,3),\ + (6,5)] + + G.add_edges_from(edges,weight=1) + self.G=G + self.G.a=dict(zip(G,[0.000000, 0.000000, 0.366025, + 0.133975, 0.500000, 0.000000])) + self.G.h=dict(zip(G,[ 0.366025, 0.000000, 0.211325, + 0.000000, 0.211325, 0.211325])) + + + def test_hits(self): + G=self.G + h,a=networkx.hits(G,tol=1.e-08) + for n in G: + assert_almost_equal(h[n],G.h[n],places=4) + for n in G: + assert_almost_equal(a[n],G.a[n],places=4) + + def test_hits_nstart(self): + G = self.G + nstart = dict([(i, 1./2) for i in G]) + h, a = networkx.hits(G, nstart = nstart) + + @attr('numpy') + def test_hits_numpy(self): + try: + import numpy as np + except ImportError: + raise SkipTest('NumPy not available.') + + + G=self.G + h,a=networkx.hits_numpy(G) + for n in G: + assert_almost_equal(h[n],G.h[n],places=4) + for n in G: + assert_almost_equal(a[n],G.a[n],places=4) + + + def test_hits_scipy(self): + try: + import scipy as sp + except ImportError: + raise SkipTest('SciPy not available.') + + G=self.G + h,a=networkx.hits_scipy(G,tol=1.e-08) + for n in G: + assert_almost_equal(h[n],G.h[n],places=4) + for n in G: + assert_almost_equal(a[n],G.a[n],places=4) + + + @attr('numpy') + def test_empty(self): + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + G=networkx.Graph() + assert_equal(networkx.hits(G),({},{})) + assert_equal(networkx.hits_numpy(G),({},{})) + assert_equal(networkx.authority_matrix(G).shape,(0,0)) + assert_equal(networkx.hub_matrix(G).shape,(0,0)) + + def test_empty_scipy(self): + try: + import scipy + except ImportError: + raise SkipTest('scipy not available.') + G=networkx.Graph() + assert_equal(networkx.hits_scipy(G),({},{})) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_pagerank.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_pagerank.py new file mode 100644 index 0000000..7b409ff --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/link_analysis/tests/test_pagerank.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +from nose.plugins.attrib import attr +import random +import networkx + +# Example from +# A. Langville and C. Meyer, "A survey of eigenvector methods of web +# information retrieval." http://citeseer.ist.psu.edu/713792.html + + +class TestPageRank: + + def setUp(self): + G=networkx.DiGraph() + edges=[(1,2),(1,3),\ + (3,1),(3,2),(3,5),\ + (4,5),(4,6),\ + (5,4),(5,6),\ + (6,4)] + G.add_edges_from(edges) + self.G=G + self.G.pagerank=dict(zip(G, + [0.03721197,0.05395735,0.04150565, + 0.37508082,0.20599833, 0.28624589])) + + def test_pagerank(self): + G=self.G + p=networkx.pagerank(G,alpha=0.9,tol=1.e-08) + for n in G: + assert_almost_equal(p[n],G.pagerank[n],places=4) + + nstart = dict((n,random.random()) for n in G) + p=networkx.pagerank(G,alpha=0.9,tol=1.e-08, nstart=nstart) + for n in G: + assert_almost_equal(p[n],G.pagerank[n],places=4) + + assert_raises(networkx.NetworkXError,networkx.pagerank,G, + max_iter=0) + + + @attr('numpy') + def test_numpy_pagerank(self): + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + G=self.G + p=networkx.pagerank_numpy(G,alpha=0.9) + for n in G: + assert_almost_equal(p[n],G.pagerank[n],places=4) + personalize = dict((n,random.random()) for n in G) + p=networkx.pagerank_numpy(G,alpha=0.9, personalization=personalize) + + + + @attr('numpy') + def test_google_matrix(self): + try: + import numpy.linalg + except ImportError: + raise SkipTest('numpy not available.') + G=self.G + M=networkx.google_matrix(G,alpha=0.9) + e,ev=numpy.linalg.eig(M.T) + p=numpy.array(ev[:,0]/ev[:,0].sum())[:,0] + for (a,b) in zip(p,self.G.pagerank.values()): + assert_almost_equal(a,b) + + personalize = dict((n,random.random()) for n in G) + M=networkx.google_matrix(G,alpha=0.9, personalization=personalize) + _ = personalize.pop(1) + assert_raises(networkx.NetworkXError,networkx.google_matrix,G, + personalization=personalize) + + def test_scipy_pagerank(self): + G=self.G + try: + import scipy + except ImportError: + raise SkipTest('scipy not available.') + p=networkx.pagerank_scipy(G,alpha=0.9,tol=1.e-08) + for n in G: + assert_almost_equal(p[n],G.pagerank[n],places=4) + personalize = dict((n,random.random()) for n in G) + p=networkx.pagerank_scipy(G,alpha=0.9,tol=1.e-08, + personalization=personalize) + + assert_raises(networkx.NetworkXError,networkx.pagerank_scipy,G, + max_iter=0) + + def test_personalization(self): + G=networkx.complete_graph(4) + personalize={0:1,1:1,2:4,3:4} + answer={0:0.1,1:0.1,2:0.4,3:0.4} + p=networkx.pagerank(G,alpha=0.0,personalization=personalize) + for n in G: + assert_almost_equal(p[n],answer[n],places=4) + _ = personalize.pop(0) + assert_raises(networkx.NetworkXError,networkx.pagerank,G, + personalization=personalize) + + + @attr('numpy') + def test_empty(self): + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + G=networkx.Graph() + assert_equal(networkx.pagerank(G),{}) + assert_equal(networkx.pagerank_numpy(G),{}) + assert_equal(networkx.google_matrix(G).shape,(0,0)) + + def test_empty_scipy(self): + try: + import scipy + except ImportError: + raise SkipTest('scipy not available.') + G=networkx.Graph() + assert_equal(networkx.pagerank_scipy(G),{}) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/matching.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/matching.py new file mode 100644 index 0000000..70c424f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/matching.py @@ -0,0 +1,825 @@ +""" +******** +Matching +******** +""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# Copyright (C) 2011 by +# Nicholas Mancuso +# All rights reserved. +# BSD license. +from itertools import repeat +__author__ = """\n""".join(['Joris van Rantwijk', + 'Nicholas Mancuso (nick.mancuso@gmail.com)']) + +_all__ = ['max_weight_matching', 'maximal_matching'] + + +def maximal_matching(G): + r"""Find a maximal cardinality matching in the graph. + + A matching is a subset of edges in which no node occurs more than once. + The cardinality of a matching is the number of matched edges. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + matching : set + A maximal matching of the graph. + + Notes + ----- + The algorithm greedily selects a maximal matching M of the graph G + (i.e. no superset of M exists). It runs in `O(|E|)` time. + """ + matching = set([]) + edges = set([]) + for edge in G.edges_iter(): + # If the edge isn't covered, add it to the matching + # then remove neighborhood of u and v from consideration. + if edge not in edges: + u, v = edge + matching.add(edge) + edges |= set(G.edges(u)) + edges |= set(G.edges(v)) + + return matching + + +def max_weight_matching(G, maxcardinality=False): + """Compute a maximum-weighted matching of G. + + A matching is a subset of edges in which no node occurs more than once. + The cardinality of a matching is the number of matched edges. + The weight of a matching is the sum of the weights of its edges. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + maxcardinality: bool, optional + If maxcardinality is True, compute the maximum-cardinality matching + with maximum weight among all maximum-cardinality matchings. + + Returns + ------- + mate : dictionary + The matching is returned as a dictionary, mate, such that + mate[v] == w if node v is matched to node w. Unmatched nodes do not + occur as a key in mate. + + + Notes + ------ + If G has edges with 'weight' attribute the edge data are used as + weight values else the weights are assumed to be 1. + + This function takes time O(number_of_nodes ** 3). + + If all edge weights are integers, the algorithm uses only integer + computations. If floating point weights are used, the algorithm + could return a slightly suboptimal matching due to numeric + precision errors. + + This method is based on the "blossom" method for finding augmenting + paths and the "primal-dual" method for finding a matching of maximum + weight, both methods invented by Jack Edmonds [1]_. + + References + ---------- + .. [1] "Efficient Algorithms for Finding Maximum Matching in Graphs", + Zvi Galil, ACM Computing Surveys, 1986. + """ + # + # The algorithm is taken from "Efficient Algorithms for Finding Maximum + # Matching in Graphs" by Zvi Galil, ACM Computing Surveys, 1986. + # It is based on the "blossom" method for finding augmenting paths and + # the "primal-dual" method for finding a matching of maximum weight, both + # methods invented by Jack Edmonds. + # + # A C program for maximum weight matching by Ed Rothberg was used + # extensively to validate this new code. + # + # Many terms used in the code comments are explained in the paper + # by Galil. You will probably need the paper to make sense of this code. + # + + class NoNode: + """Dummy value which is different from any node.""" + pass + + class Blossom: + """Representation of a non-trivial blossom or sub-blossom.""" + + __slots__ = [ 'childs', 'edges', 'mybestedges' ] + + # b.childs is an ordered list of b's sub-blossoms, starting with + # the base and going round the blossom. + + # b.edges is the list of b's connecting edges, such that + # b.edges[i] = (v, w) where v is a vertex in b.childs[i] + # and w is a vertex in b.childs[wrap(i+1)]. + + # If b is a top-level S-blossom, + # b.mybestedges is a list of least-slack edges to neighbouring + # S-blossoms, or None if no such list has been computed yet. + # This is used for efficient computation of delta3. + + # Generate the blossom's leaf vertices. + def leaves(self): + for t in self.childs: + if isinstance(t, Blossom): + for v in t.leaves(): + yield v + else: + yield t + + # Get a list of vertices. + gnodes = G.nodes() + if not gnodes: + return { } # don't bother with empty graphs + + # Find the maximum edge weight. + maxweight = 0 + allinteger = True + for i,j,d in G.edges_iter(data=True): + wt=d.get('weight',1) + if i != j and wt > maxweight: + maxweight = wt + allinteger = allinteger and (str(type(wt)).split("'")[1] + in ('int', 'long')) + + # If v is a matched vertex, mate[v] is its partner vertex. + # If v is a single vertex, v does not occur as a key in mate. + # Initially all vertices are single; updated during augmentation. + mate = { } + + # If b is a top-level blossom, + # label.get(b) is None if b is unlabeled (free), + # 1 if b is an S-blossom, + # 2 if b is a T-blossom. + # The label of a vertex is found by looking at the label of its top-level + # containing blossom. + # If v is a vertex inside a T-blossom, label[v] is 2 iff v is reachable + # from an S-vertex outside the blossom. + # Labels are assigned during a stage and reset after each augmentation. + label = { } + + # If b is a labeled top-level blossom, + # labeledge[b] = (v, w) is the edge through which b obtained its label + # such that w is a vertex in b, or None if b's base vertex is single. + # If w is a vertex inside a T-blossom and label[w] == 2, + # labeledge[w] = (v, w) is an edge through which w is reachable from + # outside the blossom. + labeledge = { } + + # If v is a vertex, inblossom[v] is the top-level blossom to which v + # belongs. + # If v is a top-level vertex, inblossom[v] == v since v is itself + # a (trivial) top-level blossom. + # Initially all vertices are top-level trivial blossoms. + inblossom = dict(zip(gnodes, gnodes)) + + # If b is a sub-blossom, + # blossomparent[b] is its immediate parent (sub-)blossom. + # If b is a top-level blossom, blossomparent[b] is None. + blossomparent = dict(zip(gnodes, repeat(None))) + + # If b is a (sub-)blossom, + # blossombase[b] is its base VERTEX (i.e. recursive sub-blossom). + blossombase = dict(zip(gnodes, gnodes)) + + # If w is a free vertex (or an unreached vertex inside a T-blossom), + # bestedge[w] = (v, w) is the least-slack edge from an S-vertex, + # or None if there is no such edge. + # If b is a (possibly trivial) top-level S-blossom, + # bestedge[b] = (v, w) is the least-slack edge to a different S-blossom + # (v inside b), or None if there is no such edge. + # This is used for efficient computation of delta2 and delta3. + bestedge = { } + + # If v is a vertex, + # dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual + # optimization problem (if all edge weights are integers, multiplication + # by two ensures that all values remain integers throughout the algorithm). + # Initially, u(v) = maxweight / 2. + dualvar = dict(zip(gnodes, repeat(maxweight))) + + # If b is a non-trivial blossom, + # blossomdual[b] = z(b) where z(b) is b's variable in the dual + # optimization problem. + blossomdual = { } + + # If (v, w) in allowedge or (w, v) in allowedg, then the edge + # (v, w) is known to have zero slack in the optimization problem; + # otherwise the edge may or may not have zero slack. + allowedge = { } + + # Queue of newly discovered S-vertices. + queue = [ ] + + # Return 2 * slack of edge (v, w) (does not work inside blossoms). + def slack(v, w): + return dualvar[v] + dualvar[w] - 2 * G[v][w].get('weight',1) + + # Assign label t to the top-level blossom containing vertex w, + # coming through an edge from vertex v. + def assignLabel(w, t, v): + b = inblossom[w] + assert label.get(w) is None and label.get(b) is None + label[w] = label[b] = t + if v is not None: + labeledge[w] = labeledge[b] = (v, w) + else: + labeledge[w] = labeledge[b] = None + bestedge[w] = bestedge[b] = None + if t == 1: + # b became an S-vertex/blossom; add it(s vertices) to the queue. + if isinstance(b, Blossom): + queue.extend(b.leaves()) + else: + queue.append(b) + elif t == 2: + # b became a T-vertex/blossom; assign label S to its mate. + # (If b is a non-trivial blossom, its base is the only vertex + # with an external mate.) + base = blossombase[b] + assignLabel(mate[base], 1, base) + + # Trace back from vertices v and w to discover either a new blossom + # or an augmenting path. Return the base vertex of the new blossom, + # or NoNode if an augmenting path was found. + def scanBlossom(v, w): + # Trace back from v and w, placing breadcrumbs as we go. + path = [ ] + base = NoNode + while v is not NoNode: + # Look for a breadcrumb in v's blossom or put a new breadcrumb. + b = inblossom[v] + if label[b] & 4: + base = blossombase[b] + break + assert label[b] == 1 + path.append(b) + label[b] = 5 + # Trace one step back. + if labeledge[b] is None: + # The base of blossom b is single; stop tracing this path. + assert blossombase[b] not in mate + v = NoNode + else: + assert labeledge[b][0] == mate[blossombase[b]] + v = labeledge[b][0] + b = inblossom[v] + assert label[b] == 2 + # b is a T-blossom; trace one more step back. + v = labeledge[b][0] + # Swap v and w so that we alternate between both paths. + if w is not NoNode: + v, w = w, v + # Remove breadcrumbs. + for b in path: + label[b] = 1 + # Return base vertex, if we found one. + return base + + # Construct a new blossom with given base, through S-vertices v and w. + # Label the new blossom as S; set its dual variable to zero; + # relabel its T-vertices to S and add them to the queue. + def addBlossom(base, v, w): + bb = inblossom[base] + bv = inblossom[v] + bw = inblossom[w] + # Create blossom. + b = Blossom() + blossombase[b] = base + blossomparent[b] = None + blossomparent[bb] = b + # Make list of sub-blossoms and their interconnecting edge endpoints. + b.childs = path = [ ] + b.edges = edgs = [ (v, w) ] + # Trace back from v to base. + while bv != bb: + # Add bv to the new blossom. + blossomparent[bv] = b + path.append(bv) + edgs.append(labeledge[bv]) + assert label[bv] == 2 or (label[bv] == 1 and labeledge[bv][0] == mate[blossombase[bv]]) + # Trace one step back. + v = labeledge[bv][0] + bv = inblossom[v] + # Add base sub-blossom; reverse lists. + path.append(bb) + path.reverse() + edgs.reverse() + # Trace back from w to base. + while bw != bb: + # Add bw to the new blossom. + blossomparent[bw] = b + path.append(bw) + edgs.append((labeledge[bw][1], labeledge[bw][0])) + assert label[bw] == 2 or (label[bw] == 1 and labeledge[bw][0] == mate[blossombase[bw]]) + # Trace one step back. + w = labeledge[bw][0] + bw = inblossom[w] + # Set label to S. + assert label[bb] == 1 + label[b] = 1 + labeledge[b] = labeledge[bb] + # Set dual variable to zero. + blossomdual[b] = 0 + # Relabel vertices. + for v in b.leaves(): + if label[inblossom[v]] == 2: + # This T-vertex now turns into an S-vertex because it becomes + # part of an S-blossom; add it to the queue. + queue.append(v) + inblossom[v] = b + # Compute b.mybestedges. + bestedgeto = { } + for bv in path: + if isinstance(bv, Blossom): + if bv.mybestedges is not None: + # Walk this subblossom's least-slack edges. + nblist = bv.mybestedges + # The sub-blossom won't need this data again. + bv.mybestedges = None + else: + # This subblossom does not have a list of least-slack + # edges; get the information from the vertices. + nblist = [ (v, w) + for v in bv.leaves() + for w in G.neighbors_iter(v) + if v != w ] + else: + nblist = [ (bv, w) + for w in G.neighbors_iter(bv) + if bv != w ] + for k in nblist: + (i, j) = k + if inblossom[j] == b: + i, j = j, i + bj = inblossom[j] + if (bj != b and label.get(bj) == 1 and + ((bj not in bestedgeto) or + slack(i, j) < slack(*bestedgeto[bj]))): + bestedgeto[bj] = k + # Forget about least-slack edge of the subblossom. + bestedge[bv] = None + b.mybestedges = list(bestedgeto.values()) + # Select bestedge[b]. + mybestedge = None + bestedge[b] = None + for k in b.mybestedges: + kslack = slack(*k) + if mybestedge is None or kslack < mybestslack: + mybestedge = k + mybestslack = kslack + bestedge[b] = mybestedge + + # Expand the given top-level blossom. + def expandBlossom(b, endstage): + # Convert sub-blossoms into top-level blossoms. + for s in b.childs: + blossomparent[s] = None + if isinstance(s, Blossom): + if endstage and blossomdual[s] == 0: + # Recursively expand this sub-blossom. + expandBlossom(s, endstage) + else: + for v in s.leaves(): + inblossom[v] = s + else: + inblossom[s] = s + # If we expand a T-blossom during a stage, its sub-blossoms must be + # relabeled. + if (not endstage) and label.get(b) == 2: + # Start at the sub-blossom through which the expanding + # blossom obtained its label, and relabel sub-blossoms untili + # we reach the base. + # Figure out through which sub-blossom the expanding blossom + # obtained its label initially. + entrychild = inblossom[labeledge[b][1]] + # Decide in which direction we will go round the blossom. + j = b.childs.index(entrychild) + if j & 1: + # Start index is odd; go forward and wrap. + j -= len(b.childs) + jstep = 1 + else: + # Start index is even; go backward. + jstep = -1 + # Move along the blossom until we get to the base. + v, w = labeledge[b] + while j != 0: + # Relabel the T-sub-blossom. + if jstep == 1: + p, q = b.edges[j] + else: + q, p = b.edges[j-1] + label[w] = None + label[q] = None + assignLabel(w, 2, v) + # Step to the next S-sub-blossom and note its forward edge. + allowedge[(p, q)] = allowedge[(q, p)] = True + j += jstep + if jstep == 1: + v, w = b.edges[j] + else: + w, v = b.edges[j-1] + # Step to the next T-sub-blossom. + allowedge[(v, w)] = allowedge[(w, v)] = True + j += jstep + # Relabel the base T-sub-blossom WITHOUT stepping through to + # its mate (so don't call assignLabel). + bw = b.childs[j] + label[w] = label[bw] = 2 + labeledge[w] = labeledge[bw] = (v, w) + bestedge[bw] = None + # Continue along the blossom until we get back to entrychild. + j += jstep + while b.childs[j] != entrychild: + # Examine the vertices of the sub-blossom to see whether + # it is reachable from a neighbouring S-vertex outside the + # expanding blossom. + bv = b.childs[j] + if label.get(bv) == 1: + # This sub-blossom just got label S through one of its + # neighbours; leave it be. + j += jstep + continue + if isinstance(bv, Blossom): + for v in bv.leaves(): + if label.get(v): + break + else: + v = bv + # If the sub-blossom contains a reachable vertex, assign + # label T to the sub-blossom. + if label.get(v): + assert label[v] == 2 + assert inblossom[v] == bv + label[v] = None + label[mate[blossombase[bv]]] = None + assignLabel(v, 2, labeledge[v][0]) + j += jstep + # Remove the expanded blossom entirely. + label.pop(b, None) + labeledge.pop(b, None) + bestedge.pop(b, None) + del blossomparent[b] + del blossombase[b] + del blossomdual[b] + + # Swap matched/unmatched edges over an alternating path through blossom b + # between vertex v and the base vertex. Keep blossom bookkeeping consistent. + def augmentBlossom(b, v): + # Bubble up through the blossom tree from vertex v to an immediate + # sub-blossom of b. + t = v + while blossomparent[t] != b: + t = blossomparent[t] + # Recursively deal with the first sub-blossom. + if isinstance(t, Blossom): + augmentBlossom(t, v) + # Decide in which direction we will go round the blossom. + i = j = b.childs.index(t) + if i & 1: + # Start index is odd; go forward and wrap. + j -= len(b.childs) + jstep = 1 + else: + # Start index is even; go backward. + jstep = -1 + # Move along the blossom until we get to the base. + while j != 0: + # Step to the next sub-blossom and augment it recursively. + j += jstep + t = b.childs[j] + if jstep == 1: + w, x = b.edges[j] + else: + x, w = b.edges[j-1] + if isinstance(t, Blossom): + augmentBlossom(t, w) + # Step to the next sub-blossom and augment it recursively. + j += jstep + t = b.childs[j] + if isinstance(t, Blossom): + augmentBlossom(t, x) + # Match the edge connecting those sub-blossoms. + mate[w] = x + mate[x] = w + # Rotate the list of sub-blossoms to put the new base at the front. + b.childs = b.childs[i:] + b.childs[:i] + b.edges = b.edges[i:] + b.edges[:i] + blossombase[b] = blossombase[b.childs[0]] + assert blossombase[b] == v + + # Swap matched/unmatched edges over an alternating path between two + # single vertices. The augmenting path runs through S-vertices v and w. + def augmentMatching(v, w): + for (s, j) in ((v, w), (w, v)): + # Match vertex s to vertex j. Then trace back from s + # until we find a single vertex, swapping matched and unmatched + # edges as we go. + while 1: + bs = inblossom[s] + assert label[bs] == 1 + assert (labeledge[bs] is None and blossombase[bs] not in mate) or (labeledge[bs][0] == mate[blossombase[bs]]) + # Augment through the S-blossom from s to base. + if isinstance(bs, Blossom): + augmentBlossom(bs, s) + # Update mate[s] + mate[s] = j + # Trace one step back. + if labeledge[bs] is None: + # Reached single vertex; stop. + break + t = labeledge[bs][0] + bt = inblossom[t] + assert label[bt] == 2 + # Trace one more step back. + s, j = labeledge[bt] + # Augment through the T-blossom from j to base. + assert blossombase[bt] == t + if isinstance(bt, Blossom): + augmentBlossom(bt, j) + # Update mate[j] + mate[j] = s + + # Verify that the optimum solution has been reached. + def verifyOptimum(): + if maxcardinality: + # Vertices may have negative dual; + # find a constant non-negative number to add to all vertex duals. + vdualoffset = max(0, -min(dualvar.values())) + else: + vdualoffset = 0 + # 0. all dual variables are non-negative + assert min(dualvar.values()) + vdualoffset >= 0 + assert len(blossomdual) == 0 or min(blossomdual.values()) >= 0 + # 0. all edges have non-negative slack and + # 1. all matched edges have zero slack; + for i,j,d in G.edges_iter(data=True): + wt=d.get('weight',1) + if i == j: + continue # ignore self-loops + s = dualvar[i] + dualvar[j] - 2 * wt + iblossoms = [ i ] + jblossoms = [ j ] + while blossomparent[iblossoms[-1]] is not None: + iblossoms.append(blossomparent[iblossoms[-1]]) + while blossomparent[jblossoms[-1]] is not None: + jblossoms.append(blossomparent[jblossoms[-1]]) + iblossoms.reverse() + jblossoms.reverse() + for (bi, bj) in zip(iblossoms, jblossoms): + if bi != bj: + break + s += 2 * blossomdual[bi] + assert s >= 0 + if mate.get(i) == j or mate.get(j) == i: + assert mate[i] == j and mate[j] == i + assert s == 0 + # 2. all single vertices have zero dual value; + for v in gnodes: + assert (v in mate) or dualvar[v] + vdualoffset == 0 + # 3. all blossoms with positive dual value are full. + for b in blossomdual: + if blossomdual[b] > 0: + assert len(b.edges) % 2 == 1 + for (i, j) in b.edges[1::2]: + assert mate[i] == j and mate[j] == i + # Ok. + + # Main loop: continue until no further improvement is possible. + while 1: + + # Each iteration of this loop is a "stage". + # A stage finds an augmenting path and uses that to improve + # the matching. + + # Remove labels from top-level blossoms/vertices. + label.clear() + labeledge.clear() + + # Forget all about least-slack edges. + bestedge.clear() + for b in blossomdual: + b.mybestedges = None + + # Loss of labeling means that we can not be sure that currently + # allowable edges remain allowable througout this stage. + allowedge.clear() + + # Make queue empty. + queue[:] = [ ] + + # Label single blossoms/vertices with S and put them in the queue. + for v in gnodes: + if (v not in mate) and label.get(inblossom[v]) is None: + assignLabel(v, 1, None) + + + # Loop until we succeed in augmenting the matching. + augmented = 0 + while 1: + + # Each iteration of this loop is a "substage". + # A substage tries to find an augmenting path; + # if found, the path is used to improve the matching and + # the stage ends. If there is no augmenting path, the + # primal-dual method is used to pump some slack out of + # the dual variables. + + # Continue labeling until all vertices which are reachable + # through an alternating path have got a label. + while queue and not augmented: + + # Take an S vertex from the queue. + v = queue.pop() + assert label[inblossom[v]] == 1 + + # Scan its neighbours: + for w in G.neighbors_iter(v): + if w == v: + continue # ignore self-loops + # w is a neighbour to v + bv = inblossom[v] + bw = inblossom[w] + if bv == bw: + # this edge is internal to a blossom; ignore it + continue + if (v, w) not in allowedge: + kslack = slack(v, w) + if kslack <= 0: + # edge k has zero slack => it is allowable + allowedge[(v, w)] = allowedge[(w, v)] = True + if (v, w) in allowedge: + if label.get(bw) is None: + # (C1) w is a free vertex; + # label w with T and label its mate with S (R12). + assignLabel(w, 2, v) + elif label.get(bw) == 1: + # (C2) w is an S-vertex (not in the same blossom); + # follow back-links to discover either an + # augmenting path or a new blossom. + base = scanBlossom(v, w) + if base is not NoNode: + # Found a new blossom; add it to the blossom + # bookkeeping and turn it into an S-blossom. + addBlossom(base, v, w) + else: + # Found an augmenting path; augment the + # matching and end this stage. + augmentMatching(v, w) + augmented = 1 + break + elif label.get(w) is None: + # w is inside a T-blossom, but w itself has not + # yet been reached from outside the blossom; + # mark it as reached (we need this to relabel + # during T-blossom expansion). + assert label[bw] == 2 + label[w] = 2 + labeledge[w] = (v, w) + elif label.get(bw) == 1: + # keep track of the least-slack non-allowable edge to + # a different S-blossom. + if bestedge.get(bv) is None or kslack < slack(*bestedge[bv]): + bestedge[bv] = (v, w) + elif label.get(w) is None: + # w is a free vertex (or an unreached vertex inside + # a T-blossom) but we can not reach it yet; + # keep track of the least-slack edge that reaches w. + if bestedge.get(w) is None or kslack < slack(*bestedge[w]): + bestedge[w] = (v, w) + + if augmented: + break + + # There is no augmenting path under these constraints; + # compute delta and reduce slack in the optimization problem. + # (Note that our vertex dual variables, edge slacks and delta's + # are pre-multiplied by two.) + deltatype = -1 + delta = deltaedge = deltablossom = None + + # Compute delta1: the minumum value of any vertex dual. + if not maxcardinality: + deltatype = 1 + delta = min(dualvar.values()) + + # Compute delta2: the minimum slack on any edge between + # an S-vertex and a free vertex. + for v in G.nodes_iter(): + if label.get(inblossom[v]) is None and bestedge.get(v) is not None: + d = slack(*bestedge[v]) + if deltatype == -1 or d < delta: + delta = d + deltatype = 2 + deltaedge = bestedge[v] + + # Compute delta3: half the minimum slack on any edge between + # a pair of S-blossoms. + for b in blossomparent: + if ( blossomparent[b] is None and label.get(b) == 1 and + bestedge.get(b) is not None ): + kslack = slack(*bestedge[b]) + if allinteger: + assert (kslack % 2) == 0 + d = kslack // 2 + else: + d = kslack / 2.0 + if deltatype == -1 or d < delta: + delta = d + deltatype = 3 + deltaedge = bestedge[b] + + # Compute delta4: minimum z variable of any T-blossom. + for b in blossomdual: + if ( blossomparent[b] is None and label.get(b) == 2 and + (deltatype == -1 or blossomdual[b] < delta) ): + delta = blossomdual[b] + deltatype = 4 + deltablossom = b + + if deltatype == -1: + # No further improvement possible; max-cardinality optimum + # reached. Do a final delta update to make the optimum + # verifyable. + assert maxcardinality + deltatype = 1 + delta = max(0, min(dualvar.values())) + + # Update dual variables according to delta. + for v in gnodes: + if label.get(inblossom[v]) == 1: + # S-vertex: 2*u = 2*u - 2*delta + dualvar[v] -= delta + elif label.get(inblossom[v]) == 2: + # T-vertex: 2*u = 2*u + 2*delta + dualvar[v] += delta + for b in blossomdual: + if blossomparent[b] is None: + if label.get(b) == 1: + # top-level S-blossom: z = z + 2*delta + blossomdual[b] += delta + elif label.get(b) == 2: + # top-level T-blossom: z = z - 2*delta + blossomdual[b] -= delta + + # Take action at the point where minimum delta occurred. + if deltatype == 1: + # No further improvement possible; optimum reached. + break + elif deltatype == 2: + # Use the least-slack edge to continue the search. + (v, w) = deltaedge + assert label[inblossom[v]] == 1 + allowedge[(v, w)] = allowedge[(w, v)] = True + queue.append(v) + elif deltatype == 3: + # Use the least-slack edge to continue the search. + (v, w) = deltaedge + allowedge[(v, w)] = allowedge[(w, v)] = True + assert label[inblossom[v]] == 1 + queue.append(v) + elif deltatype == 4: + # Expand the least-z blossom. + expandBlossom(deltablossom, False) + + # End of a this substage. + + # Paranoia check that the matching is symmetric. + for v in mate: + assert mate[mate[v]] == v + + # Stop when no more augmenting path can be found. + if not augmented: + break + + # End of a stage; expand all S-blossoms which have zero dual. + for b in list(blossomdual.keys()): + if b not in blossomdual: + continue # already expanded + if ( blossomparent[b] is None and label.get(b) == 1 and + blossomdual[b] == 0 ): + expandBlossom(b, True) + + # Verify that we reached the optimum solution (only for integer weights). + if allinteger: + verifyOptimum() + + return mate diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mis.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mis.py new file mode 100644 index 0000000..d02c2d5 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mis.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# $Id: maximalIndependentSet.py 576 2011-03-01 05:50:34Z lleeoo $ +""" +Algorithm to find a maximal (not maximum) independent set. + +""" +# Leo Lopes +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__author__ = "\n".join(["Leo Lopes ", + "Loïc Séguin-C. "]) + +__all__ = ['maximal_independent_set'] + +import random +import networkx as nx + +def maximal_independent_set(G, nodes=None): + """Return a random maximal independent set guaranteed to contain + a given set of nodes. + + An independent set is a set of nodes such that the subgraph + of G induced by these nodes contains no edges. A maximal + independent set is an independent set such that it is not possible + to add a new node and still get an independent set. + + Parameters + ---------- + G : NetworkX graph + + nodes : list or iterable + Nodes that must be part of the independent set. This set of nodes + must be independent. + + Returns + ------- + indep_nodes : list + List of nodes that are part of a maximal independent set. + + Raises + ------ + NetworkXUnfeasible + If the nodes in the provided list are not part of the graph or + do not form an independent set, an exception is raised. + + Examples + -------- + >>> G = nx.path_graph(5) + >>> nx.maximal_independent_set(G) # doctest: +SKIP + [4, 0, 2] + >>> nx.maximal_independent_set(G, [1]) # doctest: +SKIP + [1, 3] + + Notes + ------ + This algorithm does not solve the maximum independent set problem. + + """ + if not nodes: + nodes = set([random.choice(G.nodes())]) + else: + nodes = set(nodes) + if not nodes.issubset(G): + raise nx.NetworkXUnfeasible( + "%s is not a subset of the nodes of G" % nodes) + neighbors = set.union(*[set(G.neighbors(v)) for v in nodes]) + if set.intersection(neighbors, nodes): + raise nx.NetworkXUnfeasible( + "%s is not an independent set of G" % nodes) + indep_nodes = list(nodes) + available_nodes = set(G.nodes()).difference(neighbors.union(nodes)) + while available_nodes: + node = random.choice(list(available_nodes)) + indep_nodes.append(node) + available_nodes.difference_update(G.neighbors(node) + [node]) + return indep_nodes + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mst.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mst.py new file mode 100644 index 0000000..03ca5e8 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/mst.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +""" +Computes minimum spanning tree of a weighted graph. + +""" +# Copyright (C) 2009-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# Loïc Séguin-C. +# All rights reserved. +# BSD license. + +__all__ = ['kruskal_mst', + 'minimum_spanning_edges', + 'minimum_spanning_tree', + 'prim_mst_edges', 'prim_mst'] + +import networkx as nx +from heapq import heappop, heappush + +def minimum_spanning_edges(G,weight='weight',data=True): + """Generate edges in a minimum spanning forest of an undirected + weighted graph. + + A minimum spanning tree is a subgraph of the graph (a tree) + with the minimum sum of edge weights. A spanning forest is a + union of the spanning trees for each connected component of the graph. + + Parameters + ---------- + G : NetworkX Graph + + weight : string + Edge data key to use for weight (default 'weight'). + + data : bool, optional + If True yield the edge data along with the edge. + + Returns + ------- + edges : iterator + A generator that produces edges in the minimum spanning tree. + The edges are three-tuples (u,v,w) where w is the weight. + + Examples + -------- + >>> G=nx.cycle_graph(4) + >>> G.add_edge(0,3,weight=2) # assign weight 2 to edge 0-3 + >>> mst=nx.minimum_spanning_edges(G,data=False) # a generator of MST edges + >>> edgelist=list(mst) # make a list of the edges + >>> print(sorted(edgelist)) + [(0, 1), (1, 2), (2, 3)] + + Notes + ----- + Uses Kruskal's algorithm. + + If the graph edges do not have a weight attribute a default weight of 1 + will be used. + + Modified code from David Eppstein, April 2006 + http://www.ics.uci.edu/~eppstein/PADS/ + """ + # Modified code from David Eppstein, April 2006 + # http://www.ics.uci.edu/~eppstein/PADS/ + # Kruskal's algorithm: sort edges by weight, and add them one at a time. + # We use Kruskal's algorithm, first because it is very simple to + # implement once UnionFind exists, and second, because the only slow + # part (the sort) is sped up by being built in to Python. + from networkx.utils import UnionFind + if G.is_directed(): + raise nx.NetworkXError( + "Mimimum spanning tree not defined for directed graphs.") + + subtrees = UnionFind() + edges = sorted(G.edges(data=True),key=lambda t: t[2].get(weight,1)) + for u,v,d in edges: + if subtrees[u] != subtrees[v]: + if data: + yield (u,v,d) + else: + yield (u,v) + subtrees.union(u,v) + + +def minimum_spanning_tree(G,weight='weight'): + """Return a minimum spanning tree or forest of an undirected + weighted graph. + + A minimum spanning tree is a subgraph of the graph (a tree) with + the minimum sum of edge weights. + + If the graph is not connected a spanning forest is constructed. A + spanning forest is a union of the spanning trees for each + connected component of the graph. + + Parameters + ---------- + G : NetworkX Graph + + weight : string + Edge data key to use for weight (default 'weight'). + + Returns + ------- + G : NetworkX Graph + A minimum spanning tree or forest. + + Examples + -------- + >>> G=nx.cycle_graph(4) + >>> G.add_edge(0,3,weight=2) # assign weight 2 to edge 0-3 + >>> T=nx.minimum_spanning_tree(G) + >>> print(sorted(T.edges(data=True))) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + + Notes + ----- + Uses Kruskal's algorithm. + + If the graph edges do not have a weight attribute a default weight of 1 + will be used. + """ + T=nx.Graph(nx.minimum_spanning_edges(G,weight=weight,data=True)) + # Add isolated nodes + if len(T)!=len(G): + T.add_nodes_from([n for n,d in G.degree().items() if d==0]) + # Add node and graph attributes as shallow copy + for n in T: + T.node[n]=G.node[n].copy() + T.graph=G.graph.copy() + return T + +kruskal_mst=minimum_spanning_tree + +def prim_mst_edges(G, weight = 'weight', data = True): + """Generate edges in a minimum spanning forest of an undirected + weighted graph. + + A minimum spanning tree is a subgraph of the graph (a tree) + with the minimum sum of edge weights. A spanning forest is a + union of the spanning trees for each connected component of the graph. + + Parameters + ---------- + G : NetworkX Graph + + weight : string + Edge data key to use for weight (default 'weight'). + + data : bool, optional + If True yield the edge data along with the edge. + + Returns + ------- + edges : iterator + A generator that produces edges in the minimum spanning tree. + The edges are three-tuples (u,v,w) where w is the weight. + + Examples + -------- + >>> G=nx.cycle_graph(4) + >>> G.add_edge(0,3,weight=2) # assign weight 2 to edge 0-3 + >>> mst=nx.prim_mst_edges(G,data=False) # a generator of MST edges + >>> edgelist=list(mst) # make a list of the edges + >>> print(sorted(edgelist)) + [(0, 1), (1, 2), (2, 3)] + + Notes + ----- + Uses Prim's algorithm. + + If the graph edges do not have a weight attribute a default weight of 1 + will be used. + """ + + if G.is_directed(): + raise nx.NetworkXError( + "Mimimum spanning tree not defined for directed graphs.") + + nodes = G.nodes() + + while nodes: + u = nodes.pop(0) + frontier = [] + visited = [u] + for u, v in G.edges(u): + heappush(frontier, (G[u][v].get(weight, 1), u, v)) + + while frontier: + W, u, v = heappop(frontier) + if v in visited: + continue + visited.append(v) + nodes.remove(v) + for v, w in G.edges(v): + if not w in visited: + heappush(frontier, (G[v][w].get(weight, 1), v, w)) + if data: + yield u, v, G[u][v] + else: + yield u, v + + +def prim_mst(G, weight = 'weight'): + """Return a minimum spanning tree or forest of an undirected + weighted graph. + + A minimum spanning tree is a subgraph of the graph (a tree) with + the minimum sum of edge weights. + + If the graph is not connected a spanning forest is constructed. A + spanning forest is a union of the spanning trees for each + connected component of the graph. + + Parameters + ---------- + G : NetworkX Graph + + weight : string + Edge data key to use for weight (default 'weight'). + + Returns + ------- + G : NetworkX Graph + A minimum spanning tree or forest. + + Examples + -------- + >>> G=nx.cycle_graph(4) + >>> G.add_edge(0,3,weight=2) # assign weight 2 to edge 0-3 + >>> T=nx.prim_mst(G) + >>> print(sorted(T.edges(data=True))) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + + Notes + ----- + Uses Prim's algorithm. + + If the graph edges do not have a weight attribute a default weight of 1 + will be used. + """ + + T=nx.Graph(nx.prim_mst_edges(G,weight=weight,data=True)) + # Add isolated nodes + if len(T)!=len(G): + T.add_nodes_from([n for n,d in G.degree().items() if d==0]) + # Add node and graph attributes as shallow copy + for n in T: + T.node[n]=G.node[n].copy() + T.graph=G.graph.copy() + return T + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/__init__.py new file mode 100644 index 0000000..0ebc6ab --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/__init__.py @@ -0,0 +1,4 @@ +from networkx.algorithms.operators.all import * +from networkx.algorithms.operators.binary import * +from networkx.algorithms.operators.product import * +from networkx.algorithms.operators.unary import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/all.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/all.py new file mode 100644 index 0000000..755256b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/all.py @@ -0,0 +1,151 @@ +"""Operations on many graphs. +""" +# Copyright (C) 2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +try: + from itertools import izip_longest as zip_longest +except ImportError: # Python3 has zip_longest + from itertools import zip_longest +import networkx as nx +from networkx.utils import is_string_like + +__author__ = """\n""".join([ 'Robert King ', + 'Aric Hagberg ']) + +__all__ = ['union_all', 'compose_all', 'disjoint_union_all', + 'intersection_all'] + +def union_all(graphs, rename=(None,) , name=None): + """Return the union of all graphs. + + The graphs must be disjoint, otherwise an exception is raised. + + Parameters + ---------- + graphs : list of graphs + List of NetworkX graphs + + rename : bool , default=(None, None) + Node names of G and H can be changed by specifying the tuple + rename=('G-','H-') (for example). Node "u" in G is then renamed + "G-u" and "v" in H is renamed "H-v". + + name : string + Specify the name for the union graph@not_implemnted_for('direct + + Returns + ------- + U : a graph with the same type as the first graph in list + + Notes + ----- + To force a disjoint union with node relabeling, use + disjoint_union_all(G,H) or convert_node_labels_to integers(). + + Graph, edge, and node attributes are propagated to the union graph. + If a graph attribute is present in multiple graphs, then the value + from the last graph in the list with that attribute is used. + + See Also + -------- + union + disjoint_union_all + """ + graphs_names = zip_longest(graphs,rename) + U, gname = next(graphs_names) + for H,hname in graphs_names: + U = nx.union(U, H, (gname,hname),name=name) + gname = None + return U + +def disjoint_union_all(graphs): + """Return the disjoint union of all graphs. + + This operation forces distinct integer node labels starting with 0 + for the first graph in the list and numbering consecutively. + + Parameters + ---------- + graphs : list + List of NetworkX graphs + + Returns + ------- + U : A graph with the same type as the first graph in list + + Notes + ----- + It is recommended that the graphs be either all directed or all undirected. + + Graph, edge, and node attributes are propagated to the union graph. + If a graph attribute is present in multiple graphs, then the value + from the last graph in the list with that attribute is used. + """ + graphs = iter(graphs) + U = next(graphs) + for H in graphs: + U = nx.disjoint_union(U, H) + return U + +def compose_all(graphs, name=None): + """Return the composition of all graphs. + + Composition is the simple union of the node sets and edge sets. + The node sets of the supplied graphs need not be disjoint. + + Parameters + ---------- + graphs : list + List of NetworkX graphs + + name : string + Specify name for new graph + + Returns + ------- + C : A graph with the same type as the first graph in list + + Notes + ----- + It is recommended that the supplied graphs be either all directed or all + undirected. + + Graph, edge, and node attributes are propagated to the union graph. + If a graph attribute is present in multiple graphs, then the value + from the last graph in the list with that attribute is used. + """ + graphs = iter(graphs) + C = next(graphs) + for H in graphs: + C = nx.compose(C, H, name=name) + return C + +def intersection_all(graphs): + """Return a new graph that contains only the edges that exist in + all graphs. + + All supplied graphs must have the same node set. + + Parameters + ---------- + graphs_list : list + List of NetworkX graphs + + Returns + ------- + R : A new graph with the same type as the first graph in list + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. + """ + graphs = iter(graphs) + R = next(graphs) + for H in graphs: + R = nx.intersection(R, H) + return R diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/binary.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/binary.py new file mode 100644 index 0000000..a710008 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/binary.py @@ -0,0 +1,329 @@ +""" +Operations on graphs including union, intersection, difference. +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import is_string_like +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) +__all__ = ['union', 'compose', 'disjoint_union', 'intersection', + 'difference', 'symmetric_difference'] + +def union(G, H, rename=(None, None), name=None): + """ Return the union of graphs G and H. + + Graphs G and H must be disjoint, otherwise an exception is raised. + + Parameters + ---------- + G,H : graph + A NetworkX graph + + create_using : NetworkX graph + Use specified graph for result. Otherwise + + rename : bool , default=(None, None) + Node names of G and H can be changed by specifying the tuple + rename=('G-','H-') (for example). Node "u" in G is then renamed + "G-u" and "v" in H is renamed "H-v". + + name : string + Specify the name for the union graph + + Returns + ------- + U : A union graph with the same type as G. + + Notes + ----- + To force a disjoint union with node relabeling, use + disjoint_union(G,H) or convert_node_labels_to integers(). + + Graph, edge, and node attributes are propagated from G and H + to the union graph. If a graph attribute is present in both + G and H the value from H is used. + + See Also + -------- + disjoint_union + """ + # Union is the same type as G + R = G.__class__() + if name is None: + name = "union( %s, %s )"%(G.name,H.name) + R.name = name + + # rename graph to obtain disjoint node labels + def add_prefix(graph, prefix): + if prefix is None: + return graph + def label(x): + if is_string_like(x): + name=prefix+x + else: + name=prefix+repr(x) + return name + return nx.relabel_nodes(graph, label) + G = add_prefix(G,rename[0]) + H = add_prefix(H,rename[1]) + if set(G) & set(H): + raise nx.NetworkXError('The node sets of G and H are not disjoint.', + 'Use appropriate rename=(Gprefix,Hprefix)' + 'or use disjoint_union(G,H).') + if G.is_multigraph(): + G_edges = G.edges_iter(keys=True, data=True) + else: + G_edges = G.edges_iter(data=True) + if H.is_multigraph(): + H_edges = H.edges_iter(keys=True, data=True) + else: + H_edges = H.edges_iter(data=True) + + # add nodes + R.add_nodes_from(G) + R.add_edges_from(G_edges) + # add edges + R.add_nodes_from(H) + R.add_edges_from(H_edges) + # add node attributes + R.node.update(G.node) + R.node.update(H.node) + # add graph attributes, H attributes take precedent over G attributes + R.graph.update(G.graph) + R.graph.update(H.graph) + + + return R + +def disjoint_union(G,H): + """ Return the disjoint union of graphs G and H. + + This algorithm forces distinct integer node labels. + + Parameters + ---------- + G,H : graph + A NetworkX graph + + Returns + ------- + U : A union graph with the same type as G. + + Notes + ----- + A new graph is created, of the same class as G. It is recommended + that G and H be either both directed or both undirected. + + The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are + relabeled len(G) to len(G)+len(H)-1. + + Graph, edge, and node attributes are propagated from G and H + to the union graph. If a graph attribute is present in both + G and H the value from H is used. + """ + R1=nx.convert_node_labels_to_integers(G) + R2=nx.convert_node_labels_to_integers(H,first_label=len(R1)) + R=union(R1,R2) + R.name="disjoint_union( %s, %s )"%(G.name,H.name) + R.graph.update(G.graph) + R.graph.update(H.graph) + return R + + +def intersection(G, H): + """Return a new graph that contains only the edges that exist in + both G and H. + + The node sets of H and G must be the same. + + Parameters + ---------- + G,H : graph + A NetworkX graph. G and H must have the same node sets. + + Returns + ------- + GH : A new graph with the same type as G. + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. If you want a new graph of the intersection of G and H + with the attributes (including edge data) from G use remove_nodes_from() + as follows + + >>> G=nx.path_graph(3) + >>> H=nx.path_graph(5) + >>> R=G.copy() + >>> R.remove_nodes_from(n for n in G if n not in H) + """ + # create new graph + R=nx.create_empty_copy(G) + + R.name="Intersection of (%s and %s)"%(G.name, H.name) + + if set(G)!=set(H): + raise nx.NetworkXError("Node sets of graphs are not equal") + + if G.number_of_edges()<=H.number_of_edges(): + if G.is_multigraph(): + edges=G.edges_iter(keys=True) + else: + edges=G.edges_iter() + for e in edges: + if H.has_edge(*e): + R.add_edge(*e) + else: + if H.is_multigraph(): + edges=H.edges_iter(keys=True) + else: + edges=H.edges_iter() + for e in edges: + if G.has_edge(*e): + R.add_edge(*e) + + return R + +def difference(G, H): + """Return a new graph that contains the edges that exist in G but not in H. + + The node sets of H and G must be the same. + + Parameters + ---------- + G,H : graph + A NetworkX graph. G and H must have the same node sets. + + Returns + ------- + D : A new graph with the same type as G. + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. If you want a new graph of the difference of G and H with + with the attributes (including edge data) from G use remove_nodes_from() + as follows: + + >>> G=nx.path_graph(3) + >>> H=nx.path_graph(5) + >>> R=G.copy() + >>> R.remove_nodes_from(n for n in G if n in H) + """ + # create new graph + R=nx.create_empty_copy(G) + R.name="Difference of (%s and %s)"%(G.name, H.name) + + if set(G)!=set(H): + raise nx.NetworkXError("Node sets of graphs not equal") + + if G.is_multigraph(): + edges=G.edges_iter(keys=True) + else: + edges=G.edges_iter() + for e in edges: + if not H.has_edge(*e): + R.add_edge(*e) + return R + +def symmetric_difference(G, H): + """Return new graph with edges that exist in either G or H but not both. + + The node sets of H and G must be the same. + + Parameters + ---------- + G,H : graph + A NetworkX graph. G and H must have the same node sets. + + Returns + ------- + D : A new graph with the same type as G. + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. + """ + # create new graph + R=nx.create_empty_copy(G) + R.name="Symmetric difference of (%s and %s)"%(G.name, H.name) + + if set(G)!=set(H): + raise nx.NetworkXError("Node sets of graphs not equal") + + gnodes=set(G) # set of nodes in G + hnodes=set(H) # set of nodes in H + nodes=gnodes.symmetric_difference(hnodes) + R.add_nodes_from(nodes) + + if G.is_multigraph(): + edges=G.edges_iter(keys=True) + else: + edges=G.edges_iter() + # we could copy the data here but then this function doesn't + # match intersection and difference + for e in edges: + if not H.has_edge(*e): + R.add_edge(*e) + + if H.is_multigraph(): + edges=H.edges_iter(keys=True) + else: + edges=H.edges_iter() + for e in edges: + if not G.has_edge(*e): + R.add_edge(*e) + return R + +def compose(G, H, name=None): + """Return a new graph of G composed with H. + + Composition is the simple union of the node sets and edge sets. + The node sets of G and H need not be disjoint. + + Parameters + ---------- + G,H : graph + A NetworkX graph + + name : string + Specify name for new graph + + Returns + ------- + C: A new graph with the same type as G + + Notes + ----- + It is recommended that G and H be either both directed or both undirected. + Attributes from H take precedent over attributes from G. + """ + if name is None: + name="compose( %s, %s )"%(G.name,H.name) + R=G.__class__() + R.name=name + R.add_nodes_from(H.nodes()) + R.add_nodes_from(G.nodes()) + if H.is_multigraph(): + R.add_edges_from(H.edges_iter(keys=True,data=True)) + else: + R.add_edges_from(H.edges_iter(data=True)) + if G.is_multigraph(): + R.add_edges_from(G.edges_iter(keys=True,data=True)) + else: + R.add_edges_from(G.edges_iter(data=True)) + + # add node attributes, H attributes take precedent over G attributes + R.node.update(G.node) + R.node.update(H.node) + # add graph attributes, H attributes take precedent over G attributes + R.graph.update(G.graph) + R.graph.update(H.graph) + return R diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/product.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/product.py new file mode 100644 index 0000000..0fa8a17 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/product.py @@ -0,0 +1,330 @@ +""" +Graph products. +""" +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from itertools import product + +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)' + 'Ben Edwards(bedwards@cs.unm.edu)']) + +__all__ = ['tensor_product','cartesian_product', + 'lexicographic_product', 'strong_product'] + +def _dict_product(d1,d2): + return dict((k,(d1.get(k),d2.get(k))) for k in set(d1)|set(d2)) + + +# Generators for producting graph products +def _node_product(G,H): + for u,v in product(G, H): + yield ((u,v), _dict_product(G.node[u], H.node[v])) + +def _directed_edges_cross_edges(G,H): + if not G.is_multigraph() and not H.is_multigraph(): + for u,v,c in G.edges_iter(data=True): + for x,y,d in H.edges_iter(data=True): + yield (u,x),(v,y),_dict_product(c,d) + if not G.is_multigraph() and H.is_multigraph(): + for u,v,c in G.edges_iter(data=True): + for x,y,k,d in H.edges_iter(data=True,keys=True): + yield (u,x),(v,y),k,_dict_product(c,d) + if G.is_multigraph() and not H.is_multigraph(): + for u,v,k,c in G.edges_iter(data=True,keys=True): + for x,y,d in H.edges_iter(data=True): + yield (u,x),(v,y),k,_dict_product(c,d) + if G.is_multigraph() and H.is_multigraph(): + for u,v,j,c in G.edges_iter(data=True,keys=True): + for x,y,k,d in H.edges_iter(data=True,keys=True): + yield (u,x),(v,y),(j,k),_dict_product(c,d) + +def _undirected_edges_cross_edges(G,H): + if not G.is_multigraph() and not H.is_multigraph(): + for u,v,c in G.edges_iter(data=True): + for x,y,d in H.edges_iter(data=True): + yield (v,x),(u,y),_dict_product(c,d) + if not G.is_multigraph() and H.is_multigraph(): + for u,v,c in G.edges_iter(data=True): + for x,y,k,d in H.edges_iter(data=True,keys=True): + yield (v,x),(u,y),k,_dict_product(c,d) + if G.is_multigraph() and not H.is_multigraph(): + for u,v,k,c in G.edges_iter(data=True,keys=True): + for x,y,d in H.edges_iter(data=True): + yield (v,x),(u,y),k,_dict_product(c,d) + if G.is_multigraph() and H.is_multigraph(): + for u,v,j,c in G.edges_iter(data=True,keys=True): + for x,y,k,d in H.edges_iter(data=True,keys=True): + yield (v,x),(u,y),(j,k),_dict_product(c,d) + +def _edges_cross_nodes(G,H): + if G.is_multigraph(): + for u,v,k,d in G.edges_iter(data=True,keys=True): + for x in H: + yield (u,x),(v,x),k,d + else: + for u,v,d in G.edges_iter(data=True): + for x in H: + if H.is_multigraph(): + yield (u,x),(v,x),None,d + else: + yield (u,x),(v,x),d + + +def _nodes_cross_edges(G,H): + if H.is_multigraph(): + for x in G: + for u,v,k,d in H.edges_iter(data=True,keys=True): + yield (x,u),(x,v),k,d + else: + for x in G: + for u,v,d in H.edges_iter(data=True): + if G.is_multigraph(): + yield (x,u),(x,v),None,d + else: + yield (x,u),(x,v),d + +def _edges_cross_nodes_and_nodes(G,H): + if G.is_multigraph(): + for u,v,k,d in G.edges_iter(data=True,keys=True): + for x in H: + for y in H: + yield (u,x),(v,y),k,d + else: + for u,v,d in G.edges_iter(data=True): + for x in H: + for y in H: + if H.is_multigraph(): + yield (u,x),(v,y),None,d + else: + yield (u,x),(v,y),d + +def _init_product_graph(G,H): + if not G.is_directed() == H.is_directed(): + raise nx.NetworkXError("G and H must be both directed or", + "both undirected") + if G.is_multigraph() or H.is_multigraph(): + GH = nx.MultiGraph() + else: + GH = nx.Graph() + if G.is_directed(): + GH = GH.to_directed() + return GH + + +def tensor_product(G,H): + r"""Return the tensor product of G and H. + + The tensor product P of the graphs G and H has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + P has an edge ((u,v),(x,y)) if and only if (u,v) is an edge in G + and (x,y) is an edge in H. + + Sometimes referred to as the categorical product. + + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The tensor product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + For example + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0,a1=True) + >>> H.add_node('a',a2='Spam') + >>> P = nx.tensor_product(G,H) + >>> P.nodes() + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G,H) + GH.add_nodes_from(_node_product(G,H)) + GH.add_edges_from(_directed_edges_cross_edges(G,H)) + if not GH.is_directed(): + GH.add_edges_from(_undirected_edges_cross_edges(G,H)) + GH.name = "Tensor product("+G.name+","+H.name+")" + return GH + +def cartesian_product(G,H): + """Return the Cartesian product of G and H. + + The tensor product P of the graphs G and H has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + P has an edge ((u,v),(x,y)) if and only if (u,v) is an edge in G + and x==y or and (x,y) is an edge in H and u==v. + and (x,y) is an edge in H. + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The Cartesian product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + For example + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0,a1=True) + >>> H.add_node('a',a2='Spam') + >>> P = nx.cartesian_product(G,H) + >>> P.nodes() + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + if not G.is_directed() == H.is_directed(): + raise nx.NetworkXError("G and H must be both directed or", + "both undirected") + GH = _init_product_graph(G,H) + GH.add_nodes_from(_node_product(G,H)) + GH.add_edges_from(_edges_cross_nodes(G,H)) + GH.add_edges_from(_nodes_cross_edges(G,H)) + GH.name = "Cartesian product("+G.name+","+H.name+")" + return GH + +def lexicographic_product(G,H): + """Return the lexicographic product of G and H. + + The lexicographical product P of the graphs G and H has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + P has an edge ((u,v),(x,y)) if and only if (u,v) is an edge in G + or u==v and (x,y) is an edge in H. + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The Cartesian product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + For example + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0,a1=True) + >>> H.add_node('a',a2='Spam') + >>> P = nx.lexicographic_product(G,H) + >>> P.nodes() + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G,H) + GH.add_nodes_from(_node_product(G,H)) + # Edges in G regardless of H designation + GH.add_edges_from(_edges_cross_nodes_and_nodes(G,H)) + # For each x in G, only if there is an edge in H + GH.add_edges_from(_nodes_cross_edges(G,H)) + GH.name = "Lexicographic product("+G.name+","+H.name+")" + return GH + +def strong_product(G,H): + """Return the strong product of G and H. + + The strong product P of the graphs G and H has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + P has an edge ((u,v),(x,y)) if and only if + u==v and (x,y) is an edge in H, or + x==y and (u,v) is an edge in G, or + (u,v) is an edge in G and (x,y) is an edge in H. + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The Cartesian product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + For example + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0,a1=True) + >>> H.add_node('a',a2='Spam') + >>> P = nx.strong_product(G,H) + >>> P.nodes() + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G,H) + GH.add_nodes_from(_node_product(G,H)) + GH.add_edges_from(_nodes_cross_edges(G,H)) + GH.add_edges_from(_edges_cross_nodes(G,H)) + GH.add_edges_from(_directed_edges_cross_edges(G,H)) + if not GH.is_directed(): + GH.add_edges_from(_undirected_edges_cross_edges(G,H)) + GH.name = "Strong product("+G.name+","+H.name+")" + return GH diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_all.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_all.py new file mode 100644 index 0000000..fc4fa4a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_all.py @@ -0,0 +1,167 @@ +from nose.tools import * +import networkx as nx +from networkx.testing import * + +def test_union_all_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph['name'] = 'g' + + h = g.copy() + h.graph['name'] = 'h' + h.graph['attr'] = 'attr' + h.node[0]['x'] = 7 + + j = g.copy() + j.graph['name'] = 'j' + j.graph['attr'] = 'attr' + j.node[0]['x'] = 7 + + ghj = nx.union_all([g, h, j], rename=('g', 'h', 'j')) + assert_equal( set(ghj.nodes()) , set(['h0', 'h1', 'g0', 'g1', 'j0', 'j1']) ) + for n in ghj: + graph, node = n + assert_equal( ghj.node[n], eval(graph).node[int(node)] ) + + assert_equal(ghj.graph['attr'],'attr') + assert_equal(ghj.graph['name'],'j') # j graph attributes take precendent + + + +def test_intersection_all(): + G=nx.Graph() + H=nx.Graph() + R=nx.Graph() + G.add_nodes_from([1,2,3,4]) + G.add_edge(1,2) + G.add_edge(2,3) + H.add_nodes_from([1,2,3,4]) + H.add_edge(2,3) + H.add_edge(3,4) + R.add_nodes_from([1,2,3,4]) + R.add_edge(2,3) + R.add_edge(4,1) + I=nx.intersection_all([G,H,R]) + assert_equal( set(I.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(I.edges()) , [(2,3)] ) + + +def test_intersection_all_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph['name'] = 'g' + + h = g.copy() + h.graph['name'] = 'h' + h.graph['attr'] = 'attr' + h.node[0]['x'] = 7 + + gh = nx.intersection_all([g, h]) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , sorted(g.edges()) ) + + h.remove_node(0) + assert_raises(nx.NetworkXError, nx.intersection, g, h) + +def test_intersection_all_multigraph_attributes(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.intersection_all([g, h]) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , [(0,1)] ) + assert_equal( sorted(gh.edges(keys=True)) , [(0,1,0)] ) + +def test_union_all_and_compose_all(): + K3=nx.complete_graph(3) + P3=nx.path_graph(3) + + G1=nx.DiGraph() + G1.add_edge('A','B') + G1.add_edge('A','C') + G1.add_edge('A','D') + G2=nx.DiGraph() + G2.add_edge('1','2') + G2.add_edge('1','3') + G2.add_edge('1','4') + + G=nx.union_all([G1,G2]) + H=nx.compose_all([G1,G2]) + assert_edges_equal(G.edges(),H.edges()) + assert_false(G.has_edge('A','1')) + assert_raises(nx.NetworkXError, nx.union, K3, P3) + H1=nx.union_all([H,G1],rename=('H','G1')) + assert_equal(sorted(H1.nodes()), + ['G1A', 'G1B', 'G1C', 'G1D', + 'H1', 'H2', 'H3', 'H4', 'HA', 'HB', 'HC', 'HD']) + + H2=nx.union_all([H,G2],rename=("H","")) + assert_equal(sorted(H2.nodes()), + ['1', '2', '3', '4', + 'H1', 'H2', 'H3', 'H4', 'HA', 'HB', 'HC', 'HD']) + + assert_false(H1.has_edge('NB','NA')) + + G=nx.compose_all([G,G]) + assert_edges_equal(G.edges(),H.edges()) + + G2=nx.union_all([G2,G2],rename=('','copy')) + assert_equal(sorted(G2.nodes()), + ['1', '2', '3', '4', 'copy1', 'copy2', 'copy3', 'copy4']) + + assert_equal(G2.neighbors('copy4'),[]) + assert_equal(sorted(G2.neighbors('copy1')),['copy2', 'copy3', 'copy4']) + assert_equal(len(G),8) + assert_equal(nx.number_of_edges(G),6) + + E=nx.disjoint_union_all([G,G]) + assert_equal(len(E),16) + assert_equal(nx.number_of_edges(E),12) + + E=nx.disjoint_union_all([G1,G2]) + assert_equal(sorted(E.nodes()),[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + + G1=nx.DiGraph() + G1.add_edge('A','B') + G2=nx.DiGraph() + G2.add_edge(1,2) + G3=nx.DiGraph() + G3.add_edge(11,22) + G4=nx.union_all([G1,G2,G3],rename=("G1","G2","G3")) + assert_equal(sorted(G4.nodes()), + ['G1A', 'G1B', 'G21', 'G22', + 'G311', 'G322']) + + +def test_union_all_multigraph(): + G=nx.MultiGraph() + G.add_edge(1,2,key=0) + G.add_edge(1,2,key=1) + H=nx.MultiGraph() + H.add_edge(3,4,key=0) + H.add_edge(3,4,key=1) + GH=nx.union_all([G,H]) + assert_equal( set(GH) , set(G)|set(H)) + assert_equal( set(GH.edges(keys=True)) , + set(G.edges(keys=True))|set(H.edges(keys=True))) + + +def test_input_output(): + l = [nx.Graph([(1,2)]),nx.Graph([(3,4)])] + U = nx.disjoint_union_all(l) + assert_equal(len(l),2) + C = nx.compose_all(l) + assert_equal(len(l),2) + l = [nx.Graph([(1,2)]),nx.Graph([(1,2)])] + R = nx.intersection_all(l) + assert_equal(len(l),2) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_binary.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_binary.py new file mode 100644 index 0000000..34b7e6e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_binary.py @@ -0,0 +1,270 @@ +from nose.tools import * +import networkx as nx +from networkx import * +from networkx.testing import * + +def test_union_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph['name'] = 'g' + + h = g.copy() + h.graph['name'] = 'h' + h.graph['attr'] = 'attr' + h.node[0]['x'] = 7 + + gh = nx.union(g, h, rename=('g', 'h')) + assert_equal( set(gh.nodes()) , set(['h0', 'h1', 'g0', 'g1']) ) + for n in gh: + graph, node = n + assert_equal( gh.node[n], eval(graph).node[int(node)] ) + + assert_equal(gh.graph['attr'],'attr') + assert_equal(gh.graph['name'],'h') # h graph attributes take precendent + +def test_intersection(): + G=nx.Graph() + H=nx.Graph() + G.add_nodes_from([1,2,3,4]) + G.add_edge(1,2) + G.add_edge(2,3) + H.add_nodes_from([1,2,3,4]) + H.add_edge(2,3) + H.add_edge(3,4) + I=nx.intersection(G,H) + assert_equal( set(I.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(I.edges()) , [(2,3)] ) + + +def test_intersection_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph['name'] = 'g' + + h = g.copy() + h.graph['name'] = 'h' + h.graph['attr'] = 'attr' + h.node[0]['x'] = 7 + + gh = nx.intersection(g, h) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , sorted(g.edges()) ) + + h.remove_node(0) + assert_raises(nx.NetworkXError, nx.intersection, g, h) + + + +def test_intersection_multigraph_attributes(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.intersection(g, h) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , [(0,1)] ) + assert_equal( sorted(gh.edges(keys=True)) , [(0,1,0)] ) + + +def test_difference(): + G=nx.Graph() + H=nx.Graph() + G.add_nodes_from([1,2,3,4]) + G.add_edge(1,2) + G.add_edge(2,3) + H.add_nodes_from([1,2,3,4]) + H.add_edge(2,3) + H.add_edge(3,4) + D=nx.difference(G,H) + assert_equal( set(D.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(D.edges()) , [(1,2)] ) + D=nx.difference(H,G) + assert_equal( set(D.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(D.edges()) , [(3,4)] ) + D=nx.symmetric_difference(G,H) + assert_equal( set(D.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(D.edges()) , [(1,2),(3,4)] ) + + +def test_difference2(): + G=nx.Graph() + H=nx.Graph() + G.add_nodes_from([1,2,3,4]) + H.add_nodes_from([1,2,3,4]) + G.add_edge(1,2) + H.add_edge(1,2) + G.add_edge(2,3) + D=nx.difference(G,H) + assert_equal( set(D.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(D.edges()) , [(2,3)] ) + D=nx.difference(H,G) + assert_equal( set(D.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(D.edges()) , [] ) + H.add_edge(3,4) + D=nx.difference(H,G) + assert_equal( set(D.nodes()) , set([1,2,3,4]) ) + assert_equal( sorted(D.edges()) , [(3,4)] ) + + +def test_difference_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph['name'] = 'g' + + h = g.copy() + h.graph['name'] = 'h' + h.graph['attr'] = 'attr' + h.node[0]['x'] = 7 + + gh = nx.difference(g, h) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , []) + + h.remove_node(0) + assert_raises(nx.NetworkXError, nx.intersection, g, h) + +def test_difference_multigraph_attributes(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.difference(g, h) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , [(0,1),(0,1)] ) + assert_equal( sorted(gh.edges(keys=True)) , [(0,1,1),(0,1,2)] ) + + +@raises(nx.NetworkXError) +def test_difference_raise(): + G = nx.path_graph(4) + H = nx.path_graph(3) + GH = nx.difference(G, H) + +def test_symmetric_difference_multigraph(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.symmetric_difference(g, h) + assert_equal( set(gh.nodes()) , set(g.nodes()) ) + assert_equal( set(gh.nodes()) , set(h.nodes()) ) + assert_equal( sorted(gh.edges()) , 3*[(0,1)] ) + assert_equal( sorted(sorted(e) for e in gh.edges(keys=True)), + [[0,1,1],[0,1,2],[0,1,3]] ) + +@raises(nx.NetworkXError) +def test_symmetric_difference_raise(): + G = nx.path_graph(4) + H = nx.path_graph(3) + GH = nx.symmetric_difference(G, H) + +def test_union_and_compose(): + K3=complete_graph(3) + P3=path_graph(3) + + G1=nx.DiGraph() + G1.add_edge('A','B') + G1.add_edge('A','C') + G1.add_edge('A','D') + G2=nx.DiGraph() + G2.add_edge('1','2') + G2.add_edge('1','3') + G2.add_edge('1','4') + + G=union(G1,G2) + H=compose(G1,G2) + assert_edges_equal(G.edges(),H.edges()) + assert_false(G.has_edge('A',1)) + assert_raises(nx.NetworkXError, nx.union, K3, P3) + H1=union(H,G1,rename=('H','G1')) + assert_equal(sorted(H1.nodes()), + ['G1A', 'G1B', 'G1C', 'G1D', + 'H1', 'H2', 'H3', 'H4', 'HA', 'HB', 'HC', 'HD']) + + H2=union(H,G2,rename=("H","")) + assert_equal(sorted(H2.nodes()), + ['1', '2', '3', '4', + 'H1', 'H2', 'H3', 'H4', 'HA', 'HB', 'HC', 'HD']) + + assert_false(H1.has_edge('NB','NA')) + + G=compose(G,G) + assert_edges_equal(G.edges(),H.edges()) + + G2=union(G2,G2,rename=('','copy')) + assert_equal(sorted(G2.nodes()), + ['1', '2', '3', '4', 'copy1', 'copy2', 'copy3', 'copy4']) + + assert_equal(G2.neighbors('copy4'),[]) + assert_equal(sorted(G2.neighbors('copy1')),['copy2', 'copy3', 'copy4']) + assert_equal(len(G),8) + assert_equal(number_of_edges(G),6) + + E=disjoint_union(G,G) + assert_equal(len(E),16) + assert_equal(number_of_edges(E),12) + + E=disjoint_union(G1,G2) + assert_equal(sorted(E.nodes()),[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + + +def test_union_multigraph(): + G=nx.MultiGraph() + G.add_edge(1,2,key=0) + G.add_edge(1,2,key=1) + H=nx.MultiGraph() + H.add_edge(3,4,key=0) + H.add_edge(3,4,key=1) + GH=nx.union(G,H) + assert_equal( set(GH) , set(G)|set(H)) + assert_equal( set(GH.edges(keys=True)) , + set(G.edges(keys=True))|set(H.edges(keys=True))) + +def test_disjoint_union_multigraph(): + G=nx.MultiGraph() + G.add_edge(0,1,key=0) + G.add_edge(0,1,key=1) + H=nx.MultiGraph() + H.add_edge(2,3,key=0) + H.add_edge(2,3,key=1) + GH=nx.disjoint_union(G,H) + assert_equal( set(GH) , set(G)|set(H)) + assert_equal( set(GH.edges(keys=True)) , + set(G.edges(keys=True))|set(H.edges(keys=True))) + + +def test_compose_multigraph(): + G=nx.MultiGraph() + G.add_edge(1,2,key=0) + G.add_edge(1,2,key=1) + H=nx.MultiGraph() + H.add_edge(3,4,key=0) + H.add_edge(3,4,key=1) + GH=nx.compose(G,H) + assert_equal( set(GH) , set(G)|set(H)) + assert_equal( set(GH.edges(keys=True)) , + set(G.edges(keys=True))|set(H.edges(keys=True))) + H.add_edge(1,2,key=2) + GH=nx.compose(G,H) + assert_equal( set(GH) , set(G)|set(H)) + assert_equal( set(GH.edges(keys=True)) , + set(G.edges(keys=True))|set(H.edges(keys=True))) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_product.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_product.py new file mode 100644 index 0000000..b157aac --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_product.py @@ -0,0 +1,334 @@ +import networkx as nx +from networkx import tensor_product,cartesian_product,lexicographic_product,strong_product +from nose.tools import assert_raises, assert_true, assert_equal, raises + +@raises(nx.NetworkXError) +def test_tensor_product_raises(): + P = tensor_product(nx.DiGraph(),nx.Graph()) + +def test_tensor_product_null(): + null=nx.null_graph() + empty10=nx.empty_graph(10) + K3=nx.complete_graph(3) + K10=nx.complete_graph(10) + P3=nx.path_graph(3) + P10=nx.path_graph(10) + # null graph + G=tensor_product(null,null) + assert_true(nx.is_isomorphic(G,null)) + # null_graph X anything = null_graph and v.v. + G=tensor_product(null,empty10) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(null,K3) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(null,K10) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(null,P3) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(null,P10) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(empty10,null) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(K3,null) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(K10,null) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(P3,null) + assert_true(nx.is_isomorphic(G,null)) + G=tensor_product(P10,null) + assert_true(nx.is_isomorphic(G,null)) + +def test_tensor_product_size(): + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + K5 = nx.complete_graph(5) + + G=tensor_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=tensor_product(K3,K5) + assert_equal(nx.number_of_nodes(G),3*5) + + +def test_tensor_product_combinations(): + # basic smoke test, more realistic tests would be usefule + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G=tensor_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=tensor_product(P5,nx.MultiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + G=tensor_product(nx.MultiGraph(P5),K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=tensor_product(nx.MultiGraph(P5),nx.MultiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + + G=tensor_product(nx.DiGraph(P5),nx.DiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + + +def test_tensor_product_classic_result(): + K2 = nx.complete_graph(2) + G = nx.petersen_graph() + G = tensor_product(G,K2) + assert_true(nx.is_isomorphic(G,nx.desargues_graph())) + + G = nx.cycle_graph(5) + G = tensor_product(G,K2) + assert_true(nx.is_isomorphic(G,nx.cycle_graph(10))) + + G = nx.tetrahedral_graph() + G = tensor_product(G,K2) + assert_true(nx.is_isomorphic(G,nx.cubical_graph())) + +def test_tensor_product_random(): + G = nx.erdos_renyi_graph(10,2/10.) + H = nx.erdos_renyi_graph(10,2/10.) + GH = tensor_product(G,H) + + for (u_G,u_H) in GH.nodes_iter(): + for (v_G,v_H) in GH.nodes_iter(): + if H.has_edge(u_H,v_H) and G.has_edge(u_G,v_G): + assert_true(GH.has_edge((u_G,u_H),(v_G,v_H))) + else: + assert_true(not GH.has_edge((u_G,u_H),(v_G,v_H))) + + +def test_cartesian_product_multigraph(): + G=nx.MultiGraph() + G.add_edge(1,2,key=0) + G.add_edge(1,2,key=1) + H=nx.MultiGraph() + H.add_edge(3,4,key=0) + H.add_edge(3,4,key=1) + GH=cartesian_product(G,H) + assert_equal( set(GH) , set([(1, 3), (2, 3), (2, 4), (1, 4)])) + assert_equal( set(GH.edges(keys=True)) , + set([((1, 3), (2, 3), 0), ((1, 3), (2, 3), 1), + ((1, 3), (1, 4), 0), ((1, 3), (1, 4), 1), + ((2, 3), (2, 4), 0), ((2, 3), (2, 4), 1), + ((2, 4), (1, 4), 0), ((2, 4), (1, 4), 1)])) + +@raises(nx.NetworkXError) +def test_cartesian_product_raises(): + P = cartesian_product(nx.DiGraph(),nx.Graph()) + +def test_cartesian_product_null(): + null=nx.null_graph() + empty10=nx.empty_graph(10) + K3=nx.complete_graph(3) + K10=nx.complete_graph(10) + P3=nx.path_graph(3) + P10=nx.path_graph(10) + # null graph + G=cartesian_product(null,null) + assert_true(nx.is_isomorphic(G,null)) + # null_graph X anything = null_graph and v.v. + G=cartesian_product(null,empty10) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(null,K3) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(null,K10) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(null,P3) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(null,P10) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(empty10,null) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(K3,null) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(K10,null) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(P3,null) + assert_true(nx.is_isomorphic(G,null)) + G=cartesian_product(P10,null) + assert_true(nx.is_isomorphic(G,null)) + +def test_cartesian_product_size(): + # order(GXH)=order(G)*order(H) + K5=nx.complete_graph(5) + P5=nx.path_graph(5) + K3=nx.complete_graph(3) + G=cartesian_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + assert_equal(nx.number_of_edges(G), + nx.number_of_edges(P5)*nx.number_of_nodes(K3)+ + nx.number_of_edges(K3)*nx.number_of_nodes(P5)) + G=cartesian_product(K3,K5) + assert_equal(nx.number_of_nodes(G),3*5) + assert_equal(nx.number_of_edges(G), + nx.number_of_edges(K5)*nx.number_of_nodes(K3)+ + nx.number_of_edges(K3)*nx.number_of_nodes(K5)) + +def test_cartesian_product_classic(): + # test some classic product graphs + P2 = nx.path_graph(2) + P3 = nx.path_graph(3) + # cube = 2-path X 2-path + G=cartesian_product(P2,P2) + G=cartesian_product(P2,G) + assert_true(nx.is_isomorphic(G,nx.cubical_graph())) + + # 3x3 grid + G=cartesian_product(P3,P3) + assert_true(nx.is_isomorphic(G,nx.grid_2d_graph(3,3))) + +def test_cartesian_product_random(): + G = nx.erdos_renyi_graph(10,2/10.) + H = nx.erdos_renyi_graph(10,2/10.) + GH = cartesian_product(G,H) + + for (u_G,u_H) in GH.nodes_iter(): + for (v_G,v_H) in GH.nodes_iter(): + if (u_G==v_G and H.has_edge(u_H,v_H)) or \ + (u_H==v_H and G.has_edge(u_G,v_G)): + assert_true(GH.has_edge((u_G,u_H),(v_G,v_H))) + else: + assert_true(not GH.has_edge((u_G,u_H),(v_G,v_H))) + +@raises(nx.NetworkXError) +def test_lexicographic_product_raises(): + P=lexicographic_product(nx.DiGraph(),nx.Graph()) + +def test_lexicographic_product_null(): + null=nx.null_graph() + empty10=nx.empty_graph(10) + K3=nx.complete_graph(3) + K10=nx.complete_graph(10) + P3=nx.path_graph(3) + P10=nx.path_graph(10) + # null graph + G=lexicographic_product(null,null) + assert_true(nx.is_isomorphic(G,null)) + # null_graph X anything = null_graph and v.v. + G=lexicographic_product(null,empty10) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(null,K3) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(null,K10) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(null,P3) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(null,P10) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(empty10,null) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(K3,null) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(K10,null) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(P3,null) + assert_true(nx.is_isomorphic(G,null)) + G=lexicographic_product(P10,null) + assert_true(nx.is_isomorphic(G,null)) + +def test_lexicographic_product_size(): + K5=nx.complete_graph(5) + P5=nx.path_graph(5) + K3=nx.complete_graph(3) + G=lexicographic_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=lexicographic_product(K3,K5) + assert_equal(nx.number_of_nodes(G),3*5) + +def test_lexicographic_product_combinations(): + P5=nx.path_graph(5) + K3=nx.complete_graph(3) + G=lexicographic_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=lexicographic_product(nx.MultiGraph(P5),K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=lexicographic_product(P5,nx.MultiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + G=lexicographic_product(nx.MultiGraph(P5),nx.MultiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + + + + + #No classic easily found classic results for lexicographic product +def test_lexicographic_product_random(): + G = nx.erdos_renyi_graph(10,2/10.) + H = nx.erdos_renyi_graph(10,2/10.) + GH = lexicographic_product(G,H) + + for (u_G,u_H) in GH.nodes_iter(): + for (v_G,v_H) in GH.nodes_iter(): + if G.has_edge(u_G,v_G) or (u_G==v_G and H.has_edge(u_H,v_H)): + assert_true(GH.has_edge((u_G,u_H),(v_G,v_H))) + else: + assert_true(not GH.has_edge((u_G,u_H),(v_G,v_H))) + +@raises(nx.NetworkXError) +def test_strong_product_raises(): + P = strong_product(nx.DiGraph(),nx.Graph()) + +def test_strong_product_null(): + null=nx.null_graph() + empty10=nx.empty_graph(10) + K3=nx.complete_graph(3) + K10=nx.complete_graph(10) + P3=nx.path_graph(3) + P10=nx.path_graph(10) + # null graph + G=strong_product(null,null) + assert_true(nx.is_isomorphic(G,null)) + # null_graph X anything = null_graph and v.v. + G=strong_product(null,empty10) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(null,K3) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(null,K10) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(null,P3) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(null,P10) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(empty10,null) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(K3,null) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(K10,null) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(P3,null) + assert_true(nx.is_isomorphic(G,null)) + G=strong_product(P10,null) + assert_true(nx.is_isomorphic(G,null)) + +def test_strong_product_size(): + K5=nx.complete_graph(5) + P5=nx.path_graph(5) + K3 = nx.complete_graph(3) + G=strong_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=strong_product(K3,K5) + assert_equal(nx.number_of_nodes(G),3*5) + +def test_strong_product_combinations(): + P5=nx.path_graph(5) + K3 = nx.complete_graph(3) + G=strong_product(P5,K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=strong_product(nx.MultiGraph(P5),K3) + assert_equal(nx.number_of_nodes(G),5*3) + G=strong_product(P5,nx.MultiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + G=strong_product(nx.MultiGraph(P5),nx.MultiGraph(K3)) + assert_equal(nx.number_of_nodes(G),5*3) + + + + #No classic easily found classic results for strong product +def test_strong_product_random(): + G = nx.erdos_renyi_graph(10,2/10.) + H = nx.erdos_renyi_graph(10,2/10.) + GH = strong_product(G,H) + + for (u_G,u_H) in GH.nodes_iter(): + for (v_G,v_H) in GH.nodes_iter(): + if (u_G==v_G and H.has_edge(u_H,v_H)) or \ + (u_H==v_H and G.has_edge(u_G,v_G)) or \ + (G.has_edge(u_G,v_G) and H.has_edge(u_H,v_H)): + assert_true(GH.has_edge((u_G,u_H),(v_G,v_H))) + else: + assert_true(not GH.has_edge((u_G,u_H),(v_G,v_H))) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_unary.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_unary.py new file mode 100644 index 0000000..ea10d75 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/tests/test_unary.py @@ -0,0 +1,47 @@ +from nose.tools import * +import networkx as nx +from networkx import * + + +def test_complement(): + null=null_graph() + empty1=empty_graph(1) + empty10=empty_graph(10) + K3=complete_graph(3) + K5=complete_graph(5) + K10=complete_graph(10) + P2=path_graph(2) + P3=path_graph(3) + P5=path_graph(5) + P10=path_graph(10) + #complement of the complete graph is empty + + G=complement(K3) + assert_true(is_isomorphic(G,empty_graph(3))) + G=complement(K5) + assert_true(is_isomorphic(G,empty_graph(5))) + # for any G, G=complement(complement(G)) + P3cc=complement(complement(P3)) + assert_true(is_isomorphic(P3,P3cc)) + nullcc=complement(complement(null)) + assert_true(is_isomorphic(null,nullcc)) + b=bull_graph() + bcc=complement(complement(b)) + assert_true(is_isomorphic(b,bcc)) + +def test_complement_2(): + G1=nx.DiGraph() + G1.add_edge('A','B') + G1.add_edge('A','C') + G1.add_edge('A','D') + G1C=complement(G1) + assert_equal(sorted(G1C.edges()), + [('B', 'A'), ('B', 'C'), + ('B', 'D'), ('C', 'A'), ('C', 'B'), + ('C', 'D'), ('D', 'A'), ('D', 'B'), ('D', 'C')]) + +def test_reverse1(): + # Other tests for reverse are done by the DiGraph and MultiDigraph. + G1=nx.Graph() + assert_raises(nx.NetworkXError, nx.reverse, G1) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/unary.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/unary.py new file mode 100644 index 0000000..fbbb31e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/operators/unary.py @@ -0,0 +1,69 @@ +"""Unary operations on graphs""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import is_string_like +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) +__all__ = ['complement', 'reverse'] + +def complement(G, name=None): + """Return the graph complement of G. + + Parameters + ---------- + G : graph + A NetworkX graph + + name : string + Specify name for new graph + + Returns + ------- + GC : A new graph. + + Notes + ------ + Note that complement() does not create self-loops and also + does not produce parallel edges for MultiGraphs. + + Graph, node, and edge data are not propagated to the new graph. + """ + if name is None: + name="complement(%s)"%(G.name) + R=G.__class__() + R.name=name + R.add_nodes_from(G) + R.add_edges_from( ((n,n2) + for n,nbrs in G.adjacency_iter() + for n2 in G if n2 not in nbrs + if n != n2) ) + return R + +def reverse(G, copy=True): + """Return the reverse directed graph of G. + + Parameters + ---------- + G : directed graph + A NetworkX directed graph + copy : bool + If True, then a new graph is returned. If False, then the graph is + reversed in place. + + Returns + ------- + H : directed graph + The reversed G. + + """ + if not G.is_directed(): + raise nx.NetworkXError("Cannot reverse an undirected graph.") + else: + return G.reverse(copy=copy) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/richclub.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/richclub.py new file mode 100644 index 0000000..5701806 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/richclub.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +import networkx as nx +__author__ = """\n""".join(['Ben Edwards', + 'Aric Hagberg ']) + +__all__ = ['rich_club_coefficient'] + +def rich_club_coefficient(G, normalized=True, Q=100): + """Return the rich-club coefficient of the graph G. + + The rich-club coefficient is the ratio, for every degree k, of the + number of actual to the number of potential edges for nodes + with degree greater than k: + + .. math:: + + \\phi(k) = \\frac{2 Ek}{Nk(Nk-1)} + + where Nk is the number of nodes with degree larger than k, and Ek + be the number of edges among those nodes. + + Parameters + ---------- + G : NetworkX graph + normalized : bool (optional) + Normalize using randomized network (see [1]_) + Q : float (optional, default=100) + If normalized=True build a random network by performing + Q*M double-edge swaps, where M is the number of edges in G, + to use as a null-model for normalization. + + Returns + ------- + rc : dictionary + A dictionary, keyed by degree, with rich club coefficient values. + + Examples + -------- + >>> G = nx.Graph([(0,1),(0,2),(1,2),(1,3),(1,4),(4,5)]) + >>> rc = nx.rich_club_coefficient(G,normalized=False) + >>> rc[0] # doctest: +SKIP + 0.4 + + Notes + ------ + The rich club definition and algorithm are found in [1]_. This + algorithm ignores any edge weights and is not defined for directed + graphs or graphs with parallel edges or self loops. + + Estimates for appropriate values of Q are found in [2]_. + + References + ---------- + .. [1] Julian J. McAuley, Luciano da Fontoura Costa, and Tibério S. Caetano, + "The rich-club phenomenon across complex network hierarchies", + Applied Physics Letters Vol 91 Issue 8, August 2007. + http://arxiv.org/abs/physics/0701290 + .. [2] R. Milo, N. Kashtan, S. Itzkovitz, M. E. J. Newman, U. Alon, + "Uniform generation of random graphs with arbitrary degree + sequences", 2006. http://arxiv.org/abs/cond-mat/0312028 + """ + if G.is_multigraph() or G.is_directed(): + raise Exception('rich_club_coefficient is not implemented for ', + 'directed or multiedge graphs.') + if len(G.selfloop_edges()) > 0: + raise Exception('rich_club_coefficient is not implemented for ', + 'graphs with self loops.') + rc=_compute_rc(G) + if normalized: + # make R a copy of G, randomize with Q*|E| double edge swaps + # and use rich_club coefficient of R to normalize + R = G.copy() + E = R.number_of_edges() + nx.double_edge_swap(R,Q*E,max_tries=Q*E*10) + rcran=_compute_rc(R) + for d in rc: +# if rcran[d] > 0: + rc[d]/=rcran[d] + return rc + + +def _compute_rc(G): + # compute rich club coefficient for all k degrees in G + deghist = nx.degree_histogram(G) + total = sum(deghist) + # number of nodes with degree > k (omit last entry which is zero) + nks = [total-cs for cs in nx.utils.cumulative_sum(deghist) if total-cs > 1] + deg=G.degree() + edge_degrees=sorted(sorted((deg[u],deg[v])) for u,v in G.edges_iter()) + ek=G.number_of_edges() + k1,k2=edge_degrees.pop(0) + rc={} + for d,nk in zip(range(len(nks)),nks): + while k1 <= d: + if len(edge_degrees)==0: + break + k1,k2=edge_degrees.pop(0) + ek-=1 + rc[d] = 2.0*ek/(nk*(nk-1)) + return rc + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/__init__.py new file mode 100644 index 0000000..64846eb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/__init__.py @@ -0,0 +1,6 @@ +from networkx.algorithms.shortest_paths.generic import * +from networkx.algorithms.shortest_paths.unweighted import * +from networkx.algorithms.shortest_paths.weighted import * +from networkx.algorithms.shortest_paths.astar import * +from networkx.algorithms.shortest_paths.dense import * + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/astar.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/astar.py new file mode 100644 index 0000000..7b25d64 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/astar.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +"""Shortest paths and path lengths using A* ("A star") algorithm. +""" + +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +from heapq import heappush, heappop +from networkx import NetworkXError +import networkx as nx + +__author__ = "\n".join(["Salim Fadhley ", + "Matteo Dell'Amico "]) +__all__ = ['astar_path', 'astar_path_length'] + + +def astar_path(G, source, target, heuristic=None, weight='weight'): + """Return a list of nodes in a shortest path between source and target + using the A* ("A-star") algorithm. + + There may be more than one shortest path. This returns only one. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path + + target : node + Ending node for path + + heuristic : function + A function to evaluate the estimate of the distance + from the a node to the target. The function takes + two nodes arguments and must return a number. + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> print(nx.astar_path(G,0,4)) + [0, 1, 2, 3, 4] + >>> G=nx.grid_graph(dim=[3,3]) # nodes are two-tuples (x,y) + >>> def dist(a, b): + ... (x1, y1) = a + ... (x2, y2) = b + ... return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5 + >>> print(nx.astar_path(G,(0,0),(2,2),dist)) + [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)] + + + See Also + -------- + shortest_path, dijkstra_path + + """ + if G.is_multigraph(): + raise NetworkXError("astar_path() not implemented for Multi(Di)Graphs") + + if heuristic is None: + # The default heuristic is h=0 - same as Dijkstra's algorithm + def heuristic(u, v): + return 0 + # The queue stores priority, node, cost to reach, and parent. + # Uses Python heapq to keep in priority order. + # Add each node's hash to the queue to prevent the underlying heap from + # attempting to compare the nodes themselves. The hash breaks ties in the + # priority and is guarenteed unique for all nodes in the graph. + queue = [(0, hash(source), source, 0, None)] + + # Maps enqueued nodes to distance of discovered paths and the + # computed heuristics to target. We avoid computing the heuristics + # more than once and inserting the node into the queue too many times. + enqueued = {} + # Maps explored nodes to parent closest to the source. + explored = {} + + while queue: + # Pop the smallest item from queue. + _, __, curnode, dist, parent = heappop(queue) + + if curnode == target: + path = [curnode] + node = parent + while node is not None: + path.append(node) + node = explored[node] + path.reverse() + return path + + if curnode in explored: + continue + + explored[curnode] = parent + + for neighbor, w in G[curnode].items(): + if neighbor in explored: + continue + ncost = dist + w.get(weight, 1) + if neighbor in enqueued: + qcost, h = enqueued[neighbor] + # if qcost < ncost, a longer path to neighbor remains + # enqueued. Removing it would need to filter the whole + # queue, it's better just to leave it there and ignore + # it when we visit the node a second time. + if qcost <= ncost: + continue + else: + h = heuristic(neighbor, target) + enqueued[neighbor] = ncost, h + heappush(queue, (ncost + h, hash(neighbor), neighbor, + ncost, curnode)) + + raise nx.NetworkXNoPath("Node %s not reachable from %s" % (source, target)) + + +def astar_path_length(G, source, target, heuristic=None, weight='weight'): + """Return the length of the shortest path between source and target using + the A* ("A-star") algorithm. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path + + target : node + Ending node for path + + heuristic : function + A function to evaluate the estimate of the distance + from the a node to the target. The function takes + two nodes arguments and must return a number. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + See Also + -------- + astar_path + + """ + path = astar_path(G, source, target, heuristic, weight) + return sum(G[u][v].get(weight, 1) for u, v in zip(path[:-1], path[1:])) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/dense.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/dense.py new file mode 100644 index 0000000..4dccae6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/dense.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +"""Floyd-Warshall algorithm for shortest paths. +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """Aric Hagberg """ +__all__ = ['floyd_warshall', + 'floyd_warshall_predecessor_and_distance', + 'floyd_warshall_numpy'] + +def floyd_warshall_numpy(G, nodelist=None, weight='weight'): + """Find all-pairs shortest path lengths using Floyd's algorithm. + + Parameters + ---------- + G : NetworkX graph + + nodelist : list, optional + The rows and columns are ordered by the nodes in nodelist. + If nodelist is None then the ordering is produced by G.nodes(). + + weight: string, optional (default= 'weight') + Edge data key corresponding to the edge weight. + + Returns + ------- + distance : NumPy matrix + A matrix of shortest path distances between nodes. + If there is no path between to nodes the corresponding matrix entry + will be Inf. + + Notes + ------ + Floyd's algorithm is appropriate for finding shortest paths in + dense graphs or graphs with negative weights when Dijkstra's + algorithm fails. This algorithm can still fail if there are + negative cycles. It has running time O(n^3) with running space of O(n^2). + """ + try: + import numpy as np + except ImportError: + raise ImportError(\ + "to_numpy_matrix() requires numpy: http://scipy.org/ ") + A = nx.to_numpy_matrix(G, nodelist=nodelist, multigraph_weight=min, + weight=weight) + n,m = A.shape + I = np.identity(n) + A[A==0] = np.inf # set zero entries to inf + A[I==1] = 0 # except diagonal which should be zero + for i in range(n): + A = np.minimum(A, A[i,:] + A[:,i]) + return A + +def floyd_warshall_predecessor_and_distance(G, weight='weight'): + """Find all-pairs shortest path lengths using Floyd's algorithm. + + Parameters + ---------- + G : NetworkX graph + + weight: string, optional (default= 'weight') + Edge data key corresponding to the edge weight. + + Returns + ------- + predecessor,distance : dictionaries + Dictionaries, keyed by source and target, of predecessors and distances + in the shortest path. + + Notes + ------ + Floyd's algorithm is appropriate for finding shortest paths + in dense graphs or graphs with negative weights when Dijkstra's algorithm + fails. This algorithm can still fail if there are negative cycles. + It has running time O(n^3) with running space of O(n^2). + + See Also + -------- + floyd_warshall + floyd_warshall_numpy + all_pairs_shortest_path + all_pairs_shortest_path_length + """ + from collections import defaultdict + # dictionary-of-dictionaries representation for dist and pred + # use some defaultdict magick here + # for dist the default is the floating point inf value + dist = defaultdict(lambda : defaultdict(lambda: float('inf'))) + for u in G: + dist[u][u] = 0 + pred = defaultdict(dict) + # initialize path distance dictionary to be the adjacency matrix + # also set the distance to self to 0 (zero diagonal) + undirected = not G.is_directed() + for u,v,d in G.edges(data=True): + e_weight = d.get(weight, 1.0) + dist[u][v] = min(e_weight, dist[u][v]) + pred[u][v] = u + if undirected: + dist[v][u] = min(e_weight, dist[v][u]) + pred[v][u] = v + for w in G: + for u in G: + for v in G: + if dist[u][v] > dist[u][w] + dist[w][v]: + dist[u][v] = dist[u][w] + dist[w][v] + pred[u][v] = pred[w][v] + return dict(pred),dict(dist) + + +def floyd_warshall(G, weight='weight'): + """Find all-pairs shortest path lengths using Floyd's algorithm. + + Parameters + ---------- + G : NetworkX graph + + weight: string, optional (default= 'weight') + Edge data key corresponding to the edge weight. + + + Returns + ------- + distance : dict + A dictionary, keyed by source and target, of shortest paths distances + between nodes. + + Notes + ------ + Floyd's algorithm is appropriate for finding shortest paths + in dense graphs or graphs with negative weights when Dijkstra's algorithm + fails. This algorithm can still fail if there are negative cycles. + It has running time O(n^3) with running space of O(n^2). + + See Also + -------- + floyd_warshall_predecessor_and_distance + floyd_warshall_numpy + all_pairs_shortest_path + all_pairs_shortest_path_length + """ + # could make this its own function to reduce memory costs + return floyd_warshall_predecessor_and_distance(G, weight=weight)[1] + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/generic.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/generic.py new file mode 100644 index 0000000..a337cbb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/generic.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +""" +Compute the shortest paths and path lengths between nodes in the graph. + +These algorithms work with undirected and directed graphs. + +For directed graphs the paths can be computed in the reverse +order by first flipping the edge orientation using R=G.reverse(copy=False). + +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Aric Hagberg ', + 'Sérgio Nery Simões ']) +__all__ = ['shortest_path', 'all_shortest_paths', + 'shortest_path_length', 'average_shortest_path_length', + 'has_path'] + +def has_path(G, source, target): + """Return True if G has a path from source to target, False otherwise. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path + + target : node + Ending node for path + """ + try: + sp = nx.shortest_path(G,source, target) + except nx.NetworkXNoPath: + return False + return True + + +def shortest_path(G, source=None, target=None, weight=None): + """Compute shortest paths in the graph. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Starting node for path. + If not specified, compute shortest paths using all nodes as source nodes. + + target : node, optional + Ending node for path. + If not specified, compute shortest paths using all nodes as target nodes. + + weight : None or string, optional (default = None) + If None, every edge has weight/distance/cost 1. + If a string, use this edge attribute as the edge weight. + Any edge attribute not present defaults to 1. + + Returns + ------- + path: list or dictionary + All returned paths include both the source and target in the path. + + If the source and target are both specified, return a single list + of nodes in a shortest path from the source to the target. + + If only the source is specified, return a dictionary keyed by + targets with a list of nodes in a shortest path from the source + to one of the targets. + + If only the target is specified, return a dictionary keyed by + sources with a list of nodes in a shortest path from one of the + sources to the target. + + If neither the source nor target are specified return a dictionary + of dictionaries with path[source][target]=[list of nodes in path]. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> print(nx.shortest_path(G,source=0,target=4)) + [0, 1, 2, 3, 4] + >>> p=nx.shortest_path(G,source=0) # target not specified + >>> p[4] + [0, 1, 2, 3, 4] + >>> p=nx.shortest_path(G,target=4) # source not specified + >>> p[0] + [0, 1, 2, 3, 4] + >>> p=nx.shortest_path(G) # source,target not specified + >>> p[0][4] + [0, 1, 2, 3, 4] + + Notes + ----- + There may be more than one shortest path between a source and target. + This returns only one of them. + + For digraphs this returns a shortest directed path. To find paths in the + reverse direction first use G.reverse(copy=False) to flip the edge + orientation. + + See Also + -------- + all_pairs_shortest_path() + all_pairs_dijkstra_path() + single_source_shortest_path() + single_source_dijkstra_path() + """ + if source is None: + if target is None: + ## Find paths between all pairs. + if weight is None: + paths=nx.all_pairs_shortest_path(G) + else: + paths=nx.all_pairs_dijkstra_path(G,weight=weight) + else: + ## Find paths from all nodes co-accessible to the target. + directed = G.is_directed() + if directed: + G.reverse(copy=False) + + if weight is None: + paths=nx.single_source_shortest_path(G,target) + else: + paths=nx.single_source_dijkstra_path(G,target,weight=weight) + + # Now flip the paths so they go from a source to the target. + for target in paths: + paths[target] = list(reversed(paths[target])) + + if directed: + G.reverse(copy=False) + else: + if target is None: + ## Find paths to all nodes accessible from the source. + if weight is None: + paths=nx.single_source_shortest_path(G,source) + else: + paths=nx.single_source_dijkstra_path(G,source,weight=weight) + else: + ## Find shortest source-target path. + if weight is None: + paths=nx.bidirectional_shortest_path(G,source,target) + else: + paths=nx.dijkstra_path(G,source,target,weight) + + return paths + + +def shortest_path_length(G, source=None, target=None, weight=None): + """Compute shortest path lengths in the graph. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Starting node for path. + If not specified, compute shortest path lengths using all nodes as + source nodes. + + target : node, optional + Ending node for path. + If not specified, compute shortest path lengths using all nodes as + target nodes. + + weight : None or string, optional (default = None) + If None, every edge has weight/distance/cost 1. + If a string, use this edge attribute as the edge weight. + Any edge attribute not present defaults to 1. + + Returns + ------- + length: int or dictionary + If the source and target are both specified, return the length of + the shortest path from the source to the target. + + If only the source is specified, return a dictionary keyed by + targets whose values are the lengths of the shortest path from the + source to one of the targets. + + If only the target is specified, return a dictionary keyed by + sources whose values are the lengths of the shortest path from one + of the sources to the target. + + If neither the source nor target are specified return a dictionary + of dictionaries with path[source][target]=L, where L is the length + of the shortest path from source to target. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> print(nx.shortest_path_length(G,source=0,target=4)) + 4 + >>> p=nx.shortest_path_length(G,source=0) # target not specified + >>> p[4] + 4 + >>> p=nx.shortest_path_length(G,target=4) # source not specified + >>> p[0] + 4 + >>> p=nx.shortest_path_length(G) # source,target not specified + >>> p[0][4] + 4 + + Notes + ----- + The length of the path is always 1 less than the number of nodes involved + in the path since the length measures the number of edges followed. + + For digraphs this returns the shortest directed path length. To find path + lengths in the reverse direction use G.reverse(copy=False) first to flip + the edge orientation. + + See Also + -------- + all_pairs_shortest_path_length() + all_pairs_dijkstra_path_length() + single_source_shortest_path_length() + single_source_dijkstra_path_length() + + """ + if source is None: + if target is None: + ## Find paths between all pairs. + if weight is None: + paths=nx.all_pairs_shortest_path_length(G) + else: + paths=nx.all_pairs_dijkstra_path_length(G, weight=weight) + else: + ## Find paths from all nodes co-accessible to the target. + directed = G.is_directed() + if directed: + G.reverse(copy=False) + + if weight is None: + paths=nx.single_source_shortest_path_length(G,target) + else: + paths=nx.single_source_dijkstra_path_length(G,target, + weight=weight) + + if directed: + G.reverse(copy=False) + else: + if target is None: + ## Find paths to all nodes accessible from the source. + if weight is None: + paths=nx.single_source_shortest_path_length(G,source) + else: + paths=nx.single_source_dijkstra_path_length(G,source,weight=weight) + else: + ## Find shortest source-target path. + if weight is None: + p=nx.bidirectional_shortest_path(G,source,target) + paths=len(p)-1 + else: + paths=nx.dijkstra_path_length(G,source,target,weight) + return paths + + +def average_shortest_path_length(G, weight=None): + r"""Return the average shortest path length. + + The average shortest path length is + + .. math:: + + a =\sum_{s,t \in V} \frac{d(s, t)}{n(n-1)} + + where `V` is the set of nodes in `G`, + `d(s, t)` is the shortest path from `s` to `t`, + and `n` is the number of nodes in `G`. + + Parameters + ---------- + G : NetworkX graph + + weight : None or string, optional (default = None) + If None, every edge has weight/distance/cost 1. + If a string, use this edge attribute as the edge weight. + Any edge attribute not present defaults to 1. + + Raises + ------ + NetworkXError: + if the graph is not connected. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> print(nx.average_shortest_path_length(G)) + 2.0 + + For disconnected graphs you can compute the average shortest path + length for each component: + >>> G=nx.Graph([(1,2),(3,4)]) + >>> for g in nx.connected_component_subgraphs(G): + ... print(nx.average_shortest_path_length(g)) + 1.0 + 1.0 + + """ + if G.is_directed(): + if not nx.is_weakly_connected(G): + raise nx.NetworkXError("Graph is not connected.") + else: + if not nx.is_connected(G): + raise nx.NetworkXError("Graph is not connected.") + avg=0.0 + if weight is None: + for node in G: + path_length=nx.single_source_shortest_path_length(G, node) + avg += sum(path_length.values()) + else: + for node in G: + path_length=nx.single_source_dijkstra_path_length(G, node, weight=weight) + avg += sum(path_length.values()) + n=len(G) + return avg/(n*(n-1)) + + +def all_shortest_paths(G, source, target, weight=None): + """Compute all shortest paths in the graph. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path. + + target : node + Ending node for path. + + weight : None or string, optional (default = None) + If None, every edge has weight/distance/cost 1. + If a string, use this edge attribute as the edge weight. + Any edge attribute not present defaults to 1. + + Returns + ------- + paths: generator of lists + A generator of all paths between source and target. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_path([0,1,2]) + >>> G.add_path([0,10,2]) + >>> print([p for p in nx.all_shortest_paths(G,source=0,target=2)]) + [[0, 1, 2], [0, 10, 2]] + + Notes + ----- + There may be many shortest paths between the source and target. + + See Also + -------- + shortest_path() + single_source_shortest_path() + all_pairs_shortest_path() + """ + if weight is not None: + pred,dist = nx.dijkstra_predecessor_and_distance(G,source,weight=weight) + else: + pred = nx.predecessor(G,source) + if target not in pred: + raise nx.NetworkXNoPath() + stack = [[target,0]] + top = 0 + while top >= 0: + node,i = stack[top] + if node == source: + yield [p for p,n in reversed(stack[:top+1])] + if len(pred[node]) > i: + top += 1 + if top == len(stack): + stack.append([pred[node][i],0]) + else: + stack[top] = [pred[node][i],0] + else: + stack[top-1][1] += 1 + top -= 1 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_astar.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_astar.py new file mode 100644 index 0000000..81ba6ab --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_astar.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from random import random, choice + +class TestAStar: + + def setUp(self): + self.XG=nx.DiGraph() + self.XG.add_edges_from([('s','u',{'weight':10}), + ('s','x',{'weight':5}), + ('u','v',{'weight':1}), + ('u','x',{'weight':2}), + ('v','y',{'weight':1}), + ('x','u',{'weight':3}), + ('x','v',{'weight':5}), + ('x','y',{'weight':2}), + ('y','s',{'weight':7}), + ('y','v',{'weight':6})]) + + def test_random_graph(self): + + def dist(a, b): + (x1, y1) = a + (x2, y2) = b + return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5 + + G = nx.Graph() + + points = [(random(), random()) for _ in range(100)] + + # Build a path from points[0] to points[-1] to be sure it exists + for p1, p2 in zip(points[:-1], points[1:]): + G.add_edge(p1, p2, weight=dist(p1, p2)) + + # Add other random edges + for _ in range(100): + p1, p2 = choice(points), choice(points) + G.add_edge(p1, p2, weight=dist(p1, p2)) + + path = nx.astar_path(G, points[0], points[-1], dist) + assert path == nx.dijkstra_path(G, points[0], points[-1]) + + def test_astar_directed(self): + assert nx.astar_path(self.XG,'s','v')==['s', 'x', 'u', 'v'] + assert nx.astar_path_length(self.XG,'s','v')==9 + + def test_astar_multigraph(self): + G=nx.MultiDiGraph(self.XG) + assert_raises((TypeError,nx.NetworkXError), + nx.astar_path, [G,'s','v']) + assert_raises((TypeError,nx.NetworkXError), + nx.astar_path_length, [G,'s','v']) + + def test_astar_undirected(self): + GG=self.XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG['u']['x']['weight']=2 + GG['y']['v']['weight'] = 2 + assert_equal(nx.astar_path(GG,'s','v'),['s', 'x', 'u', 'v']) + assert_equal(nx.astar_path_length(GG,'s','v'),8) + + def test_astar_directed2(self): + XG2=nx.DiGraph() + XG2.add_edges_from([[1,4,{'weight':1}], + [4,5,{'weight':1}], + [5,6,{'weight':1}], + [6,3,{'weight':1}], + [1,3,{'weight':50}], + [1,2,{'weight':100}], + [2,3,{'weight':100}]]) + assert nx.astar_path(XG2,1,3)==[1, 4, 5, 6, 3] + + def test_astar_undirected2(self): + XG3=nx.Graph() + XG3.add_edges_from([ [0,1,{'weight':2}], + [1,2,{'weight':12}], + [2,3,{'weight':1}], + [3,4,{'weight':5}], + [4,5,{'weight':1}], + [5,0,{'weight':10}] ]) + assert nx.astar_path(XG3,0,3)==[0, 1, 2, 3] + assert nx.astar_path_length(XG3,0,3)==15 + + + def test_astar_undirected3(self): + XG4=nx.Graph() + XG4.add_edges_from([ [0,1,{'weight':2}], + [1,2,{'weight':2}], + [2,3,{'weight':1}], + [3,4,{'weight':1}], + [4,5,{'weight':1}], + [5,6,{'weight':1}], + [6,7,{'weight':1}], + [7,0,{'weight':1}] ]) + assert nx.astar_path(XG4,0,2)==[0, 1, 2] + assert nx.astar_path_length(XG4,0,2)==4 + + +# >>> MXG4=NX.MultiGraph(XG4) +# >>> MXG4.add_edge(0,1,3) +# >>> NX.dijkstra_path(MXG4,0,2) +# [0, 1, 2] + + def test_astar_w1(self): + G=nx.DiGraph() + G.add_edges_from([('s','u'), ('s','x'), ('u','v'), ('u','x'), + ('v','y'), ('x','u'), ('x','w'), ('w', 'v'), ('x','y'), + ('y','s'), ('y','v')]) + assert nx.astar_path(G,'s','v')==['s', 'u', 'v'] + assert nx.astar_path_length(G,'s','v')== 2 + + @raises(nx.NetworkXNoPath) + def test_astar_nopath(self): + p = nx.astar_path(self.XG,'s','moon') + + def test_cycle(self): + C=nx.cycle_graph(7) + assert nx.astar_path(C,0,3)==[0, 1, 2, 3] + assert nx.dijkstra_path(C,0,4)==[0, 6, 5, 4] + + + def test_orderable(self): + class UnorderableClass: pass + node_1 = UnorderableClass() + node_2 = UnorderableClass() + node_3 = UnorderableClass() + node_4 = UnorderableClass() + G = nx.Graph() + G.add_edge(node_1, node_2) + G.add_edge(node_1, node_3) + G.add_edge(node_2, node_4) + G.add_edge(node_3, node_4) + path=nx.algorithms.shortest_paths.astar.astar_path(G, node_1, node_4) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense.py new file mode 100644 index 0000000..6c170da --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx as nx + +class TestFloyd: + def setUp(self): + pass + + def test_floyd_warshall_predecessor_and_distance(self): + XG=nx.DiGraph() + XG.add_weighted_edges_from([('s','u',10) ,('s','x',5) , + ('u','v',1) ,('u','x',2) , + ('v','y',1) ,('x','u',3) , + ('x','v',5) ,('x','y',2) , + ('y','s',7) ,('y','v',6)]) + path, dist =nx.floyd_warshall_predecessor_and_distance(XG) + assert_equal(dist['s']['v'],9) + assert_equal(path['s']['v'],'u') + assert_equal(dist, + {'y': {'y': 0, 'x': 12, 's': 7, 'u': 15, 'v': 6}, + 'x': {'y': 2, 'x': 0, 's': 9, 'u': 3, 'v': 4}, + 's': {'y': 7, 'x': 5, 's': 0, 'u': 8, 'v': 9}, + 'u': {'y': 2, 'x': 2, 's': 9, 'u': 0, 'v': 1}, + 'v': {'y': 1, 'x': 13, 's': 8, 'u': 16, 'v': 0}}) + + + GG=XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG['u']['x']['weight']=2 + path, dist = nx.floyd_warshall_predecessor_and_distance(GG) + assert_equal(dist['s']['v'],8) + # skip this test, could be alternate path s-u-v +# assert_equal(path['s']['v'],'y') + + G=nx.DiGraph() # no weights + G.add_edges_from([('s','u'), ('s','x'), + ('u','v'), ('u','x'), + ('v','y'), ('x','u'), + ('x','v'), ('x','y'), + ('y','s'), ('y','v')]) + path, dist = nx.floyd_warshall_predecessor_and_distance(G) + assert_equal(dist['s']['v'],2) + # skip this test, could be alternate path s-u-v + # assert_equal(path['s']['v'],'x') + + # alternate interface + dist = nx.floyd_warshall(G) + assert_equal(dist['s']['v'],2) + + def test_cycle(self): + path, dist = nx.floyd_warshall_predecessor_and_distance(nx.cycle_graph(7)) + assert_equal(dist[0][3],3) + assert_equal(path[0][3],2) + assert_equal(dist[0][4],3) + + def test_weighted(self): + XG3=nx.Graph() + XG3.add_weighted_edges_from([ [0,1,2],[1,2,12],[2,3,1], + [3,4,5],[4,5,1],[5,0,10] ]) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG3) + assert_equal(dist[0][3],15) + assert_equal(path[0][3],2) + + def test_weighted2(self): + XG4=nx.Graph() + XG4.add_weighted_edges_from([ [0,1,2],[1,2,2],[2,3,1], + [3,4,1],[4,5,1],[5,6,1], + [6,7,1],[7,0,1] ]) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG4) + assert_equal(dist[0][2],4) + assert_equal(path[0][2],1) + + def test_weight_parameter(self): + XG4 = nx.Graph() + XG4.add_edges_from([ (0, 1, {'heavy': 2}), (1, 2, {'heavy': 2}), + (2, 3, {'heavy': 1}), (3, 4, {'heavy': 1}), + (4, 5, {'heavy': 1}), (5, 6, {'heavy': 1}), + (6, 7, {'heavy': 1}), (7, 0, {'heavy': 1}) ]) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG4, + weight='heavy') + assert_equal(dist[0][2], 4) + assert_equal(path[0][2], 1) + + def test_zero_distance(self): + XG=nx.DiGraph() + XG.add_weighted_edges_from([('s','u',10) ,('s','x',5) , + ('u','v',1) ,('u','x',2) , + ('v','y',1) ,('x','u',3) , + ('x','v',5) ,('x','y',2) , + ('y','s',7) ,('y','v',6)]) + path, dist =nx.floyd_warshall_predecessor_and_distance(XG) + + for u in XG: + assert_equal(dist[u][u], 0) + + GG=XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG['u']['x']['weight']=2 + path, dist = nx.floyd_warshall_predecessor_and_distance(GG) + + for u in GG: + dist[u][u] = 0 + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py new file mode 100644 index 0000000..2fa0b67 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx as nx + +class TestFloydNumpy(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global numpy + global assert_equal + global assert_almost_equal + try: + import numpy + from numpy.testing import assert_equal,assert_almost_equal + except ImportError: + raise SkipTest('NumPy not available.') + + def test_cycle_numpy(self): + dist = nx.floyd_warshall_numpy(nx.cycle_graph(7)) + assert_equal(dist[0,3],3) + assert_equal(dist[0,4],3) + + def test_weighted_numpy(self): + XG3=nx.Graph() + XG3.add_weighted_edges_from([ [0,1,2],[1,2,12],[2,3,1], + [3,4,5],[4,5,1],[5,0,10] ]) + dist = nx.floyd_warshall_numpy(XG3) + assert_equal(dist[0,3],15) + + def test_weighted_numpy(self): + XG4=nx.Graph() + XG4.add_weighted_edges_from([ [0,1,2],[1,2,2],[2,3,1], + [3,4,1],[4,5,1],[5,6,1], + [6,7,1],[7,0,1] ]) + dist = nx.floyd_warshall_numpy(XG4) + assert_equal(dist[0,2],4) + + def test_weight_parameter_numpy(self): + XG4 = nx.Graph() + XG4.add_edges_from([ (0, 1, {'heavy': 2}), (1, 2, {'heavy': 2}), + (2, 3, {'heavy': 1}), (3, 4, {'heavy': 1}), + (4, 5, {'heavy': 1}), (5, 6, {'heavy': 1}), + (6, 7, {'heavy': 1}), (7, 0, {'heavy': 1}) ]) + dist = nx.floyd_warshall_numpy(XG4, weight='heavy') + assert_equal(dist[0, 2], 4) + + def test_directed_cycle_numpy(self): + G = nx.DiGraph() + G.add_cycle([0,1,2,3]) + pred,dist = nx.floyd_warshall_predecessor_and_distance(G) + D = nx.utils.dict_to_numpy_array(dist) + assert_equal(nx.floyd_warshall_numpy(G),D) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_generic.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_generic.py new file mode 100644 index 0000000..edaa9f9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_generic.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestGenericPath: + + def setUp(self): + from networkx import convert_node_labels_to_integers as cnlti + self.grid=cnlti(nx.grid_2d_graph(4,4),first_label=1,ordering="sorted") + self.cycle=nx.cycle_graph(7) + self.directed_cycle=nx.cycle_graph(7,create_using=nx.DiGraph()) + + + def test_shortest_path(self): + assert_equal(nx.shortest_path(self.cycle,0,3),[0, 1, 2, 3]) + assert_equal(nx.shortest_path(self.cycle,0,4),[0, 6, 5, 4]) + assert_equal(nx.shortest_path(self.grid,1,12),[1, 2, 3, 4, 8, 12]) + assert_equal(nx.shortest_path(self.directed_cycle,0,3),[0, 1, 2, 3]) + # now with weights + assert_equal(nx.shortest_path(self.cycle,0,3,weight='weight'),[0, 1, 2, 3]) + assert_equal(nx.shortest_path(self.cycle,0,4,weight='weight'),[0, 6, 5, 4]) + assert_equal(nx.shortest_path(self.grid,1,12,weight='weight'),[1, 2, 3, 4, 8, 12]) + assert_equal(nx.shortest_path(self.directed_cycle,0,3,weight='weight'), + [0, 1, 2, 3]) + + def test_shortest_path_target(self): + sp = nx.shortest_path(nx.path_graph(3), target=1) + assert_equal(sp, {0: [0, 1], 1: [1], 2: [2, 1]}) + + def test_shortest_path_length(self): + assert_equal(nx.shortest_path_length(self.cycle,0,3),3) + assert_equal(nx.shortest_path_length(self.grid,1,12),5) + assert_equal(nx.shortest_path_length(self.directed_cycle,0,4),4) + # now with weights + assert_equal(nx.shortest_path_length(self.cycle,0,3,weight='weight'),3) + assert_equal(nx.shortest_path_length(self.grid,1,12,weight='weight'),5) + assert_equal(nx.shortest_path_length(self.directed_cycle,0,4,weight='weight'),4) + + def test_shortest_path_length_target(self): + sp = nx.shortest_path_length(nx.path_graph(3), target=1) + assert_equal(sp[0], 1) + assert_equal(sp[1], 0) + assert_equal(sp[2], 1) + + def test_single_source_shortest_path(self): + p=nx.shortest_path(self.cycle,0) + assert_equal(p[3],[0,1,2,3]) + assert_equal(p,nx.single_source_shortest_path(self.cycle,0)) + p=nx.shortest_path(self.grid,1) + assert_equal(p[12],[1, 2, 3, 4, 8, 12]) + # now with weights + p=nx.shortest_path(self.cycle,0,weight='weight') + assert_equal(p[3],[0,1,2,3]) + assert_equal(p,nx.single_source_dijkstra_path(self.cycle,0)) + p=nx.shortest_path(self.grid,1,weight='weight') + assert_equal(p[12],[1, 2, 3, 4, 8, 12]) + + + def test_single_source_shortest_path_length(self): + l=nx.shortest_path_length(self.cycle,0) + assert_equal(l,{0:0,1:1,2:2,3:3,4:3,5:2,6:1}) + assert_equal(l,nx.single_source_shortest_path_length(self.cycle,0)) + l=nx.shortest_path_length(self.grid,1) + assert_equal(l[16],6) + # now with weights + l=nx.shortest_path_length(self.cycle,0,weight='weight') + assert_equal(l,{0:0,1:1,2:2,3:3,4:3,5:2,6:1}) + assert_equal(l,nx.single_source_dijkstra_path_length(self.cycle,0)) + l=nx.shortest_path_length(self.grid,1,weight='weight') + assert_equal(l[16],6) + + + def test_all_pairs_shortest_path(self): + p=nx.shortest_path(self.cycle) + assert_equal(p[0][3],[0,1,2,3]) + assert_equal(p,nx.all_pairs_shortest_path(self.cycle)) + p=nx.shortest_path(self.grid) + assert_equal(p[1][12],[1, 2, 3, 4, 8, 12]) + # now with weights + p=nx.shortest_path(self.cycle,weight='weight') + assert_equal(p[0][3],[0,1,2,3]) + assert_equal(p,nx.all_pairs_dijkstra_path(self.cycle)) + p=nx.shortest_path(self.grid,weight='weight') + assert_equal(p[1][12],[1, 2, 3, 4, 8, 12]) + + + def test_all_pairs_shortest_path_length(self): + l=nx.shortest_path_length(self.cycle) + assert_equal(l[0],{0:0,1:1,2:2,3:3,4:3,5:2,6:1}) + assert_equal(l,nx.all_pairs_shortest_path_length(self.cycle)) + l=nx.shortest_path_length(self.grid) + assert_equal(l[1][16],6) + # now with weights + l=nx.shortest_path_length(self.cycle,weight='weight') + assert_equal(l[0],{0:0,1:1,2:2,3:3,4:3,5:2,6:1}) + assert_equal(l,nx.all_pairs_dijkstra_path_length(self.cycle)) + l=nx.shortest_path_length(self.grid,weight='weight') + assert_equal(l[1][16],6) + + def test_average_shortest_path(self): + l=nx.average_shortest_path_length(self.cycle) + assert_almost_equal(l,2) + l=nx.average_shortest_path_length(nx.path_graph(5)) + assert_almost_equal(l,2) + + + def test_weighted_average_shortest_path(self): + G=nx.Graph() + G.add_cycle(range(7),weight=2) + l=nx.average_shortest_path_length(G,weight='weight') + assert_almost_equal(l,4) + G=nx.Graph() + G.add_path(range(5),weight=2) + l=nx.average_shortest_path_length(G,weight='weight') + assert_almost_equal(l,4) + + + def test_average_shortest_disconnected(self): + g = nx.Graph() + g.add_nodes_from(range(3)) + g.add_edge(0, 1) + assert_raises(nx.NetworkXError,nx.average_shortest_path_length,g) + g = g.to_directed() + assert_raises(nx.NetworkXError,nx.average_shortest_path_length,g) + + def test_has_path(self): + G = nx.Graph() + G.add_path(range(3)) + G.add_path(range(3,5)) + assert_true(nx.has_path(G,0,2)) + assert_false(nx.has_path(G,0,4)) + + def test_all_shortest_paths(self): + G = nx.Graph() + G.add_path([0,1,2,3]) + G.add_path([0,10,20,3]) + assert_equal([[0,1,2,3],[0,10,20,3]], + sorted(nx.all_shortest_paths(G,0,3))) + + @raises(nx.NetworkXNoPath) + def test_all_shortest_paths_raise(self): + G = nx.Graph() + G.add_path([0,1,2,3]) + G.add_node(4) + paths = list(nx.all_shortest_paths(G,0,4)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_unweighted.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_unweighted.py new file mode 100644 index 0000000..fc2abfb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_unweighted.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestUnweightedPath: + + def setUp(self): + from networkx import convert_node_labels_to_integers as cnlti + self.grid=cnlti(nx.grid_2d_graph(4,4),first_label=1,ordering="sorted") + self.cycle=nx.cycle_graph(7) + self.directed_cycle=nx.cycle_graph(7,create_using=nx.DiGraph()) + + + def test_bidirectional_shortest_path(self): + assert_equal(nx.bidirectional_shortest_path(self.cycle,0,3), + [0, 1, 2, 3]) + assert_equal(nx.bidirectional_shortest_path(self.cycle,0,4), + [0, 6, 5, 4]) + assert_equal(nx.bidirectional_shortest_path(self.grid,1,12), + [1, 2, 3, 4, 8, 12]) + assert_equal(nx.bidirectional_shortest_path(self.directed_cycle,0,3), + [0, 1, 2, 3]) + + def test_shortest_path_length(self): + assert_equal(nx.shortest_path_length(self.cycle,0,3),3) + assert_equal(nx.shortest_path_length(self.grid,1,12),5) + assert_equal(nx.shortest_path_length(self.directed_cycle,0,4),4) + # now with weights + assert_equal(nx.shortest_path_length(self.cycle,0,3,weight=True),3) + assert_equal(nx.shortest_path_length(self.grid,1,12,weight=True),5) + assert_equal(nx.shortest_path_length(self.directed_cycle,0,4,weight=True),4) + + + def test_single_source_shortest_path(self): + p=nx.single_source_shortest_path(self.cycle,0) + assert_equal(p[3],[0,1,2,3]) + p=nx.single_source_shortest_path(self.cycle,0, cutoff=0) + assert_equal(p,{0 : [0]}) + + def test_single_source_shortest_path_length(self): + assert_equal(nx.single_source_shortest_path_length(self.cycle,0), + {0:0,1:1,2:2,3:3,4:3,5:2,6:1}) + + def test_all_pairs_shortest_path(self): + p=nx.all_pairs_shortest_path(self.cycle) + assert_equal(p[0][3],[0,1,2,3]) + p=nx.all_pairs_shortest_path(self.grid) + assert_equal(p[1][12],[1, 2, 3, 4, 8, 12]) + + def test_all_pairs_shortest_path_length(self): + l=nx.all_pairs_shortest_path_length(self.cycle) + assert_equal(l[0],{0:0,1:1,2:2,3:3,4:3,5:2,6:1}) + l=nx.all_pairs_shortest_path_length(self.grid) + assert_equal(l[1][16],6) + + def test_predecessor(self): + G=nx.path_graph(4) + assert_equal(nx.predecessor(G,0),{0: [], 1: [0], 2: [1], 3: [2]}) + assert_equal(nx.predecessor(G,0,3),[2]) + G=nx.grid_2d_graph(2,2) + assert_equal(sorted(nx.predecessor(G,(0,0)).items()), + [((0, 0), []), ((0, 1), [(0, 0)]), + ((1, 0), [(0, 0)]), ((1, 1), [(0, 1), (1, 0)])]) + + def test_predecessor_cutoff(self): + G=nx.path_graph(4) + p = nx.predecessor(G,0,3) + assert_false(4 in p) + + def test_predecessor_target(self): + G=nx.path_graph(4) + p = nx.predecessor(G,0,3) + assert_equal(p,[2]) + p = nx.predecessor(G,0,3,cutoff=2) + assert_equal(p,[]) + p,s = nx.predecessor(G,0,3,return_seen=True) + assert_equal(p,[2]) + assert_equal(s,3) + p,s = nx.predecessor(G,0,3,cutoff=2,return_seen=True) + assert_equal(p,[]) + assert_equal(s,-1) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_weighted.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_weighted.py new file mode 100644 index 0000000..c3998d4 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/tests/test_weighted.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestWeightedPath: + + def setUp(self): + from networkx import convert_node_labels_to_integers as cnlti + self.grid=cnlti(nx.grid_2d_graph(4,4),first_label=1,ordering="sorted") + self.cycle=nx.cycle_graph(7) + self.directed_cycle=nx.cycle_graph(7,create_using=nx.DiGraph()) + self.XG=nx.DiGraph() + self.XG.add_weighted_edges_from([('s','u',10) ,('s','x',5) , + ('u','v',1) ,('u','x',2) , + ('v','y',1) ,('x','u',3) , + ('x','v',5) ,('x','y',2) , + ('y','s',7) ,('y','v',6)]) + self.MXG=nx.MultiDiGraph(self.XG) + self.MXG.add_edge('s','u',weight=15) + self.XG2=nx.DiGraph() + self.XG2.add_weighted_edges_from([[1,4,1],[4,5,1], + [5,6,1],[6,3,1], + [1,3,50],[1,2,100],[2,3,100]]) + + self.XG3=nx.Graph() + self.XG3.add_weighted_edges_from([ [0,1,2],[1,2,12], + [2,3,1],[3,4,5], + [4,5,1],[5,0,10] ]) + + self.XG4=nx.Graph() + self.XG4.add_weighted_edges_from([ [0,1,2],[1,2,2], + [2,3,1],[3,4,1], + [4,5,1],[5,6,1], + [6,7,1],[7,0,1] ]) + self.MXG4=nx.MultiGraph(self.XG4) + self.MXG4.add_edge(0,1,weight=3) + self.G=nx.DiGraph() # no weights + self.G.add_edges_from([('s','u'), ('s','x'), + ('u','v'), ('u','x'), + ('v','y'), ('x','u'), + ('x','v'), ('x','y'), + ('y','s'), ('y','v')]) + + def test_dijkstra(self): + (D,P)= nx.single_source_dijkstra(self.XG,'s') + assert_equal(P['v'], ['s', 'x', 'u', 'v']) + assert_equal(D['v'],9) + + assert_equal(nx.single_source_dijkstra_path(self.XG,'s')['v'], + ['s', 'x', 'u', 'v']) + assert_equal(nx.single_source_dijkstra_path_length(self.XG,'s')['v'],9) + + assert_equal(nx.single_source_dijkstra(self.XG,'s')[1]['v'], + ['s', 'x', 'u', 'v']) + + assert_equal(nx.single_source_dijkstra_path(self.MXG,'s')['v'], + ['s', 'x', 'u', 'v']) + + GG=self.XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG['u']['x']['weight']=2 + (D,P)= nx.single_source_dijkstra(GG,'s') + assert_equal(P['v'] , ['s', 'x', 'u', 'v']) + assert_equal(D['v'],8) # uses lower weight of 2 on u<->x edge + assert_equal(nx.dijkstra_path(GG,'s','v'), ['s', 'x', 'u', 'v']) + assert_equal(nx.dijkstra_path_length(GG,'s','v'),8) + + assert_equal(nx.dijkstra_path(self.XG2,1,3), [1, 4, 5, 6, 3]) + assert_equal(nx.dijkstra_path(self.XG3,0,3), [0, 1, 2, 3]) + assert_equal(nx.dijkstra_path_length(self.XG3,0,3),15) + assert_equal(nx.dijkstra_path(self.XG4,0,2), [0, 1, 2]) + assert_equal(nx.dijkstra_path_length(self.XG4,0,2), 4) + assert_equal(nx.dijkstra_path(self.MXG4,0,2), [0, 1, 2]) + assert_equal(nx.single_source_dijkstra(self.G,'s','v')[1]['v'], + ['s', 'u', 'v']) + assert_equal(nx.single_source_dijkstra(self.G,'s')[1]['v'], + ['s', 'u', 'v']) + + assert_equal(nx.dijkstra_path(self.G,'s','v'), ['s', 'u', 'v']) + assert_equal(nx.dijkstra_path_length(self.G,'s','v'), 2) + + # NetworkXError: node s not reachable from moon + assert_raises(nx.NetworkXNoPath,nx.dijkstra_path,self.G,'s','moon') + assert_raises(nx.NetworkXNoPath,nx.dijkstra_path_length,self.G,'s','moon') + + assert_equal(nx.dijkstra_path(self.cycle,0,3),[0, 1, 2, 3]) + assert_equal(nx.dijkstra_path(self.cycle,0,4), [0, 6, 5, 4]) + + assert_equal(nx.single_source_dijkstra(self.cycle,0,0),({0:0}, {0:[0]}) ) + + def test_bidirectional_dijkstra(self): + assert_equal(nx.bidirectional_dijkstra(self.XG, 's', 'v'), + (9, ['s', 'x', 'u', 'v'])) + (dist,path) = nx.bidirectional_dijkstra(self.G,'s','v') + assert_equal(dist,2) + # skip this test, correct path could also be ['s','u','v'] +# assert_equal(nx.bidirectional_dijkstra(self.G,'s','v'), +# (2, ['s', 'x', 'v'])) + assert_equal(nx.bidirectional_dijkstra(self.cycle,0,3), + (3, [0, 1, 2, 3])) + assert_equal(nx.bidirectional_dijkstra(self.cycle,0,4), + (3, [0, 6, 5, 4])) + assert_equal(nx.bidirectional_dijkstra(self.XG3,0,3), + (15, [0, 1, 2, 3])) + assert_equal(nx.bidirectional_dijkstra(self.XG4,0,2), + (4, [0, 1, 2])) + + # need more tests here + assert_equal(nx.dijkstra_path(self.XG,'s','v'), + nx.single_source_dijkstra_path(self.XG,'s')['v']) + + + @raises(nx.NetworkXNoPath) + def test_bidirectional_dijkstra_no_path(self): + G = nx.Graph() + G.add_path([1,2,3]) + G.add_path([4,5,6]) + path = nx.bidirectional_dijkstra(G,1,6) + + def test_dijkstra_predecessor(self): + G=nx.path_graph(4) + assert_equal(nx.dijkstra_predecessor_and_distance(G,0), + ({0: [], 1: [0], 2: [1], 3: [2]}, {0: 0, 1: 1, 2: 2, 3: 3})) + G=nx.grid_2d_graph(2,2) + pred,dist=nx.dijkstra_predecessor_and_distance(G,(0,0)) + assert_equal(sorted(pred.items()), + [((0, 0), []), ((0, 1), [(0, 0)]), + ((1, 0), [(0, 0)]), ((1, 1), [(0, 1), (1, 0)])]) + assert_equal(sorted(dist.items()), + [((0, 0), 0), ((0, 1), 1), ((1, 0), 1), ((1, 1), 2)]) + + XG=nx.DiGraph() + XG.add_weighted_edges_from([('s','u',10) ,('s','x',5) , + ('u','v',1) ,('u','x',2) , + ('v','y',1) ,('x','u',3) , + ('x','v',5) ,('x','y',2) , + ('y','s',7) ,('y','v',6)]) + (P,D)= nx.dijkstra_predecessor_and_distance(XG,'s') + assert_equal(P['v'],['u']) + assert_equal(D['v'],9) + (P,D)= nx.dijkstra_predecessor_and_distance(XG,'s',cutoff=8) + assert_false('v' in D) + + def test_single_source_dijkstra_path_length(self): + pl = nx.single_source_dijkstra_path_length + assert_equal(pl(self.MXG4,0)[2], 4) + spl = pl(self.MXG4,0,cutoff=2) + assert_false(2 in spl) + + def test_bidirectional_dijkstra_multigraph(self): + G = nx.MultiGraph() + G.add_edge('a', 'b', weight=10) + G.add_edge('a', 'b', weight=100) + dp= nx.bidirectional_dijkstra(G, 'a', 'b') + assert_equal(dp,(10, ['a', 'b'])) + + + def test_dijkstra_pred_distance_multigraph(self): + G = nx.MultiGraph() + G.add_edge('a', 'b', key='short',foo=5, weight=100) + G.add_edge('a', 'b', key='long',bar=1, weight=110) + p,d= nx.dijkstra_predecessor_and_distance(G, 'a') + assert_equal(p,{'a': [], 'b': ['a']}) + assert_equal(d,{'a': 0, 'b': 100}) + + def test_negative_edge_cycle(self): + G = nx.cycle_graph(5, create_using = nx.DiGraph()) + assert_equal(nx.negative_edge_cycle(G), False) + G.add_edge(8, 9, weight = -7) + G.add_edge(9, 8, weight = 3) + assert_equal(nx.negative_edge_cycle(G), True) + assert_raises(ValueError,nx.single_source_dijkstra_path_length,G,8) + assert_raises(ValueError,nx.single_source_dijkstra,G,8) + assert_raises(ValueError,nx.dijkstra_predecessor_and_distance,G,8) + G.add_edge(9,10) + assert_raises(ValueError,nx.bidirectional_dijkstra,G,8,10) + + def test_bellman_ford(self): + # single node graph + G = nx.DiGraph() + G.add_node(0) + assert_equal(nx.bellman_ford(G, 0), ({0: None}, {0: 0})) + assert_raises(KeyError, nx.bellman_ford, G, 1) + + # negative weight cycle + G = nx.cycle_graph(5, create_using = nx.DiGraph()) + G.add_edge(1, 2, weight = -7) + for i in range(5): + assert_raises(nx.NetworkXUnbounded, nx.bellman_ford, G, i) + G = nx.cycle_graph(5) # undirected Graph + G.add_edge(1, 2, weight = -3) + for i in range(5): + assert_raises(nx.NetworkXUnbounded, nx.bellman_ford, G, i) + # no negative cycle but negative weight + G = nx.cycle_graph(5, create_using = nx.DiGraph()) + G.add_edge(1, 2, weight = -3) + assert_equal(nx.bellman_ford(G, 0), + ({0: None, 1: 0, 2: 1, 3: 2, 4: 3}, + {0: 0, 1: 1, 2: -2, 3: -1, 4: 0})) + + # not connected + G = nx.complete_graph(6) + G.add_edge(10, 11) + G.add_edge(10, 12) + assert_equal(nx.bellman_ford(G, 0), + ({0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1})) + + # not connected, with a component not containing the source that + # contains a negative cost cycle. + G = nx.complete_graph(6) + G.add_edges_from([('A', 'B', {'load': 3}), + ('B', 'C', {'load': -10}), + ('C', 'A', {'load': 2})]) + assert_equal(nx.bellman_ford(G, 0, weight = 'load'), + ({0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1})) + + # multigraph + P, D = nx.bellman_ford(self.MXG,'s') + assert_equal(P['v'], 'u') + assert_equal(D['v'], 9) + P, D = nx.bellman_ford(self.MXG4, 0) + assert_equal(P[2], 1) + assert_equal(D[2], 4) + + # other tests + (P,D)= nx.bellman_ford(self.XG,'s') + assert_equal(P['v'], 'u') + assert_equal(D['v'], 9) + + G=nx.path_graph(4) + assert_equal(nx.bellman_ford(G,0), + ({0: None, 1: 0, 2: 1, 3: 2}, {0: 0, 1: 1, 2: 2, 3: 3})) + assert_equal(nx.bellman_ford(G, 3), + ({0: 1, 1: 2, 2: 3, 3: None}, {0: 3, 1: 2, 2: 1, 3: 0})) + + G=nx.grid_2d_graph(2,2) + pred,dist=nx.bellman_ford(G,(0,0)) + assert_equal(sorted(pred.items()), + [((0, 0), None), ((0, 1), (0, 0)), + ((1, 0), (0, 0)), ((1, 1), (0, 1))]) + assert_equal(sorted(dist.items()), + [((0, 0), 0), ((0, 1), 1), ((1, 0), 1), ((1, 1), 2)]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/unweighted.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/unweighted.py new file mode 100644 index 0000000..b7ce18a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/unweighted.py @@ -0,0 +1,359 @@ +# -*- coding: utf-8 -*- +""" +Shortest path algorithms for unweighted graphs. +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['bidirectional_shortest_path', + 'single_source_shortest_path', + 'single_source_shortest_path_length', + 'all_pairs_shortest_path', + 'all_pairs_shortest_path_length', + 'predecessor'] + + +import networkx as nx + +def single_source_shortest_path_length(G,source,cutoff=None): + """Compute the shortest path lengths from source to all reachable nodes. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path + + cutoff : integer, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + lengths : dictionary + Dictionary of shortest path lengths keyed by target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> length=nx.single_source_shortest_path_length(G,0) + >>> length[4] + 4 + >>> print(length) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} + + See Also + -------- + shortest_path_length + """ + seen={} # level (number of hops) when seen in BFS + level=0 # the current level + nextlevel={source:1} # dict of nodes to check at next level + while nextlevel: + thislevel=nextlevel # advance to next level + nextlevel={} # and start a new list (fringe) + for v in thislevel: + if v not in seen: + seen[v]=level # set the level of vertex v + nextlevel.update(G[v]) # add neighbors of v + if (cutoff is not None and cutoff <= level): break + level=level+1 + return seen # return all path lengths as dictionary + + +def all_pairs_shortest_path_length(G,cutoff=None): + """ Compute the shortest path lengths between all nodes in G. + + Parameters + ---------- + G : NetworkX graph + + cutoff : integer, optional + depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + lengths : dictionary + Dictionary of shortest path lengths keyed by source and target. + + Notes + ----- + The dictionary returned only has keys for reachable node pairs. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> length=nx.all_pairs_shortest_path_length(G) + >>> print(length[1][4]) + 3 + >>> length[1] + {0: 1, 1: 0, 2: 1, 3: 2, 4: 3} + + """ + paths={} + for n in G: + paths[n]=single_source_shortest_path_length(G,n,cutoff=cutoff) + return paths + + + + +def bidirectional_shortest_path(G,source,target): + """Return a list of nodes in a shortest path between source and target. + + Parameters + ---------- + G : NetworkX graph + + source : node label + starting node for path + + target : node label + ending node for path + + Returns + ------- + path: list + List of nodes in a path from source to target. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + See Also + -------- + shortest_path + + Notes + ----- + This algorithm is used by shortest_path(G,source,target). + """ + # call helper to do the real work + results=_bidirectional_pred_succ(G,source,target) + pred,succ,w=results + + # build path from pred+w+succ + path=[] + # from w to target + while w is not None: + path.append(w) + w=succ[w] + # from source to w + w=pred[path[0]] + while w is not None: + path.insert(0,w) + w=pred[w] + + return path + +def _bidirectional_pred_succ(G, source, target): + """Bidirectional shortest path helper. + + Returns (pred,succ,w) where + pred is a dictionary of predecessors from w to the source, and + succ is a dictionary of successors from w to the target. + """ + # does BFS from both source and target and meets in the middle + if target == source: + return ({target:None},{source:None},source) + + # handle either directed or undirected + if G.is_directed(): + Gpred=G.predecessors_iter + Gsucc=G.successors_iter + else: + Gpred=G.neighbors_iter + Gsucc=G.neighbors_iter + + # predecesssor and successors in search + pred={source:None} + succ={target:None} + + # initialize fringes, start with forward + forward_fringe=[source] + reverse_fringe=[target] + + while forward_fringe and reverse_fringe: + if len(forward_fringe) <= len(reverse_fringe): + this_level=forward_fringe + forward_fringe=[] + for v in this_level: + for w in Gsucc(v): + if w not in pred: + forward_fringe.append(w) + pred[w]=v + if w in succ: return pred,succ,w # found path + else: + this_level=reverse_fringe + reverse_fringe=[] + for v in this_level: + for w in Gpred(v): + if w not in succ: + succ[w]=v + reverse_fringe.append(w) + if w in pred: return pred,succ,w # found path + + raise nx.NetworkXNoPath("No path between %s and %s." % (source, target)) + + +def single_source_shortest_path(G,source,cutoff=None): + """Compute shortest path between source + and all other nodes reachable from source. + + Parameters + ---------- + G : NetworkX graph + + source : node label + Starting node for path + + cutoff : integer, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + lengths : dictionary + Dictionary, keyed by target, of shortest paths. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> path=nx.single_source_shortest_path(G,0) + >>> path[4] + [0, 1, 2, 3, 4] + + Notes + ----- + The shortest path is not necessarily unique. So there can be multiple + paths between the source and each target node, all of which have the + same 'shortest' length. For each target node, this function returns + only one of those paths. + + See Also + -------- + shortest_path + """ + level=0 # the current level + nextlevel={source:1} # list of nodes to check at next level + paths={source:[source]} # paths dictionary (paths to key from source) + if cutoff==0: + return paths + while nextlevel: + thislevel=nextlevel + nextlevel={} + for v in thislevel: + for w in G[v]: + if w not in paths: + paths[w]=paths[v]+[w] + nextlevel[w]=1 + level=level+1 + if (cutoff is not None and cutoff <= level): break + return paths + + +def all_pairs_shortest_path(G,cutoff=None): + """ Compute shortest paths between all nodes. + + Parameters + ---------- + G : NetworkX graph + + cutoff : integer, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + lengths : dictionary + Dictionary, keyed by source and target, of shortest paths. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> path=nx.all_pairs_shortest_path(G) + >>> print(path[0][4]) + [0, 1, 2, 3, 4] + + See Also + -------- + floyd_warshall() + + """ + paths={} + for n in G: + paths[n]=single_source_shortest_path(G,n,cutoff=cutoff) + return paths + + + + +def predecessor(G,source,target=None,cutoff=None,return_seen=None): + """ Returns dictionary of predecessors for the path from source to all nodes in G. + + + Parameters + ---------- + G : NetworkX graph + + source : node label + Starting node for path + + target : node label, optional + Ending node for path. If provided only predecessors between + source and target are returned + + cutoff : integer, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + + Returns + ------- + pred : dictionary + Dictionary, keyed by node, of predecessors in the shortest path. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> print(G.nodes()) + [0, 1, 2, 3] + >>> nx.predecessor(G,0) + {0: [], 1: [0], 2: [1], 3: [2]} + + """ + level=0 # the current level + nextlevel=[source] # list of nodes to check at next level + seen={source:level} # level (number of hops) when seen in BFS + pred={source:[]} # predecessor dictionary + while nextlevel: + level=level+1 + thislevel=nextlevel + nextlevel=[] + for v in thislevel: + for w in G[v]: + if w not in seen: + pred[w]=[v] + seen[w]=level + nextlevel.append(w) + elif (seen[w]==level):# add v to predecessor list if it + pred[w].append(v) # is at the correct level + if (cutoff and cutoff <= level): + break + + if target is not None: + if return_seen: + if not target in pred: return ([],-1) # No predecessor + return (pred[target],seen[target]) + else: + if not target in pred: return [] # No predecessor + return pred[target] + else: + if return_seen: + return (pred,seen) + else: + return pred + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/weighted.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/weighted.py new file mode 100644 index 0000000..41757f9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/shortest_paths/weighted.py @@ -0,0 +1,765 @@ +# -*- coding: utf-8 -*- +""" +Shortest path algorithms for weighed graphs. +""" +__author__ = """\n""".join(['Aric Hagberg ', + 'Loïc Séguin-C. ', + 'Dan Schult ']) +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['dijkstra_path', + 'dijkstra_path_length', + 'bidirectional_dijkstra', + 'single_source_dijkstra', + 'single_source_dijkstra_path', + 'single_source_dijkstra_path_length', + 'all_pairs_dijkstra_path', + 'all_pairs_dijkstra_path_length', + 'dijkstra_predecessor_and_distance', + 'bellman_ford','negative_edge_cycle'] + +import heapq +import networkx as nx +from networkx.utils import generate_unique_node + +def dijkstra_path(G, source, target, weight='weight'): + """Returns the shortest path from source to target in a weighted graph G. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node + + target : node + Ending node + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + Returns + ------- + path : list + List of nodes in a shortest path. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> print(nx.dijkstra_path(G,0,4)) + [0, 1, 2, 3, 4] + + Notes + ------ + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + See Also + -------- + bidirectional_dijkstra() + """ + (length,path)=single_source_dijkstra(G, source, target=target, + weight=weight) + try: + return path[target] + except KeyError: + raise nx.NetworkXNoPath("node %s not reachable from %s"%(source,target)) + + +def dijkstra_path_length(G, source, target, weight='weight'): + """Returns the shortest path length from source to target + in a weighted graph. + + Parameters + ---------- + G : NetworkX graph + + source : node label + starting node for path + + target : node label + ending node for path + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + Returns + ------- + length : number + Shortest path length. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> print(nx.dijkstra_path_length(G,0,4)) + 4 + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + See Also + -------- + bidirectional_dijkstra() + """ + length=single_source_dijkstra_path_length(G, source, weight=weight) + try: + return length[target] + except KeyError: + raise nx.NetworkXNoPath("node %s not reachable from %s"%(source,target)) + + +def single_source_dijkstra_path(G,source, cutoff=None, weight='weight'): + """Compute shortest path between source and all other reachable + nodes for a weighted graph. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path. + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + cutoff : integer or float, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + paths : dictionary + Dictionary of shortest path lengths keyed by target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> path=nx.single_source_dijkstra_path(G,0) + >>> path[4] + [0, 1, 2, 3, 4] + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + See Also + -------- + single_source_dijkstra() + + """ + (length,path)=single_source_dijkstra(G,source, cutoff = cutoff, weight = weight) + return path + + +def single_source_dijkstra_path_length(G, source, cutoff= None, + weight= 'weight'): + """Compute the shortest path length between source and all other + reachable nodes for a weighted graph. + + Parameters + ---------- + G : NetworkX graph + + source : node label + Starting node for path + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight. + + cutoff : integer or float, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + length : dictionary + Dictionary of shortest lengths keyed by target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> length=nx.single_source_dijkstra_path_length(G,0) + >>> length[4] + 4 + >>> print(length) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + See Also + -------- + single_source_dijkstra() + + """ + dist = {} # dictionary of final distances + seen = {source:0} + fringe=[] # use heapq with (distance,label) tuples + heapq.heappush(fringe,(0,source)) + while fringe: + (d,v)=heapq.heappop(fringe) + if v in dist: + continue # already searched this node. + dist[v] = d + #for ignore,w,edgedata in G.edges_iter(v,data=True): + #is about 30% slower than the following + if G.is_multigraph(): + edata=[] + for w,keydata in G[v].items(): + minweight=min((dd.get(weight,1) + for k,dd in keydata.items())) + edata.append((w,{weight:minweight})) + else: + edata=iter(G[v].items()) + + for w,edgedata in edata: + vw_dist = dist[v] + edgedata.get(weight,1) + if cutoff is not None: + if vw_dist>cutoff: + continue + if w in dist: + if vw_dist < dist[w]: + raise ValueError('Contradictory paths found:', + 'negative weights?') + elif w not in seen or vw_dist < seen[w]: + seen[w] = vw_dist + heapq.heappush(fringe,(vw_dist,w)) + return dist + + +def single_source_dijkstra(G,source,target=None,cutoff=None,weight='weight'): + """Compute shortest paths and lengths in a weighted graph G. + + Uses Dijkstra's algorithm for shortest paths. + + Parameters + ---------- + G : NetworkX graph + + source : node label + Starting node for path + + target : node label, optional + Ending node for path + + cutoff : integer or float, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + distance,path : dictionaries + Returns a tuple of two dictionaries keyed by node. + The first dictionary stores distance from the source. + The second stores the path from the source to that node. + + + Examples + -------- + >>> G=nx.path_graph(5) + >>> length,path=nx.single_source_dijkstra(G,0) + >>> print(length[4]) + 4 + >>> print(length) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} + >>> path[4] + [0, 1, 2, 3, 4] + + Notes + --------- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + Based on the Python cookbook recipe (119466) at + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/119466 + + This algorithm is not guaranteed to work if edge weights + are negative or are floating point numbers + (overflows and roundoff errors can cause problems). + + See Also + -------- + single_source_dijkstra_path() + single_source_dijkstra_path_length() + """ + if source==target: + return ({source:0}, {source:[source]}) + dist = {} # dictionary of final distances + paths = {source:[source]} # dictionary of paths + seen = {source:0} + fringe=[] # use heapq with (distance,label) tuples + heapq.heappush(fringe,(0,source)) + while fringe: + (d,v)=heapq.heappop(fringe) + if v in dist: + continue # already searched this node. + dist[v] = d + if v == target: + break + #for ignore,w,edgedata in G.edges_iter(v,data=True): + #is about 30% slower than the following + if G.is_multigraph(): + edata=[] + for w,keydata in G[v].items(): + minweight=min((dd.get(weight,1) + for k,dd in keydata.items())) + edata.append((w,{weight:minweight})) + else: + edata=iter(G[v].items()) + + for w,edgedata in edata: + vw_dist = dist[v] + edgedata.get(weight,1) + if cutoff is not None: + if vw_dist>cutoff: + continue + if w in dist: + if vw_dist < dist[w]: + raise ValueError('Contradictory paths found:', + 'negative weights?') + elif w not in seen or vw_dist < seen[w]: + seen[w] = vw_dist + heapq.heappush(fringe,(vw_dist,w)) + paths[w] = paths[v]+[w] + return (dist,paths) + + +def dijkstra_predecessor_and_distance(G,source, cutoff=None, weight='weight'): + """Compute shortest path length and predecessors on shortest paths + in weighted graphs. + + Parameters + ---------- + G : NetworkX graph + + source : node label + Starting node for path + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + cutoff : integer or float, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + pred,distance : dictionaries + Returns two dictionaries representing a list of predecessors + of a node and the distance to each node. + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + The list of predecessors contains more than one element only when + there are more than one shortest paths to the key node. + """ + push=heapq.heappush + pop=heapq.heappop + dist = {} # dictionary of final distances + pred = {source:[]} # dictionary of predecessors + seen = {source:0} + fringe=[] # use heapq with (distance,label) tuples + push(fringe,(0,source)) + while fringe: + (d,v)=pop(fringe) + if v in dist: continue # already searched this node. + dist[v] = d + if G.is_multigraph(): + edata=[] + for w,keydata in G[v].items(): + minweight=min((dd.get(weight,1) + for k,dd in keydata.items())) + edata.append((w,{weight:minweight})) + else: + edata=iter(G[v].items()) + for w,edgedata in edata: + vw_dist = dist[v] + edgedata.get(weight,1) + if cutoff is not None: + if vw_dist>cutoff: + continue + if w in dist: + if vw_dist < dist[w]: + raise ValueError('Contradictory paths found:', + 'negative weights?') + elif w not in seen or vw_dist < seen[w]: + seen[w] = vw_dist + push(fringe,(vw_dist,w)) + pred[w] = [v] + elif vw_dist==seen[w]: + pred[w].append(v) + return (pred,dist) + + +def all_pairs_dijkstra_path_length(G, cutoff=None, weight='weight'): + """ Compute shortest path lengths between all nodes in a weighted graph. + + Parameters + ---------- + G : NetworkX graph + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + cutoff : integer or float, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + distance : dictionary + Dictionary, keyed by source and target, of shortest path lengths. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> length=nx.all_pairs_dijkstra_path_length(G) + >>> print(length[1][4]) + 3 + >>> length[1] + {0: 1, 1: 0, 2: 1, 3: 2, 4: 3} + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + The dictionary returned only has keys for reachable node pairs. + """ + paths={} + for n in G: + paths[n]=single_source_dijkstra_path_length(G,n, cutoff=cutoff, + weight=weight) + return paths + +def all_pairs_dijkstra_path(G, cutoff=None, weight='weight'): + """ Compute shortest paths between all nodes in a weighted graph. + + Parameters + ---------- + G : NetworkX graph + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + cutoff : integer or float, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + distance : dictionary + Dictionary, keyed by source and target, of shortest paths. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> path=nx.all_pairs_dijkstra_path(G) + >>> print(path[0][4]) + [0, 1, 2, 3, 4] + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + See Also + -------- + floyd_warshall() + + """ + paths={} + for n in G: + paths[n]=single_source_dijkstra_path(G, n, cutoff=cutoff, + weight=weight) + return paths + +def bellman_ford(G, source, weight = 'weight'): + """Compute shortest path lengths and predecessors on shortest paths + in weighted graphs. + + The algorithm has a running time of O(mn) where n is the number of + nodes and m is the number of edges. It is slower than Dijkstra but + can handle negative edge weights. + + Parameters + ---------- + G : NetworkX graph + The algorithm works for all types of graphs, including directed + graphs and multigraphs. + + source: node label + Starting node for path + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + Returns + ------- + pred, dist : dictionaries + Returns two dictionaries keyed by node to predecessor in the + path and to the distance from the source respectively. + + Raises + ------ + NetworkXUnbounded + If the (di)graph contains a negative cost (di)cycle, the + algorithm raises an exception to indicate the presence of the + negative cost (di)cycle. Note: any negative weight edge in an + undirected graph is a negative cost cycle. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.path_graph(5, create_using = nx.DiGraph()) + >>> pred, dist = nx.bellman_ford(G, 0) + >>> pred + {0: None, 1: 0, 2: 1, 3: 2, 4: 3} + >>> dist + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} + + >>> from nose.tools import assert_raises + >>> G = nx.cycle_graph(5, create_using = nx.DiGraph()) + >>> G[1][2]['weight'] = -7 + >>> assert_raises(nx.NetworkXUnbounded, nx.bellman_ford, G, 0) + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + The dictionaries returned only have keys for nodes reachable from + the source. + + In the case where the (di)graph is not connected, if a component + not containing the source contains a negative cost (di)cycle, it + will not be detected. + + """ + if source not in G: + raise KeyError("Node %s is not found in the graph"%source) + numb_nodes = len(G) + + dist = {source: 0} + pred = {source: None} + + if numb_nodes == 1: + return pred, dist + + if G.is_multigraph(): + def get_weight(edge_dict): + return min([eattr.get(weight,1) for eattr in edge_dict.values()]) + else: + def get_weight(edge_dict): + return edge_dict.get(weight,1) + + for i in range(numb_nodes): + no_changes=True + # Only need edges from nodes in dist b/c all others have dist==inf + for u, dist_u in list(dist.items()): # get all edges from nodes in dist + for v, edict in G[u].items(): # double loop handles undirected too + dist_v = dist_u + get_weight(edict) + if v not in dist or dist[v] > dist_v: + dist[v] = dist_v + pred[v] = u + no_changes = False + if no_changes: + break + else: + raise nx.NetworkXUnbounded("Negative cost cycle detected.") + return pred, dist + +def negative_edge_cycle(G, weight = 'weight'): + """Return True if there exists a negative edge cycle anywhere in G. + + Parameters + ---------- + G : NetworkX graph + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + Returns + ------- + negative_cycle : bool + True if a negative edge cycle exists, otherwise False. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.cycle_graph(5, create_using = nx.DiGraph()) + >>> print(nx.negative_edge_cycle(G)) + False + >>> G[1][2]['weight'] = -7 + >>> print(nx.negative_edge_cycle(G)) + True + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + This algorithm uses bellman_ford() but finds negative cycles + on any component by first adding a new node connected to + every node, and starting bellman_ford on that node. It then + removes that extra node. + """ + newnode = generate_unique_node() + G.add_edges_from([ (newnode,n) for n in G]) + + try: + bellman_ford(G, newnode, weight) + except nx.NetworkXUnbounded: + G.remove_node(newnode) + return True + G.remove_node(newnode) + return False + + +def bidirectional_dijkstra(G, source, target, weight = 'weight'): + """Dijkstra's algorithm for shortest paths using bidirectional search. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node. + + target : node + Ending node. + + weight: string, optional (default='weight') + Edge data key corresponding to the edge weight + + Returns + ------- + length : number + Shortest path length. + + Returns a tuple of two dictionaries keyed by node. + The first dictionary stores distance from the source. + The second stores the path from the source to that node. + + Raises + ------ + NetworkXNoPath + If no path exists between source and target. + + Examples + -------- + >>> G=nx.path_graph(5) + >>> length,path=nx.bidirectional_dijkstra(G,0,4) + >>> print(length) + 4 + >>> print(path) + [0, 1, 2, 3, 4] + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + In practice bidirectional Dijkstra is much more than twice as fast as + ordinary Dijkstra. + + Ordinary Dijkstra expands nodes in a sphere-like manner from the + source. The radius of this sphere will eventually be the length + of the shortest path. Bidirectional Dijkstra will expand nodes + from both the source and the target, making two spheres of half + this radius. Volume of the first sphere is pi*r*r while the + others are 2*pi*r/2*r/2, making up half the volume. + + This algorithm is not guaranteed to work if edge weights + are negative or are floating point numbers + (overflows and roundoff errors can cause problems). + + See Also + -------- + shortest_path + shortest_path_length + """ + if source == target: return (0, [source]) + #Init: Forward Backward + dists = [{}, {}]# dictionary of final distances + paths = [{source:[source]}, {target:[target]}] # dictionary of paths + fringe = [[], []] #heap of (distance, node) tuples for extracting next node to expand + seen = [{source:0}, {target:0} ]#dictionary of distances to nodes seen + #initialize fringe heap + heapq.heappush(fringe[0], (0, source)) + heapq.heappush(fringe[1], (0, target)) + #neighs for extracting correct neighbor information + if G.is_directed(): + neighs = [G.successors_iter, G.predecessors_iter] + else: + neighs = [G.neighbors_iter, G.neighbors_iter] + #variables to hold shortest discovered path + #finaldist = 1e30000 + finalpath = [] + dir = 1 + while fringe[0] and fringe[1]: + # choose direction + # dir == 0 is forward direction and dir == 1 is back + dir = 1-dir + # extract closest to expand + (dist, v )= heapq.heappop(fringe[dir]) + if v in dists[dir]: + # Shortest path to v has already been found + continue + # update distance + dists[dir][v] = dist #equal to seen[dir][v] + if v in dists[1-dir]: + # if we have scanned v in both directions we are done + # we have now discovered the shortest path + return (finaldist,finalpath) + + for w in neighs[dir](v): + if(dir==0): #forward + if G.is_multigraph(): + minweight=min((dd.get(weight,1) + for k,dd in G[v][w].items())) + else: + minweight=G[v][w].get(weight,1) + vwLength = dists[dir][v] + minweight #G[v][w].get(weight,1) + else: #back, must remember to change v,w->w,v + if G.is_multigraph(): + minweight=min((dd.get(weight,1) + for k,dd in G[w][v].items())) + else: + minweight=G[w][v].get(weight,1) + vwLength = dists[dir][v] + minweight #G[w][v].get(weight,1) + + if w in dists[dir]: + if vwLength < dists[dir][w]: + raise ValueError("Contradictory paths found: negative weights?") + elif w not in seen[dir] or vwLength < seen[dir][w]: + # relaxing + seen[dir][w] = vwLength + heapq.heappush(fringe[dir], (vwLength,w)) + paths[dir][w] = paths[dir][v]+[w] + if w in seen[0] and w in seen[1]: + #see if this path is better than than the already + #discovered shortest path + totaldist = seen[0][w] + seen[1][w] + if finalpath == [] or finaldist > totaldist: + finaldist = totaldist + revpath = paths[1][w][:] + revpath.reverse() + finalpath = paths[0][w] + revpath[1:] + raise nx.NetworkXNoPath("No path between %s and %s." % (source, target)) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/simple_paths.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/simple_paths.py new file mode 100644 index 0000000..f72d4d2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/simple_paths.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 by +# Sergio Nery Simoes +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Sérgio Nery Simões ', + 'Aric Hagberg ']) +__all__ = ['all_simple_paths'] + +def all_simple_paths(G, source, target, cutoff=None): + """Generate all simple paths in the graph G from source to target. + + A simple path is a path with no repeated nodes. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for path + + target : node + Ending node for path + + cutoff : integer, optional + Depth to stop the search. Only paths of length <= cutoff are returned. + + Returns + ------- + path_generator: generator + A generator that produces lists of simple paths. If there are no paths + between the source and target within the given cutoff the generator + produces no output. + + Examples + -------- + >>> G = nx.complete_graph(4) + >>> for path in nx.all_simple_paths(G, source=0, target=3): + ... print(path) + ... + [0, 1, 2, 3] + [0, 1, 3] + [0, 2, 1, 3] + [0, 2, 3] + [0, 3] + >>> paths = nx.all_simple_paths(G, source=0, target=3, cutoff=2) + >>> print(list(paths)) + [[0, 1, 3], [0, 2, 3], [0, 3]] + + Notes + ----- + This algorithm uses a modified depth-first search to generate the + paths [1]_. A single path can be found in `O(V+E)` time but the + number of simple paths in a graph can be very large, e.g. `O(n!)` in + the complete graph of order n. + + References + ---------- + .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms", + Addison Wesley Professional, 3rd ed., 2001. + + See Also + -------- + all_shortest_paths, shortest_path + """ + if source not in G: + raise nx.NetworkXError('source node %s not in graph'%source) + if target not in G: + raise nx.NetworkXError('target node %s not in graph'%target) + if cutoff is None: + cutoff = len(G)-1 + if G.is_multigraph(): + return _all_simple_paths_multigraph(G, source, target, cutoff=cutoff) + else: + return _all_simple_paths_graph(G, source, target, cutoff=cutoff) + +def _all_simple_paths_graph(G, source, target, cutoff=None): + if cutoff < 1: + return + visited = [source] + stack = [iter(G[source])] + while stack: + children = stack[-1] + child = next(children, None) + if child is None: + stack.pop() + visited.pop() + elif len(visited) < cutoff: + if child == target: + yield visited + [target] + elif child not in visited: + visited.append(child) + stack.append(iter(G[child])) + else: #len(visited) == cutoff: + if child == target or target in children: + yield visited + [target] + stack.pop() + visited.pop() + + +def _all_simple_paths_multigraph(G, source, target, cutoff=None): + if cutoff < 1: + return + visited = [source] + stack = [(v for u,v in G.edges(source))] + while stack: + children = stack[-1] + child = next(children, None) + if child is None: + stack.pop() + visited.pop() + elif len(visited) < cutoff: + if child == target: + yield visited + [target] + elif child not in visited: + visited.append(child) + stack.append((v for u,v in G.edges(child))) + else: #len(visited) == cutoff: + count = ([child]+list(children)).count(target) + for i in range(count): + yield visited + [target] + stack.pop() + visited.pop() diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/smetric.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/smetric.py new file mode 100644 index 0000000..0e801bf --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/smetric.py @@ -0,0 +1,37 @@ +import networkx as nx +#from networkx.generators.smax import li_smax_graph + +def s_metric(G, normalized=True): + """Return the s-metric of graph. + + The s-metric is defined as the sum of the products deg(u)*deg(v) + for every edge (u,v) in G. If norm is provided construct the + s-max graph and compute it's s_metric, and return the normalized + s value + + Parameters + ---------- + G : graph + The graph used to compute the s-metric. + normalized : bool (optional) + Normalize the value. + + Returns + ------- + s : float + The s-metric of the graph. + + References + ---------- + .. [1] Lun Li, David Alderson, John C. Doyle, and Walter Willinger, + Towards a Theory of Scale-Free Graphs: + Definition, Properties, and Implications (Extended Version), 2005. + http://arxiv.org/abs/cond-mat/0501169 + """ + if normalized: + raise nx.NetworkXError("Normalization not implemented") +# Gmax = li_smax_graph(list(G.degree().values())) +# return s_metric(G,normalized=False)/s_metric(Gmax,normalized=False) +# else: + return float(sum([G.degree(u)*G.degree(v) for (u,v) in G.edges_iter()])) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/swap.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/swap.py new file mode 100644 index 0000000..33d882d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/swap.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +"""Swap edges in a graph. +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import math +import random +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult (dschult@colgate.edu)' + 'Joel Miller (joel.c.miller.research@gmail.com)' + 'Ben Edwards']) + +__all__ = ['double_edge_swap', + 'connected_double_edge_swap'] + + +def double_edge_swap(G, nswap=1, max_tries=100): + """Swap two edges in the graph while keeping the node degrees fixed. + + A double-edge swap removes two randomly chosen edges u-v and x-y + and creates the new edges u-x and v-y:: + + u--v u v + becomes | | + x--y x y + + If either the edge u-x or v-y already exist no swap is performed + and another attempt is made to find a suitable edge pair. + + Parameters + ---------- + G : graph + An undirected graph + + nswap : integer (optional, default=1) + Number of double-edge swaps to perform + + max_tries : integer (optional) + Maximum number of attempts to swap edges + + Returns + ------- + G : graph + The graph after double edge swaps. + + Notes + ----- + Does not enforce any connectivity constraints. + + The graph G is modified in place. + """ + if G.is_directed(): + raise nx.NetworkXError(\ + "double_edge_swap() not defined for directed graphs.") + if nswap>max_tries: + raise nx.NetworkXError("Number of swaps > number of tries allowed.") + if len(G) < 4: + raise nx.NetworkXError("Graph has less than four nodes.") + # Instead of choosing uniformly at random from a generated edge list, + # this algorithm chooses nonuniformly from the set of nodes with + # probability weighted by degree. + n=0 + swapcount=0 + keys,degrees=zip(*G.degree().items()) # keys, degree + cdf=nx.utils.cumulative_distribution(degrees) # cdf of degree + while swapcount < nswap: +# if random.random() < 0.5: continue # trick to avoid periodicities? + # pick two random edges without creating edge list + # choose source node indices from discrete distribution + (ui,xi)=nx.utils.discrete_sequence(2,cdistribution=cdf) + if ui==xi: + continue # same source, skip + u=keys[ui] # convert index to label + x=keys[xi] + # choose target uniformly from neighbors + v=random.choice(list(G[u])) + y=random.choice(list(G[x])) + if v==y: + continue # same target, skip + if (x not in G[u]) and (y not in G[v]): # don't create parallel edges + G.add_edge(u,x) + G.add_edge(v,y) + G.remove_edge(u,v) + G.remove_edge(x,y) + swapcount+=1 + if n >= max_tries: + e=('Maximum number of swap attempts (%s) exceeded '%n + + 'before desired swaps achieved (%s).'%nswap) + raise nx.NetworkXAlgorithmError(e) + n+=1 + return G + +def connected_double_edge_swap(G, nswap=1): + """Attempt nswap double-edge swaps in the graph G. + + A double-edge swap removes two randomly chosen edges u-v and x-y + and creates the new edges u-x and v-y:: + + u--v u v + becomes | | + x--y x y + + If either the edge u-x or v-y already exist no swap is performed so + the actual count of swapped edges is always <= nswap + + Parameters + ---------- + G : graph + An undirected graph + + nswap : integer (optional, default=1) + Number of double-edge swaps to perform + + Returns + ------- + G : int + The number of successful swaps + + Notes + ----- + The initial graph G must be connected, and the resulting graph is connected. + The graph G is modified in place. + + References + ---------- + .. [1] C. Gkantsidis and M. Mihail and E. Zegura, + The Markov chain simulation method for generating connected + power law random graphs, 2003. + http://citeseer.ist.psu.edu/gkantsidis03markov.html + """ + import math + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected") + if len(G) < 4: + raise nx.NetworkXError("Graph has less than four nodes.") + n=0 + swapcount=0 + deg=G.degree() + dk=list(deg.keys()) # Label key for nodes + cdf=nx.utils.cumulative_distribution(list(G.degree().values())) + window=1 + while n < nswap: + wcount=0 + swapped=[] + while wcount < window and n < nswap: + # Pick two random edges without creating edge list + # Choose source nodes from discrete degree distribution + (ui,xi)=nx.utils.discrete_sequence(2,cdistribution=cdf) + if ui==xi: + continue # same source, skip + u=dk[ui] # convert index to label + x=dk[xi] + # Choose targets uniformly from neighbors + v=random.choice(G.neighbors(u)) + y=random.choice(G.neighbors(x)) # + if v==y: continue # same target, skip + if (not G.has_edge(u,x)) and (not G.has_edge(v,y)): + G.remove_edge(u,v) + G.remove_edge(x,y) + G.add_edge(u,x) + G.add_edge(v,y) + swapped.append((u,v,x,y)) + swapcount+=1 + n+=1 + wcount+=1 + if nx.is_connected(G): + window+=1 + else: + # not connected, undo changes from previous window, decrease window + while swapped: + (u,v,x,y)=swapped.pop() + G.add_edge(u,v) + G.add_edge(x,y) + G.remove_edge(u,x) + G.remove_edge(v,y) + swapcount-=1 + window = int(math.ceil(float(window)/2)) + return swapcount + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_block.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_block.py new file mode 100644 index 0000000..281e385 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_block.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx + +class TestBlock: + + def test_path(self): + G=networkx.path_graph(6) + partition=[[0,1],[2,3],[4,5]] + M=networkx.blockmodel(G,partition) + assert_equal(sorted(M.nodes()),[0,1,2]) + assert_equal(sorted(M.edges()),[(0,1),(1,2)]) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],1) + assert_equal(M.node[n]['nnodes'],2) + assert_equal(M.node[n]['density'],1.0) + + def test_multigraph_path(self): + G=networkx.MultiGraph(networkx.path_graph(6)) + partition=[[0,1],[2,3],[4,5]] + M=networkx.blockmodel(G,partition,multigraph=True) + assert_equal(sorted(M.nodes()),[0,1,2]) + assert_equal(sorted(M.edges()),[(0,1),(1,2)]) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],1) + assert_equal(M.node[n]['nnodes'],2) + assert_equal(M.node[n]['density'],1.0) + + def test_directed_path(self): + G = networkx.DiGraph() + G.add_path(list(range(6))) + partition=[[0,1],[2,3],[4,5]] + M=networkx.blockmodel(G,partition) + assert_equal(sorted(M.nodes()),[0,1,2]) + assert_equal(sorted(M.edges()),[(0,1),(1,2)]) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],1) + assert_equal(M.node[n]['nnodes'],2) + assert_equal(M.node[n]['density'],0.5) + + def test_directed_multigraph_path(self): + G = networkx.MultiDiGraph() + G.add_path(list(range(6))) + partition=[[0,1],[2,3],[4,5]] + M=networkx.blockmodel(G,partition,multigraph=True) + assert_equal(sorted(M.nodes()),[0,1,2]) + assert_equal(sorted(M.edges()),[(0,1),(1,2)]) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],1) + assert_equal(M.node[n]['nnodes'],2) + assert_equal(M.node[n]['density'],0.5) + + @raises(networkx.NetworkXException) + def test_overlapping(self): + G=networkx.path_graph(6) + partition=[[0,1,2],[2,3],[4,5]] + M=networkx.blockmodel(G,partition) + + def test_weighted_path(self): + G=networkx.path_graph(6) + G[0][1]['weight']=1 + G[1][2]['weight']=2 + G[2][3]['weight']=3 + G[3][4]['weight']=4 + G[4][5]['weight']=5 + partition=[[0,1],[2,3],[4,5]] + M=networkx.blockmodel(G,partition) + assert_equal(sorted(M.nodes()),[0,1,2]) + assert_equal(sorted(M.edges()),[(0,1),(1,2)]) + assert_equal(M[0][1]['weight'],2) + assert_equal(M[1][2]['weight'],4) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],1) + assert_equal(M.node[n]['nnodes'],2) + assert_equal(M.node[n]['density'],1.0) + + + def test_barbell(self): + G=networkx.barbell_graph(3,0) + partition=[[0,1,2],[3,4,5]] + M=networkx.blockmodel(G,partition) + assert_equal(sorted(M.nodes()),[0,1]) + assert_equal(sorted(M.edges()),[(0,1)]) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],3) + assert_equal(M.node[n]['nnodes'],3) + assert_equal(M.node[n]['density'],1.0) + + def test_barbell_plus(self): + G=networkx.barbell_graph(3,0) + G.add_edge(0,5) # add extra edge between bells + partition=[[0,1,2],[3,4,5]] + M=networkx.blockmodel(G,partition) + assert_equal(sorted(M.nodes()),[0,1]) + assert_equal(sorted(M.edges()),[(0,1)]) + assert_equal(M[0][1]['weight'],2) + for n in M.nodes(): + assert_equal(M.node[n]['nedges'],3) + assert_equal(M.node[n]['nnodes'],3) + assert_equal(M.node[n]['density'],1.0) + + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_boundary.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_boundary.py new file mode 100644 index 0000000..2b9e714 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_boundary.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +from networkx import convert_node_labels_to_integers as cnlti + +class TestBoundary: + + def setUp(self): + self.null=nx.null_graph() + self.P10=cnlti(nx.path_graph(10),first_label=1) + self.K10=cnlti(nx.complete_graph(10),first_label=1) + + def test_null_node_boundary(self): + """null graph has empty node boundaries""" + null=self.null + assert_equal(nx.node_boundary(null,[]),[]) + assert_equal(nx.node_boundary(null,[],[]),[]) + assert_equal(nx.node_boundary(null,[1,2,3]),[]) + assert_equal(nx.node_boundary(null,[1,2,3],[4,5,6]),[]) + assert_equal(nx.node_boundary(null,[1,2,3],[3,4,5]),[]) + + def test_null_edge_boundary(self): + """null graph has empty edge boundaries""" + null=self.null + assert_equal(nx.edge_boundary(null,[]),[]) + assert_equal(nx.edge_boundary(null,[],[]),[]) + assert_equal(nx.edge_boundary(null,[1,2,3]),[]) + assert_equal(nx.edge_boundary(null,[1,2,3],[4,5,6]),[]) + assert_equal(nx.edge_boundary(null,[1,2,3],[3,4,5]),[]) + + def test_path_node_boundary(self): + """Check node boundaries in path graph.""" + P10=self.P10 + assert_equal(nx.node_boundary(P10,[]),[]) + assert_equal(nx.node_boundary(P10,[],[]),[]) + assert_equal(nx.node_boundary(P10,[1,2,3]),[4]) + assert_equal(sorted(nx.node_boundary(P10,[4,5,6])),[3, 7]) + assert_equal(sorted(nx.node_boundary(P10,[3,4,5,6,7])),[2, 8]) + assert_equal(nx.node_boundary(P10,[8,9,10]),[7]) + assert_equal(sorted(nx.node_boundary(P10,[4,5,6],[9,10])),[]) + + def test_path_edge_boundary(self): + """Check edge boundaries in path graph.""" + P10=self.P10 + + assert_equal(nx.edge_boundary(P10,[]),[]) + assert_equal(nx.edge_boundary(P10,[],[]),[]) + assert_equal(nx.edge_boundary(P10,[1,2,3]),[(3, 4)]) + assert_equal(sorted(nx.edge_boundary(P10,[4,5,6])),[(4, 3), (6, 7)]) + assert_equal(sorted(nx.edge_boundary(P10,[3,4,5,6,7])),[(3, 2), (7, 8)]) + assert_equal(nx.edge_boundary(P10,[8,9,10]),[(8, 7)]) + assert_equal(sorted(nx.edge_boundary(P10,[4,5,6],[9,10])),[]) + assert_equal(nx.edge_boundary(P10,[1,2,3],[3,4,5]) ,[(2, 3), (3, 4)]) + + + def test_k10_node_boundary(self): + """Check node boundaries in K10""" + K10=self.K10 + + assert_equal(nx.node_boundary(K10,[]),[]) + assert_equal(nx.node_boundary(K10,[],[]),[]) + assert_equal(sorted(nx.node_boundary(K10,[1,2,3])), + [4, 5, 6, 7, 8, 9, 10]) + assert_equal(sorted(nx.node_boundary(K10,[4,5,6])), + [1, 2, 3, 7, 8, 9, 10]) + assert_equal(sorted(nx.node_boundary(K10,[3,4,5,6,7])), + [1, 2, 8, 9, 10]) + assert_equal(nx.node_boundary(K10,[4,5,6],[]),[]) + assert_equal(nx.node_boundary(K10,K10),[]) + assert_equal(nx.node_boundary(K10,[1,2,3],[3,4,5]),[4, 5]) + + def test_k10_edge_boundary(self): + """Check edge boundaries in K10""" + K10=self.K10 + + assert_equal(nx.edge_boundary(K10,[]),[]) + assert_equal(nx.edge_boundary(K10,[],[]),[]) + assert_equal(len(nx.edge_boundary(K10,[1,2,3])),21) + assert_equal(len(nx.edge_boundary(K10,[4,5,6,7])),24) + assert_equal(len(nx.edge_boundary(K10,[3,4,5,6,7])),25) + assert_equal(len(nx.edge_boundary(K10,[8,9,10])),21) + assert_equal(sorted(nx.edge_boundary(K10,[4,5,6],[9,10])), + [(4, 9), (4, 10), (5, 9), (5, 10), (6, 9), (6, 10)]) + assert_equal(nx.edge_boundary(K10,[1,2,3],[3,4,5]), + [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), + (2, 5), (3, 4), (3, 5)]) + + + def test_petersen(self): + """Check boundaries in the petersen graph + + cheeger(G,k)=min(|bdy(S)|/|S| for |S|=k, 0 0 or len(set([u]) & vset) > 0, \ + "not a proper matching!") + + eq_(1, len(matching), "matching not length 1!") + graph = nx.Graph() + graph.add_edge(1, 2) + graph.add_edge(1, 5) + graph.add_edge(2, 3) + graph.add_edge(2, 5) + graph.add_edge(3, 4) + graph.add_edge(3, 6) + graph.add_edge(5, 6) + + matching = nx.maximal_matching(graph) + vset = set(u for u, v in matching) + vset = vset | set(v for u, v in matching) + + for edge in graph.edges_iter(): + u, v = edge + ok_(len(set([v]) & vset) > 0 or len(set([u]) & vset) > 0, \ + "not a proper matching!") + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mis.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mis.py new file mode 100644 index 0000000..01092ef --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mis.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: test_maximal_independent_set.py 577 2011-03-01 06:07:53Z lleeoo $ +""" +Tests for maximal (not maximum) independent sets. + +""" +# Copyright (C) 2004-2010 by +# Leo Lopes +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__author__ = """Leo Lopes (leo.lopes@monash.edu)""" + +from nose.tools import * +import networkx as nx +import random + +class TestMaximalIndependantSet(object): + def setup(self): + self.florentine = nx.Graph() + self.florentine.add_edge('Acciaiuoli','Medici') + self.florentine.add_edge('Castellani','Peruzzi') + self.florentine.add_edge('Castellani','Strozzi') + self.florentine.add_edge('Castellani','Barbadori') + self.florentine.add_edge('Medici','Barbadori') + self.florentine.add_edge('Medici','Ridolfi') + self.florentine.add_edge('Medici','Tornabuoni') + self.florentine.add_edge('Medici','Albizzi') + self.florentine.add_edge('Medici','Salviati') + self.florentine.add_edge('Salviati','Pazzi') + self.florentine.add_edge('Peruzzi','Strozzi') + self.florentine.add_edge('Peruzzi','Bischeri') + self.florentine.add_edge('Strozzi','Ridolfi') + self.florentine.add_edge('Strozzi','Bischeri') + self.florentine.add_edge('Ridolfi','Tornabuoni') + self.florentine.add_edge('Tornabuoni','Guadagni') + self.florentine.add_edge('Albizzi','Ginori') + self.florentine.add_edge('Albizzi','Guadagni') + self.florentine.add_edge('Bischeri','Guadagni') + self.florentine.add_edge('Guadagni','Lamberteschi') + + def test_K5(self): + """Maximal independent set: K5""" + G = nx.complete_graph(5) + for node in G: + assert_equal(nx.maximal_independent_set(G, [node]), [node]) + + def test_K55(self): + """Maximal independent set: K55""" + G = nx.complete_graph(55) + for node in G: + assert_equal(nx.maximal_independent_set(G, [node]), [node]) + + def test_exception(self): + """Bad input should raise exception.""" + G = self.florentine + assert_raises(nx.NetworkXUnfeasible, + nx.maximal_independent_set, G, ["Smith"]) + assert_raises(nx.NetworkXUnfeasible, + nx.maximal_independent_set, G, ["Salviati", "Pazzi"]) + + def test_florentine_family(self): + G = self.florentine + indep = nx.maximal_independent_set(G, ["Medici", "Bischeri"]) + assert_equal(sorted(indep), + sorted(["Medici", "Bischeri", "Castellani", "Pazzi", + "Ginori", "Lamberteschi"])) + + def test_bipartite(self): + G = nx.complete_bipartite_graph(12, 34) + indep = nx.maximal_independent_set(G, [4, 5, 9, 10]) + assert_equal(sorted(indep), list(range(12))) + + + def test_random_graphs(self): + """Generate 50 random graphs of different types and sizes and + make sure that all sets are independent and maximal.""" + for i in range(0, 50, 10): + G = nx.random_graphs.erdos_renyi_graph(i*10+1, random.random()) + IS = nx.maximal_independent_set(G) + assert_false(G.subgraph(IS).edges()) + neighbors_of_MIS = set.union(*(set(G.neighbors(v)) for v in IS)) + for v in set(G.nodes()).difference(IS): + assert_true(v in neighbors_of_MIS) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mst.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mst.py new file mode 100644 index 0000000..5716506 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_mst.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestMST: + + def setUp(self): + # example from Wikipedia: http://en.wikipedia.org/wiki/Kruskal's_algorithm + G=nx.Graph() + edgelist = [(0,3,[('weight',5)]), + (0,1,[('weight',7)]), + (1,3,[('weight',9)]), + (1,2,[('weight',8)]), + (1,4,[('weight',7)]), + (3,4,[('weight',15)]), + (3,5,[('weight',6)]), + (2,4,[('weight',5)]), + (4,5,[('weight',8)]), + (4,6,[('weight',9)]), + (5,6,[('weight',11)])] + + + G.add_edges_from(edgelist) + self.G=G + tree_edgelist = [(0,1,{'weight':7}), + (0,3,{'weight':5}), + (3,5,{'weight':6}), + (1,4,{'weight':7}), + (4,2,{'weight':5}), + (4,6,{'weight':9})] + self.tree_edgelist=sorted((sorted((u, v))[0], sorted((u, v))[1], d) + for u,v,d in tree_edgelist) + + def test_mst(self): + T=nx.minimum_spanning_tree(self.G) + assert_equal(T.edges(data=True),self.tree_edgelist) + + def test_mst_edges(self): + edgelist=sorted(nx.minimum_spanning_edges(self.G)) + assert_equal(edgelist,self.tree_edgelist) + + def test_mst_disconnected(self): + G=nx.Graph() + G.add_path([1,2]) + G.add_path([10,20]) + T=nx.minimum_spanning_tree(G) + assert_equal(sorted(T.edges()),[(1, 2), (20, 10)]) + assert_equal(sorted(T.nodes()),[1, 2, 10, 20]) + + def test_mst_isolate(self): + G=nx.Graph() + G.add_nodes_from([1,2]) + T=nx.minimum_spanning_tree(G) + assert_equal(sorted(T.nodes()),[1, 2]) + assert_equal(sorted(T.edges()),[]) + + def test_mst_attributes(self): + G=nx.Graph() + G.add_edge(1,2,weight=1,color='red',distance=7) + G.add_edge(2,3,weight=1,color='green',distance=2) + G.add_edge(1,3,weight=10,color='blue',distance=1) + G.add_node(13,color='purple') + G.graph['foo']='bar' + T=nx.minimum_spanning_tree(G) + assert_equal(T.graph,G.graph) + assert_equal(T.node[13],G.node[13]) + assert_equal(T.edge[1][2],G.edge[1][2]) + + def test_mst_edges_specify_weight(self): + G=nx.Graph() + G.add_edge(1,2,weight=1,color='red',distance=7) + G.add_edge(1,3,weight=30,color='blue',distance=1) + G.add_edge(2,3,weight=1,color='green',distance=1) + G.add_node(13,color='purple') + G.graph['foo']='bar' + T=nx.minimum_spanning_tree(G) + assert_equal(sorted(T.nodes()),[1,2,3,13]) + assert_equal(sorted(T.edges()),[(1,2),(2,3)]) + T=nx.minimum_spanning_tree(G,weight='distance') + assert_equal(sorted(T.edges()),[(1,3),(2,3)]) + assert_equal(sorted(T.nodes()),[1,2,3,13]) + + def test_prim_mst(self): + T=nx.prim_mst(self.G) + assert_equal(T.edges(data=True),self.tree_edgelist) + + def test_prim_mst_edges(self): + edgelist=sorted(nx.prim_mst_edges(self.G)) + edgelist=sorted((sorted((u, v))[0], sorted((u, v))[1], d) + for u,v,d in edgelist) + assert_equal(edgelist,self.tree_edgelist) + + def test_prim_mst_disconnected(self): + G=nx.Graph() + G.add_path([1,2]) + G.add_path([10,20]) + T=nx.prim_mst(G) + assert_equal(sorted(T.edges()),[(1, 2), (20, 10)]) + assert_equal(sorted(T.nodes()),[1, 2, 10, 20]) + + def test_prim_mst_isolate(self): + G=nx.Graph() + G.add_nodes_from([1,2]) + T=nx.prim_mst(G) + assert_equal(sorted(T.nodes()),[1, 2]) + assert_equal(sorted(T.edges()),[]) + + def test_prim_mst_attributes(self): + G=nx.Graph() + G.add_edge(1,2,weight=1,color='red',distance=7) + G.add_edge(2,3,weight=1,color='green',distance=2) + G.add_edge(1,3,weight=10,color='blue',distance=1) + G.add_node(13,color='purple') + G.graph['foo']='bar' + T=nx.prim_mst(G) + assert_equal(T.graph,G.graph) + assert_equal(T.node[13],G.node[13]) + assert_equal(T.edge[1][2],G.edge[1][2]) + + def test_prim_mst_edges_specify_weight(self): + G=nx.Graph() + G.add_edge(1,2,weight=1,color='red',distance=7) + G.add_edge(1,3,weight=30,color='blue',distance=1) + G.add_edge(2,3,weight=1,color='green',distance=1) + G.add_node(13,color='purple') + G.graph['foo']='bar' + T=nx.prim_mst(G) + assert_equal(sorted(T.nodes()),[1,2,3,13]) + assert_equal(sorted(T.edges()),[(1,2),(2,3)]) + T=nx.prim_mst(G,weight='distance') + assert_equal(sorted(T.edges()),[(1,3),(2,3)]) + assert_equal(sorted(T.nodes()),[1,2,3,13]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_richclub.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_richclub.py new file mode 100644 index 0000000..8114d91 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_richclub.py @@ -0,0 +1,30 @@ +import networkx as nx +from nose.tools import * + + +def test_richclub(): + G = nx.Graph([(0,1),(0,2),(1,2),(1,3),(1,4),(4,5)]) + rc = nx.richclub.rich_club_coefficient(G,normalized=False) + assert_equal(rc,{0: 12.0/30,1:8.0/12}) + + # test single value + rc0 = nx.richclub.rich_club_coefficient(G,normalized=False)[0] + assert_equal(rc0,12.0/30.0) + +def test_richclub_normalized(): + G = nx.Graph([(0,1),(0,2),(1,2),(1,3),(1,4),(4,5)]) + rcNorm = nx.richclub.rich_club_coefficient(G,Q=2) + assert_equal(rcNorm,{0:1.0,1:1.0}) + + +def test_richclub2(): + T = nx.balanced_tree(2,10) + rc = nx.richclub.rich_club_coefficient(T,normalized=False) + assert_equal(rc,{0:4092/(2047*2046.0), + 1:(2044.0/(1023*1022)), + 2:(2040.0/(1022*1021))}) + +#def test_richclub2_normalized(): +# T = nx.balanced_tree(2,10) +# rcNorm = nx.richclub.rich_club_coefficient(T,Q=2) +# assert_true(rcNorm[0] ==1.0 and rcNorm[1] < 0.9 and rcNorm[2] < 0.9) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_simple_paths.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_simple_paths.py new file mode 100644 index 0000000..268a890 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_simple_paths.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +def test_all_simple_paths(): + G = nx.path_graph(4) + paths = nx.all_simple_paths(G,0,3) + assert_equal(list(list(p) for p in paths),[[0,1,2,3]]) + +def test_all_simple_paths_cutoff(): + G = nx.complete_graph(4) + paths = nx.all_simple_paths(G,0,1,cutoff=1) + assert_equal(list(list(p) for p in paths),[[0,1]]) + paths = nx.all_simple_paths(G,0,1,cutoff=2) + assert_equal(list(list(p) for p in paths),[[0,1],[0,2,1],[0,3,1]]) + +def test_all_simple_paths_multigraph(): + G = nx.MultiGraph([(1,2),(1,2)]) + paths = nx.all_simple_paths(G,1,2) + assert_equal(list(list(p) for p in paths),[[1,2],[1,2]]) + +def test_all_simple_paths_multigraph_with_cutoff(): + G = nx.MultiGraph([(1,2),(1,2),(1,10),(10,2)]) + paths = nx.all_simple_paths(G,1,2, cutoff=1) + assert_equal(list(list(p) for p in paths),[[1,2],[1,2]]) + + +def test_all_simple_paths_directed(): + G = nx.DiGraph() + G.add_path([1,2,3]) + G.add_path([3,2,1]) + paths = nx.all_simple_paths(G,1,3) + assert_equal(list(list(p) for p in paths),[[1,2,3]]) + +def test_all_simple_paths_empty(): + G = nx.path_graph(4) + paths = nx.all_simple_paths(G,0,3,cutoff=2) + assert_equal(list(list(p) for p in paths),[]) + +def hamiltonian_path(G,source): + source = next(G.nodes_iter()) + neighbors = set(G[source])-set([source]) + n = len(G) + for target in neighbors: + for path in nx.all_simple_paths(G,source,target): + if len(path) == n: + yield path + +def test_hamiltonian_path(): + from itertools import permutations + G=nx.complete_graph(4) + paths = [list(p) for p in hamiltonian_path(G,0)] + exact = [[0]+list(p) for p in permutations([1,2,3],3) ] + assert_equal(sorted(paths),sorted(exact)) + +def test_cutoff_zero(): + G = nx.complete_graph(4) + paths = nx.all_simple_paths(G,0,3,cutoff=0) + assert_equal(list(list(p) for p in paths),[]) + paths = nx.all_simple_paths(nx.MultiGraph(G),0,3,cutoff=0) + assert_equal(list(list(p) for p in paths),[]) + +@raises(nx.NetworkXError) +def test_source_missing(): + G = nx.Graph() + G.add_path([1,2,3]) + paths = list(nx.all_simple_paths(nx.MultiGraph(G),0,3)) + +@raises(nx.NetworkXError) +def test_target_missing(): + G = nx.Graph() + G.add_path([1,2,3]) + paths = list(nx.all_simple_paths(nx.MultiGraph(G),1,4)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_smetric.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_smetric.py new file mode 100644 index 0000000..3fb288b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_smetric.py @@ -0,0 +1,19 @@ + +from nose.tools import assert_equal,raises + +import networkx as nx + +def test_smetric(): + g = nx.Graph() + g.add_edge(1,2) + g.add_edge(2,3) + g.add_edge(2,4) + g.add_edge(1,4) + sm = nx.s_metric(g,normalized=False) + assert_equal(sm, 19.0) +# smNorm = nx.s_metric(g,normalized=True) +# assert_equal(smNorm, 0.95) + +@raises(nx.NetworkXError) +def test_normalized(): + sm = nx.s_metric(nx.Graph(),normalized=True) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_swap.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_swap.py new file mode 100644 index 0000000..afa2355 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_swap.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +from nose.tools import * +from networkx import * + +def test_double_edge_swap(): + graph = barabasi_albert_graph(200,1) + degrees = sorted(graph.degree().values()) + G = double_edge_swap(graph, 40) + assert_equal(degrees, sorted(graph.degree().values())) + +def test_connected_double_edge_swap(): + graph = barabasi_albert_graph(200,1) + degrees = sorted(graph.degree().values()) + G = connected_double_edge_swap(graph, 40) + assert_true(is_connected(graph)) + assert_equal(degrees, sorted(graph.degree().values())) + +@raises(NetworkXError) +def test_double_edge_swap_small(): + G = nx.double_edge_swap(nx.path_graph(3)) + +@raises(NetworkXError) +def test_double_edge_swap_tries(): + G = nx.double_edge_swap(nx.path_graph(10),nswap=1,max_tries=0) + +@raises(NetworkXError) +def test_connected_double_edge_swap_small(): + G = nx.connected_double_edge_swap(nx.path_graph(3)) + +@raises(NetworkXError) +def test_connected_double_edge_swap_not_connected(): + G = nx.path_graph(3) + G.add_path([10,11,12]) + G = nx.connected_double_edge_swap(G) + + +def test_degree_seq_c4(): + G = cycle_graph(4) + degrees = sorted(G.degree().values()) + G = double_edge_swap(G,1,100) + assert_equal(degrees, sorted(G.degree().values())) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_vitality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_vitality.py new file mode 100644 index 0000000..06f09fe --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/tests/test_vitality.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestVitality: + + def test_closeness_vitality_unweighted(self): + G=nx.cycle_graph(3) + v=nx.closeness_vitality(G) + assert_equal(v,{0:4.0, 1:4.0, 2:4.0}) + assert_equal(v[0],4.0) + + def test_closeness_vitality_weighted(self): + G=nx.Graph() + G.add_cycle([0,1,2],weight=2) + v=nx.closeness_vitality(G,weight='weight') + assert_equal(v,{0:8.0, 1:8.0, 2:8.0}) + + def test_closeness_vitality_unweighted_digraph(self): + G=nx.DiGraph() + G.add_cycle([0,1,2]) + v=nx.closeness_vitality(G) + assert_equal(v,{0:8.0, 1:8.0, 2:8.0}) + + def test_closeness_vitality_weighted_digraph(self): + G=nx.DiGraph() + G.add_cycle([0,1,2],weight=2) + v=nx.closeness_vitality(G,weight='weight') + assert_equal(v,{0:16.0, 1:16.0, 2:16.0}) + + def test_closeness_vitality_weighted_multidigraph(self): + G=nx.MultiDiGraph() + G.add_cycle([0,1,2],weight=2) + v=nx.closeness_vitality(G,weight='weight') + assert_equal(v,{0:16.0, 1:16.0, 2:16.0}) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/__init__.py new file mode 100644 index 0000000..de558ba --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/__init__.py @@ -0,0 +1,4 @@ +import networkx.algorithms.traversal.depth_first_search +from networkx.algorithms.traversal.depth_first_search import * +import networkx.algorithms.traversal.breadth_first_search +from networkx.algorithms.traversal.breadth_first_search import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/breadth_first_search.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/breadth_first_search.py new file mode 100644 index 0000000..6ef2985 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/breadth_first_search.py @@ -0,0 +1,53 @@ +""" +==================== +Breadth-first search +==================== + +Basic algorithms for breadth-first searching. +""" +__author__ = """\n""".join(['Aric Hagberg ']) + +__all__ = ['bfs_edges', 'bfs_tree', + 'bfs_predecessors', 'bfs_successors'] + +import networkx as nx +from collections import defaultdict, deque + +def bfs_edges(G, source, reverse=False): + """Produce edges in a breadth-first-search starting at source.""" + # Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py + # by D. Eppstein, July 2004. + if reverse and isinstance(G, nx.DiGraph): + neighbors = G.predecessors_iter + else: + neighbors = G.neighbors_iter + visited=set([source]) + queue = deque([(source, neighbors(source))]) + while queue: + parent, children = queue[0] + try: + child = next(children) + if child not in visited: + yield parent, child + visited.add(child) + queue.append((child, neighbors(child))) + except StopIteration: + queue.popleft() + +def bfs_tree(G, source, reverse=False): + """Return directed tree of breadth-first-search from source.""" + T = nx.DiGraph() + T.add_node(source) + T.add_edges_from(bfs_edges(G,source,reverse=reverse)) + return T + +def bfs_predecessors(G, source): + """Return dictionary of predecessors in breadth-first-search from source.""" + return dict((t,s) for s,t in bfs_edges(G,source)) + +def bfs_successors(G, source): + """Return dictionary of successors in breadth-first-search from source.""" + d=defaultdict(list) + for s,t in bfs_edges(G,source): + d[s].append(t) + return dict(d) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/depth_first_search.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/depth_first_search.py new file mode 100644 index 0000000..feb6713 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/depth_first_search.py @@ -0,0 +1,124 @@ +""" +================== +Depth-first search +================== + +Basic algorithms for depth-first searching. + +Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py +by D. Eppstein, July 2004. +""" +__author__ = """\n""".join(['Aric Hagberg ']) + +__all__ = ['dfs_edges', 'dfs_tree', + 'dfs_predecessors', 'dfs_successors', + 'dfs_preorder_nodes','dfs_postorder_nodes', + 'dfs_labeled_edges'] + +import networkx as nx +from collections import defaultdict + +def dfs_edges(G,source=None): + """Produce edges in a depth-first-search starting at source.""" + # Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py + # by D. Eppstein, July 2004. + if source is None: + # produce edges for all components + nodes=G + else: + # produce edges for components with source + nodes=[source] + visited=set() + for start in nodes: + if start in visited: + continue + visited.add(start) + stack = [(start,iter(G[start]))] + while stack: + parent,children = stack[-1] + try: + child = next(children) + if child not in visited: + yield parent,child + visited.add(child) + stack.append((child,iter(G[child]))) + except StopIteration: + stack.pop() + +def dfs_tree(G, source): + """Return directed tree of depth-first-search from source.""" + T = nx.DiGraph() + if source is None: + T.add_nodes_from(G) + else: + T.add_node(source) + T.add_edges_from(dfs_edges(G,source)) + return T + +def dfs_predecessors(G, source=None): + """Return dictionary of predecessors in depth-first-search from source.""" + return dict((t,s) for s,t in dfs_edges(G,source=source)) + + +def dfs_successors(G, source=None): + """Return dictionary of successors in depth-first-search from source.""" + d=defaultdict(list) + for s,t in dfs_edges(G,source=source): + d[s].append(t) + return dict(d) + + +def dfs_postorder_nodes(G,source=None): + """Produce nodes in a depth-first-search post-ordering starting + from source. + """ + post=(v for u,v,d in nx.dfs_labeled_edges(G,source=source) + if d['dir']=='reverse') + # chain source to end of pre-ordering +# return chain(post,[source]) + return post + + +def dfs_preorder_nodes(G,source=None): + """Produce nodes in a depth-first-search pre-ordering starting at source.""" + pre=(v for u,v,d in nx.dfs_labeled_edges(G,source=source) + if d['dir']=='forward') + # chain source to beginning of pre-ordering +# return chain([source],pre) + return pre + + +def dfs_labeled_edges(G,source=None): + """Produce edges in a depth-first-search starting at source and + labeled by direction type (forward, reverse, nontree). + """ + # Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py + # by D. Eppstein, July 2004. + if source is None: + # produce edges for all components + nodes=G + else: + # produce edges for components with source + nodes=[source] + visited=set() + for start in nodes: + if start in visited: + continue + yield start,start,{'dir':'forward'} + visited.add(start) + stack = [(start,iter(G[start]))] + while stack: + parent,children = stack[-1] + try: + child = next(children) + if child in visited: + yield parent,child,{'dir':'nontree'} + else: + yield parent,child,{'dir':'forward'} + visited.add(child) + stack.append((child,iter(G[child]))) + except StopIteration: + stack.pop() + if stack: + yield stack[-1][0],parent,{'dir':'reverse'} + yield start,start,{'dir':'reverse'} diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_bfs.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_bfs.py new file mode 100644 index 0000000..bae2fac --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_bfs.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestBFS: + + def setUp(self): + # simple graph + G=nx.Graph() + G.add_edges_from([(0,1),(1,2),(1,3),(2,4),(3,4)]) + self.G=G + + def test_successor(self): + assert_equal(nx.bfs_successors(self.G,source=0), + {0: [1], 1: [2,3], 2:[4]}) + + def test_predecessor(self): + assert_equal(nx.bfs_predecessors(self.G,source=0), + {1: 0, 2: 1, 3: 1, 4: 2}) + + def test_bfs_tree(self): + T=nx.bfs_tree(self.G,source=0) + assert_equal(sorted(T.nodes()),sorted(self.G.nodes())) + assert_equal(sorted(T.edges()),[(0, 1), (1, 2), (1, 3), (2, 4)]) + + def test_bfs_edges(self): + edges=nx.bfs_edges(self.G,source=0) + assert_equal(list(edges),[(0, 1), (1, 2), (1, 3), (2, 4)]) + + def test_bfs_tree_isolates(self): + G = nx.Graph() + G.add_node(1) + G.add_node(2) + T=nx.bfs_tree(G,source=1) + assert_equal(sorted(T.nodes()),[1]) + assert_equal(sorted(T.edges()),[]) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_dfs.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_dfs.py new file mode 100644 index 0000000..9fad985 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/traversal/tests/test_dfs.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestDFS: + + def setUp(self): + # simple graph + G=nx.Graph() + G.add_edges_from([(0,1),(1,2),(1,3),(2,4),(3,4)]) + self.G=G + # simple graph, disconnected + D=nx.Graph() + D.add_edges_from([(0,1),(2,3)]) + self.D=D + + + def test_preorder_nodes(self): + assert_equal(list(nx.dfs_preorder_nodes(self.G,source=0)), + [0, 1, 2, 4, 3]) + assert_equal(list(nx.dfs_preorder_nodes(self.D)),[0, 1, 2, 3]) + + def test_postorder_nodes(self): + assert_equal(list(nx.dfs_postorder_nodes(self.G,source=0)), + [3, 4, 2, 1, 0]) + assert_equal(list(nx.dfs_postorder_nodes(self.D)),[1, 0, 3, 2]) + + def test_successor(self): + assert_equal(nx.dfs_successors(self.G,source=0), + {0: [1], 1: [2], 2: [4], 4: [3]}) + assert_equal(nx.dfs_successors(self.D), {0: [1], 2: [3]}) + + def test_predecessor(self): + assert_equal(nx.dfs_predecessors(self.G,source=0), + {1: 0, 2: 1, 3: 4, 4: 2}) + assert_equal(nx.dfs_predecessors(self.D), {1: 0, 3: 2}) + + def test_dfs_tree(self): + T=nx.dfs_tree(self.G,source=0) + assert_equal(sorted(T.nodes()),sorted(self.G.nodes())) + assert_equal(sorted(T.edges()),[(0, 1), (1, 2), (2, 4), (4, 3)]) + + def test_dfs_edges(self): + edges=nx.dfs_edges(self.G,source=0) + assert_equal(list(edges),[(0, 1), (1, 2), (2, 4), (4, 3)]) + edges=nx.dfs_edges(self.D) + assert_equal(list(edges),[(0, 1), (2, 3)]) + + def test_dfs_labeled_edges(self): + edges=list(nx.dfs_labeled_edges(self.G,source=0)) + forward=[(u,v) for (u,v,d) in edges if d['dir']=='forward'] + assert_equal(forward,[(0,0), (0, 1), (1, 2), (2, 4), (4, 3)]) + + def test_dfs_labeled_disconnected_edges(self): + edges=list(nx.dfs_labeled_edges(self.D)) + forward=[(u,v) for (u,v,d) in edges if d['dir']=='forward'] + assert_equal(forward,[(0, 0), (0, 1), (2, 2), (2, 3)]) + + def test_dfs_tree_isolates(self): + G = nx.Graph() + G.add_node(1) + G.add_node(2) + T=nx.dfs_tree(G,source=1) + assert_equal(sorted(T.nodes()),[1]) + assert_equal(sorted(T.edges()),[]) + T=nx.dfs_tree(G,source=None) + assert_equal(sorted(T.nodes()),[1, 2]) + assert_equal(sorted(T.edges()),[]) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/vitality.py b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/vitality.py new file mode 100644 index 0000000..c4db32e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/algorithms/vitality.py @@ -0,0 +1,84 @@ +""" +Vitality measures. +""" +# Copyright (C) 2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Renato Fabbri']) +__all__ = ['closeness_vitality'] + +def weiner_index(G, weight=None): + # compute sum of distances between all node pairs + # (with optional weights) + weiner=0.0 + if weight is None: + for n in G: + path_length=nx.single_source_shortest_path_length(G,n) + weiner+=sum(path_length.values()) + else: + for n in G: + path_length=nx.single_source_dijkstra_path_length(G, + n,weight=weight) + weiner+=sum(path_length.values()) + return weiner + + +def closeness_vitality(G, weight=None): + """Compute closeness vitality for nodes. + + Closeness vitality of a node is the change in the sum of distances + between all node pairs when excluding that node. + + Parameters + ---------- + G : graph + + weight : None or string (optional) + The name of the edge attribute used as weight. If None the edge + weights are ignored. + + Returns + ------- + nodes : dictionary + Dictionary with nodes as keys and closeness vitality as the value. + + Examples + -------- + >>> G=nx.cycle_graph(3) + >>> nx.closeness_vitality(G) + {0: 4.0, 1: 4.0, 2: 4.0} + + See Also + -------- + closeness_centrality() + + References + ---------- + .. [1] Ulrik Brandes, Sec. 3.6.2 in + Network Analysis: Methodological Foundations, Springer, 2005. + http://books.google.com/books?id=TTNhSm7HYrIC + """ + multigraph = G.is_multigraph() + wig = weiner_index(G,weight) + closeness_vitality = {} + for n in G: + # remove edges connected to node n and keep list of edges with data + # could remove node n but it doesn't count anyway + if multigraph: + edges = G.edges(n,data=True,keys=True) + if G.is_directed(): + edges += G.in_edges(n,data=True,keys=True) + else: + edges = G.edges(n,data=True) + if G.is_directed(): + edges += G.in_edges(n,data=True) + G.remove_edges_from(edges) + closeness_vitality[n] = wig - weiner_index(G,weight) + # add edges and data back to graph + G.add_edges_from(edges) + return closeness_vitality diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/__init__.py new file mode 100644 index 0000000..fa97851 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/__init__.py @@ -0,0 +1,5 @@ +from networkx.classes.graph import Graph +from networkx.classes.digraph import DiGraph +from networkx.classes.multigraph import MultiGraph +from networkx.classes.multidigraph import MultiDiGraph +from networkx.classes.function import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/digraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/digraph.py new file mode 100644 index 0000000..37b9f9d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/digraph.py @@ -0,0 +1,1236 @@ +"""Base class for directed graphs.""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from copy import deepcopy +import networkx as nx +from networkx.classes.graph import Graph +from networkx.exception import NetworkXError +import networkx.convert as convert +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) + +class DiGraph(Graph): + """ + Base class for directed graphs. + + A DiGraph stores nodes and edges with optional data, or attributes. + + DiGraphs hold directed edges. Self loops are allowed but multiple + (parallel) edges are not. + + Nodes can be arbitrary (hashable) Python objects with optional + key/value attributes. + + Edges are represented as links between nodes with optional + key/value attributes. + + Parameters + ---------- + data : input graph + Data to initialize graph. If data=None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to graph as key=value pairs. + + See Also + -------- + Graph + MultiGraph + MultiDiGraph + + Examples + -------- + Create an empty graph structure (a "null graph") with no nodes and + no edges. + + >>> G = nx.DiGraph() + + G can be grown in several ways. + + **Nodes:** + + Add one node at a time: + + >>> G.add_node(1) + + Add the nodes from any container (a list, dict, set or + even the lines from a file or the nodes from another graph). + + >>> G.add_nodes_from([2,3]) + >>> G.add_nodes_from(range(100,110)) + >>> H=nx.Graph() + >>> H.add_path([0,1,2,3,4,5,6,7,8,9]) + >>> G.add_nodes_from(H) + + In addition to strings and integers any hashable Python object + (except None) can represent a node, e.g. a customized node object, + or even another Graph. + + >>> G.add_node(H) + + **Edges:** + + G can also be grown by adding edges. + + Add one edge, + + >>> G.add_edge(1, 2) + + a list of edges, + + >>> G.add_edges_from([(1,2),(1,3)]) + + or a collection of edges, + + >>> G.add_edges_from(H.edges()) + + If some edges connect nodes not yet in the graph, the nodes + are added automatically. There are no errors when adding + nodes or edges that already exist. + + **Attributes:** + + Each graph, node, and edge can hold key/value attribute pairs + in an associated attribute dictionary (the keys must be hashable). + By default these are empty, but can be added or changed using + add_edge, add_node or direct manipulation of the attribute + dictionaries named graph, node and edge respectively. + + >>> G = nx.DiGraph(day="Friday") + >>> G.graph + {'day': 'Friday'} + + Add node attributes using add_node(), add_nodes_from() or G.node + + >>> G.add_node(1, time='5pm') + >>> G.add_nodes_from([3], time='2pm') + >>> G.node[1] + {'time': '5pm'} + >>> G.node[1]['room'] = 714 + >>> del G.node[1]['room'] # remove attribute + >>> G.nodes(data=True) + [(1, {'time': '5pm'}), (3, {'time': '2pm'})] + + Warning: adding a node to G.node does not add it to the graph. + + Add edge attributes using add_edge(), add_edges_from(), subscript + notation, or G.edge. + + >>> G.add_edge(1, 2, weight=4.7 ) + >>> G.add_edges_from([(3,4),(4,5)], color='red') + >>> G.add_edges_from([(1,2,{'color':'blue'}), (2,3,{'weight':8})]) + >>> G[1][2]['weight'] = 4.7 + >>> G.edge[1][2]['weight'] = 4 + + **Shortcuts:** + + Many common graph features allow python syntax to speed reporting. + + >>> 1 in G # check if node in graph + True + >>> [n for n in G if n<3] # iterate through nodes + [1, 2] + >>> len(G) # number of nodes in graph + 5 + + The fastest way to traverse all edges of a graph is via + adjacency_iter(), but the edges() method is often more convenient. + + >>> for n,nbrsdict in G.adjacency_iter(): + ... for nbr,eattr in nbrsdict.items(): + ... if 'weight' in eattr: + ... (n,nbr,eattr['weight']) + (1, 2, 4) + (2, 3, 8) + >>> [ (u,v,edata['weight']) for u,v,edata in G.edges(data=True) if 'weight' in edata ] + [(1, 2, 4), (2, 3, 8)] + + **Reporting:** + + Simple graph information is obtained using methods. + Iterator versions of many reporting methods exist for efficiency. + Methods exist for reporting nodes(), edges(), neighbors() and degree() + as well as the number of nodes and edges. + + For details on these and other miscellaneous methods, see below. + """ + def __init__(self, data=None, **attr): + """Initialize a graph with edges, name, graph attributes. + + Parameters + ---------- + data : input graph + Data to initialize graph. If data=None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + name : string, optional (default='') + An optional name for the graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to graph as key=value pairs. + + See Also + -------- + convert + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G = nx.Graph(name='my graph') + >>> e = [(1,2),(2,3),(3,4)] # list of edges + >>> G = nx.Graph(e) + + Arbitrary graph attribute pairs (key=value) may be assigned + + >>> G=nx.Graph(e, day="Friday") + >>> G.graph + {'day': 'Friday'} + + """ + self.graph = {} # dictionary for graph attributes + self.node = {} # dictionary for node attributes + # We store two adjacency lists: + # the predecessors of node n are stored in the dict self.pred + # the successors of node n are stored in the dict self.succ=self.adj + self.adj = {} # empty adjacency dictionary + self.pred = {} # predecessor + self.succ = self.adj # successor + + # attempt to load graph with data + if data is not None: + convert.to_networkx_graph(data,create_using=self) + # load graph attributes (must be after convert) + self.graph.update(attr) + self.edge=self.adj + + + def add_node(self, n, attr_dict=None, **attr): + """Add a single node n and update node attributes. + + Parameters + ---------- + n : node + A node can be any hashable Python object except None. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of node attributes. Key/value pairs will + update existing data associated with the node. + attr : keyword arguments, optional + Set or change attributes using key=value. + + See Also + -------- + add_nodes_from + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_node(1) + >>> G.add_node('Hello') + >>> K3 = nx.Graph([(0,1),(1,2),(2,0)]) + >>> G.add_node(K3) + >>> G.number_of_nodes() + 3 + + Use keywords set/change node attributes: + + >>> G.add_node(1,size=10) + >>> G.add_node(3,weight=0.4,UTM=('13S',382871,3972649)) + + Notes + ----- + A hashable object is one that can be used as a key in a Python + dictionary. This includes strings, numbers, tuples of strings + and numbers, etc. + + On many platforms hashable items also include mutables such as + NetworkX Graphs, though one should be careful that the hash + doesn't change on mutables. + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + if n not in self.succ: + self.succ[n] = {} + self.pred[n] = {} + self.node[n] = attr_dict + else: # update attr even if node already exists + self.node[n].update(attr_dict) + + + def add_nodes_from(self, nodes, **attr): + """Add multiple nodes. + + Parameters + ---------- + nodes : iterable container + A container of nodes (list, dict, set, etc.). + OR + A container of (node, attribute dict) tuples. + Node attributes are updated using the attribute dict. + attr : keyword arguments, optional (default= no attributes) + Update attributes for all nodes in nodes. + Node attributes specified in nodes as a tuple + take precedence over attributes specified generally. + + See Also + -------- + add_node + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_nodes_from('Hello') + >>> K3 = nx.Graph([(0,1),(1,2),(2,0)]) + >>> G.add_nodes_from(K3) + >>> sorted(G.nodes(),key=str) + [0, 1, 2, 'H', 'e', 'l', 'o'] + + Use keywords to update specific node attributes for every node. + + >>> G.add_nodes_from([1,2], size=10) + >>> G.add_nodes_from([3,4], weight=0.4) + + Use (node, attrdict) tuples to update attributes for specific + nodes. + + >>> G.add_nodes_from([(1,dict(size=11)), (2,{'color':'blue'})]) + >>> G.node[1]['size'] + 11 + >>> H = nx.Graph() + >>> H.add_nodes_from(G.nodes(data=True)) + >>> H.node[1]['size'] + 11 + + """ + for n in nodes: + try: + newnode=n not in self.succ + except TypeError: + nn,ndict = n + if nn not in self.succ: + self.succ[nn] = {} + self.pred[nn] = {} + newdict = attr.copy() + newdict.update(ndict) + self.node[nn] = newdict + else: + olddict = self.node[nn] + olddict.update(attr) + olddict.update(ndict) + continue + if newnode: + self.succ[n] = {} + self.pred[n] = {} + self.node[n] = attr.copy() + else: + self.node[n].update(attr) + + def remove_node(self, n): + """Remove node n. + + Removes the node n and all adjacent edges. + Attempting to remove a non-existent node will raise an exception. + + Parameters + ---------- + n : node + A node in the graph + + Raises + ------- + NetworkXError + If n is not in the graph. + + See Also + -------- + remove_nodes_from + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> G.edges() + [(0, 1), (1, 2)] + >>> G.remove_node(1) + >>> G.edges() + [] + + """ + try: + nbrs=self.succ[n] + del self.node[n] + except KeyError: # NetworkXError if n not in self + raise NetworkXError("The node %s is not in the digraph."%(n,)) + for u in nbrs: + del self.pred[u][n] # remove all edges n-u in digraph + del self.succ[n] # remove node from succ + for u in self.pred[n]: + del self.succ[u][n] # remove all edges n-u in digraph + del self.pred[n] # remove node from pred + + + def remove_nodes_from(self, nbunch): + """Remove multiple nodes. + + Parameters + ---------- + nodes : iterable container + A container of nodes (list, dict, set, etc.). If a node + in the container is not in the graph it is silently + ignored. + + See Also + -------- + remove_node + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> e = G.nodes() + >>> e + [0, 1, 2] + >>> G.remove_nodes_from(e) + >>> G.nodes() + [] + + """ + for n in nbunch: + try: + succs=self.succ[n] + del self.node[n] + for u in succs: + del self.pred[u][n] # remove all edges n-u in digraph + del self.succ[n] # now remove node + for u in self.pred[n]: + del self.succ[u][n] # remove all edges n-u in digraph + del self.pred[n] # now remove node + except KeyError: + pass # silent failure on remove + + + def add_edge(self, u, v, attr_dict=None, **attr): + """Add an edge between u and v. + + The nodes u and v will be automatically added if they are + not already in the graph. + + Edge attributes can be specified with keywords or by providing + a dictionary with key/value pairs. See examples below. + + Parameters + ---------- + u,v : nodes + Nodes can be, for example, strings or numbers. + Nodes must be hashable (and not None) Python objects. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with the edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + See Also + -------- + add_edges_from : add a collection of edges + + Notes + ----- + Adding an edge that already exists updates the edge data. + + Many NetworkX algorithms designed for weighted graphs use as + the edge weight a numerical value assigned to a keyword + which by default is 'weight'. + + Examples + -------- + The following all add the edge e=(1,2) to graph G: + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> e = (1,2) + >>> G.add_edge(1, 2) # explicit two-node form + >>> G.add_edge(*e) # single edge as tuple of two nodes + >>> G.add_edges_from( [(1,2)] ) # add edges from iterable container + + Associate data to edges using keywords: + + >>> G.add_edge(1, 2, weight=3) + >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7) + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + # add nodes + if u not in self.succ: + self.succ[u]={} + self.pred[u]={} + self.node[u] = {} + if v not in self.succ: + self.succ[v]={} + self.pred[v]={} + self.node[v] = {} + # add the edge + datadict=self.adj[u].get(v,{}) + datadict.update(attr_dict) + self.succ[u][v]=datadict + self.pred[v][u]=datadict + + def add_edges_from(self, ebunch, attr_dict=None, **attr): + """Add all the edges in ebunch. + + Parameters + ---------- + ebunch : container of edges + Each edge given in the container will be added to the + graph. The edges must be given as as 2-tuples (u,v) or + 3-tuples (u,v,d) where d is a dictionary containing edge + data. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with each edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + + See Also + -------- + add_edge : add a single edge + add_weighted_edges_from : convenient way to add weighted edges + + Notes + ----- + Adding the same edge twice has no effect but any edge data + will be updated when each duplicate edge is added. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edges_from([(0,1),(1,2)]) # using a list of edge tuples + >>> e = zip(range(0,3),range(1,4)) + >>> G.add_edges_from(e) # Add the path graph 0-1-2-3 + + Associate data to edges + + >>> G.add_edges_from([(1,2),(2,3)], weight=3) + >>> G.add_edges_from([(3,4),(1,4)], label='WN2898') + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dict.") + # process ebunch + for e in ebunch: + ne = len(e) + if ne==3: + u,v,dd = e + assert hasattr(dd,"update") + elif ne==2: + u,v = e + dd = {} + else: + raise NetworkXError(\ + "Edge tuple %s must be a 2-tuple or 3-tuple."%(e,)) + if u not in self.succ: + self.succ[u] = {} + self.pred[u] = {} + self.node[u] = {} + if v not in self.succ: + self.succ[v] = {} + self.pred[v] = {} + self.node[v] = {} + datadict=self.adj[u].get(v,{}) + datadict.update(attr_dict) + datadict.update(dd) + self.succ[u][v] = datadict + self.pred[v][u] = datadict + + + def remove_edge(self, u, v): + """Remove the edge between u and v. + + Parameters + ---------- + u,v: nodes + Remove the edge between nodes u and v. + + Raises + ------ + NetworkXError + If there is not an edge between u and v. + + See Also + -------- + remove_edges_from : remove a collection of edges + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.remove_edge(0,1) + >>> e = (1,2) + >>> G.remove_edge(*e) # unpacks e from an edge tuple + >>> e = (2,3,{'weight':7}) # an edge with attribute data + >>> G.remove_edge(*e[:2]) # select first part of edge tuple + """ + try: + del self.succ[u][v] + del self.pred[v][u] + except KeyError: + raise NetworkXError("The edge %s-%s not in graph."%(u,v)) + + + def remove_edges_from(self, ebunch): + """Remove all edges specified in ebunch. + + Parameters + ---------- + ebunch: list or container of edge tuples + Each edge given in the list or container will be removed + from the graph. The edges can be: + + - 2-tuples (u,v) edge between u and v. + - 3-tuples (u,v,k) where k is ignored. + + See Also + -------- + remove_edge : remove a single edge + + Notes + ----- + Will fail silently if an edge in ebunch is not in the graph. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> ebunch=[(1,2),(2,3)] + >>> G.remove_edges_from(ebunch) + """ + for e in ebunch: + (u,v)=e[:2] # ignore edge data + if u in self.succ and v in self.succ[u]: + del self.succ[u][v] + del self.pred[v][u] + + + def has_successor(self, u, v): + """Return True if node u has successor v. + + This is true if graph has the edge u->v. + """ + return (u in self.succ and v in self.succ[u]) + + def has_predecessor(self, u, v): + """Return True if node u has predecessor v. + + This is true if graph has the edge u<-v. + """ + return (u in self.pred and v in self.pred[u]) + + def successors_iter(self,n): + """Return an iterator over successor nodes of n. + + neighbors_iter() and successors_iter() are the same. + """ + try: + return iter(self.succ[n]) + except KeyError: + raise NetworkXError("The node %s is not in the digraph."%(n,)) + + def predecessors_iter(self,n): + """Return an iterator over predecessor nodes of n.""" + try: + return iter(self.pred[n]) + except KeyError: + raise NetworkXError("The node %s is not in the digraph."%(n,)) + + def successors(self, n): + """Return a list of successor nodes of n. + + neighbors() and successors() are the same function. + """ + return list(self.successors_iter(n)) + + def predecessors(self, n): + """Return a list of predecessor nodes of n.""" + return list(self.predecessors_iter(n)) + + + # digraph definitions + neighbors = successors + neighbors_iter = successors_iter + + def edges_iter(self, nbunch=None, data=False): + """Return an iterator over the edges. + + Edges are returned as tuples with optional data + in the order (node, neighbor, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict in 3-tuple (u,v,data). + + Returns + ------- + edge_iter : iterator + An iterator of (u,v) or (u,v,d) tuples of edges. + + See Also + -------- + edges : return a list of edges + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs this returns the out-edges. + + Examples + -------- + >>> G = nx.DiGraph() # or MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> [e for e in G.edges_iter()] + [(0, 1), (1, 2), (2, 3)] + >>> list(G.edges_iter(data=True)) # default data is {} (empty dict) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + >>> list(G.edges_iter([0,2])) + [(0, 1), (2, 3)] + >>> list(G.edges_iter(0)) + [(0, 1)] + + """ + if nbunch is None: + nodes_nbrs=self.adj.items() + else: + nodes_nbrs=((n,self.adj[n]) for n in self.nbunch_iter(nbunch)) + if data: + for n,nbrs in nodes_nbrs: + for nbr,data in nbrs.items(): + yield (n,nbr,data) + else: + for n,nbrs in nodes_nbrs: + for nbr in nbrs: + yield (n,nbr) + + # alias out_edges to edges + out_edges_iter=edges_iter + out_edges=Graph.edges + + def in_edges_iter(self, nbunch=None, data=False): + """Return an iterator over the incoming edges. + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict in 3-tuple (u,v,data). + + Returns + ------- + in_edge_iter : iterator + An iterator of (u,v) or (u,v,d) tuples of incoming edges. + + See Also + -------- + edges_iter : return an iterator of edges + """ + if nbunch is None: + nodes_nbrs=self.pred.items() + else: + nodes_nbrs=((n,self.pred[n]) for n in self.nbunch_iter(nbunch)) + if data: + for n,nbrs in nodes_nbrs: + for nbr,data in nbrs.items(): + yield (nbr,n,data) + else: + for n,nbrs in nodes_nbrs: + for nbr in nbrs: + yield (nbr,n) + + def in_edges(self, nbunch=None, data=False): + """Return a list of the incoming edges. + + See Also + -------- + edges : return a list of edges + """ + return list(self.in_edges_iter(nbunch, data)) + + def degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, degree). + + The node degree is the number of edges adjacent to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, degree). + + See Also + -------- + degree, in_degree, out_degree, in_degree_iter, out_degree_iter + + Examples + -------- + >>> G = nx.DiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> list(G.degree_iter(0)) # node 0 with degree 1 + [(0, 1)] + >>> list(G.degree_iter([0,1])) + [(0, 1), (1, 2)] + + """ + if nbunch is None: + nodes_nbrs=zip(iter(self.succ.items()),iter(self.pred.items())) + else: + nodes_nbrs=zip( + ((n,self.succ[n]) for n in self.nbunch_iter(nbunch)), + ((n,self.pred[n]) for n in self.nbunch_iter(nbunch))) + + if weight is None: + for (n,succ),(n2,pred) in nodes_nbrs: + yield (n,len(succ)+len(pred)) + else: + # edge weighted graph - degree is sum of edge weights + for (n,succ),(n2,pred) in nodes_nbrs: + yield (n, + sum((succ[nbr].get(weight,1) for nbr in succ))+ + sum((pred[nbr].get(weight,1) for nbr in pred))) + + + def in_degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, in-degree). + + The node in-degree is the number of edges pointing in to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, in-degree). + + See Also + -------- + degree, in_degree, out_degree, out_degree_iter + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_path([0,1,2,3]) + >>> list(G.in_degree_iter(0)) # node 0 with degree 0 + [(0, 0)] + >>> list(G.in_degree_iter([0,1])) + [(0, 0), (1, 1)] + + """ + if nbunch is None: + nodes_nbrs=self.pred.items() + else: + nodes_nbrs=((n,self.pred[n]) for n in self.nbunch_iter(nbunch)) + + if weight is None: + for n,nbrs in nodes_nbrs: + yield (n,len(nbrs)) + else: + # edge weighted graph - degree is sum of edge weights + for n,nbrs in nodes_nbrs: + yield (n, sum(data.get(weight,1) for data in nbrs.values())) + + + def out_degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, out-degree). + + The node out-degree is the number of edges pointing out of the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, out-degree). + + See Also + -------- + degree, in_degree, out_degree, in_degree_iter + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_path([0,1,2,3]) + >>> list(G.out_degree_iter(0)) # node 0 with degree 1 + [(0, 1)] + >>> list(G.out_degree_iter([0,1])) + [(0, 1), (1, 1)] + + """ + if nbunch is None: + nodes_nbrs=self.succ.items() + else: + nodes_nbrs=((n,self.succ[n]) for n in self.nbunch_iter(nbunch)) + + if weight is None: + for n,nbrs in nodes_nbrs: + yield (n,len(nbrs)) + else: + # edge weighted graph - degree is sum of edge weights + for n,nbrs in nodes_nbrs: + yield (n, sum(data.get(weight,1) for data in nbrs.values())) + + + def in_degree(self, nbunch=None, weight=None): + """Return the in-degree of a node or nodes. + + The node in-degree is the number of edges pointing in to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd : dictionary, or number + A dictionary with nodes as keys and in-degree as values or + a number if a single node is specified. + + See Also + -------- + degree, out_degree, in_degree_iter + + Examples + -------- + >>> G = nx.DiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> G.in_degree(0) + 0 + >>> G.in_degree([0,1]) + {0: 0, 1: 1} + >>> list(G.in_degree([0,1]).values()) + [0, 1] + """ + if nbunch in self: # return a single node + return next(self.in_degree_iter(nbunch,weight))[1] + else: # return a dict + return dict(self.in_degree_iter(nbunch,weight)) + + def out_degree(self, nbunch=None, weight=None): + """Return the out-degree of a node or nodes. + + The node out-degree is the number of edges pointing out of the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd : dictionary, or number + A dictionary with nodes as keys and out-degree as values or + a number if a single node is specified. + + Examples + -------- + >>> G = nx.DiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> G.out_degree(0) + 1 + >>> G.out_degree([0,1]) + {0: 1, 1: 1} + >>> list(G.out_degree([0,1]).values()) + [1, 1] + + + """ + if nbunch in self: # return a single node + return next(self.out_degree_iter(nbunch,weight))[1] + else: # return a dict + return dict(self.out_degree_iter(nbunch,weight)) + + def clear(self): + """Remove all nodes and edges from the graph. + + This also removes the name, and all graph, node, and edge attributes. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.clear() + >>> G.nodes() + [] + >>> G.edges() + [] + + """ + self.succ.clear() + self.pred.clear() + self.node.clear() + self.graph.clear() + + + def is_multigraph(self): + """Return True if graph is a multigraph, False otherwise.""" + return False + + + def is_directed(self): + """Return True if graph is directed, False otherwise.""" + return True + + def to_directed(self): + """Return a directed copy of the graph. + + Returns + ------- + G : DiGraph + A deepcopy of the graph. + + Notes + ----- + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar D=DiGraph(G) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + + Examples + -------- + >>> G = nx.Graph() # or MultiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1), (1, 0)] + + If already directed, return a (deep) copy + + >>> G = nx.DiGraph() # or MultiDiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1)] + """ + return deepcopy(self) + + def to_undirected(self, reciprocal=False): + """Return an undirected representation of the digraph. + + Parameters + ---------- + reciprocal : bool (optional) + If True only keep edges that appear in both directions + in the original digraph. + + Returns + ------- + G : Graph + An undirected graph with the same name and nodes and + with edge (u,v,data) if either (u,v,data) or (v,u,data) + is in the digraph. If both edges exist in digraph and + their edge data is different, only one edge is created + with an arbitrary choice of which edge data to use. + You must check and correct for this manually if desired. + + Notes + ----- + If edges in both directions (u,v) and (v,u) exist in the + graph, attributes for the new undirected edge will be a combination of + the attributes of the directed edges. The edge data is updated + in the (arbitrary) order that the edges are encountered. For + more customized control of the edge attributes use add_edge(). + + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar G=DiGraph(D) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + """ + H=Graph() + H.name=self.name + H.add_nodes_from(self) + if reciprocal is True: + H.add_edges_from( (u,v,deepcopy(d)) + for u,nbrs in self.adjacency_iter() + for v,d in nbrs.items() + if v in self.pred[u]) + else: + H.add_edges_from( (u,v,deepcopy(d)) + for u,nbrs in self.adjacency_iter() + for v,d in nbrs.items() ) + H.graph=deepcopy(self.graph) + H.node=deepcopy(self.node) + return H + + + def reverse(self, copy=True): + """Return the reverse of the graph. + + The reverse is a graph with the same nodes and edges + but with the directions of the edges reversed. + + Parameters + ---------- + copy : bool optional (default=True) + If True, return a new DiGraph holding the reversed edges. + If False, reverse the reverse graph is created using + the original graph (this changes the original graph). + """ + if copy: + H = self.__class__(name="Reverse of (%s)"%self.name) + H.add_nodes_from(self) + H.add_edges_from( (v,u,deepcopy(d)) for u,v,d + in self.edges(data=True) ) + H.graph=deepcopy(self.graph) + H.node=deepcopy(self.node) + else: + self.pred,self.succ=self.succ,self.pred + self.adj=self.succ + H=self + return H + + + def subgraph(self, nbunch): + """Return the subgraph induced on nodes in nbunch. + + The induced subgraph of the graph contains the nodes in nbunch + and the edges between those nodes. + + Parameters + ---------- + nbunch : list, iterable + A container of nodes which will be iterated through once. + + Returns + ------- + G : Graph + A subgraph of the graph with the same edge attributes. + + Notes + ----- + The graph, edge or node attributes just point to the original graph. + So changes to the node or edge structure will not be reflected in + the original graph while changes to the attributes will. + + To create a subgraph with its own copy of the edge/node attributes use: + nx.Graph(G.subgraph(nbunch)) + + If edge attributes are containers, a deep copy can be obtained using: + G.subgraph(nbunch).copy() + + For an inplace reduction of a graph to a subgraph you can remove nodes: + G.remove_nodes_from([ n in G if n not in set(nbunch)]) + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> H = G.subgraph([0,1,2]) + >>> H.edges() + [(0, 1), (1, 2)] + """ + bunch = self.nbunch_iter(nbunch) + # create new graph and copy subgraph into it + H = self.__class__() + # copy node and attribute dictionaries + for n in bunch: + H.node[n]=self.node[n] + # namespace shortcuts for speed + H_succ=H.succ + H_pred=H.pred + self_succ=self.succ + # add nodes + for n in H: + H_succ[n]={} + H_pred[n]={} + # add edges + for u in H_succ: + Hnbrs=H_succ[u] + for v,datadict in self_succ[u].items(): + if v in H_succ: + # add both representations of edge: u-v and v-u + Hnbrs[v]=datadict + H_pred[v][u]=datadict + H.graph=self.graph + return H diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/function.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/function.py new file mode 100644 index 0000000..0c5e208 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/function.py @@ -0,0 +1,423 @@ +"""Functional interface to graph methods and assorted utilities. +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# +import networkx as nx +import itertools +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) +__all__ = ['nodes', 'edges', 'degree', 'degree_histogram', 'neighbors', + 'number_of_nodes', 'number_of_edges', 'density', + 'nodes_iter', 'edges_iter', 'is_directed','info', + 'freeze','is_frozen','subgraph','create_empty_copy', + 'set_node_attributes','get_node_attributes', + 'set_edge_attributes','get_edge_attributes', + 'all_neighbors','non_neighbors'] + +def nodes(G): + """Return a copy of the graph nodes in a list.""" + return G.nodes() + +def nodes_iter(G): + """Return an iterator over the graph nodes.""" + return G.nodes_iter() + +def edges(G,nbunch=None): + """Return list of edges adjacent to nodes in nbunch. + + Return all edges if nbunch is unspecified or nbunch=None. + + For digraphs, edges=out_edges + """ + return G.edges(nbunch) + +def edges_iter(G,nbunch=None): + """Return iterator over edges adjacent to nodes in nbunch. + + Return all edges if nbunch is unspecified or nbunch=None. + + For digraphs, edges=out_edges + """ + return G.edges_iter(nbunch) + +def degree(G,nbunch=None,weight=None): + """Return degree of single node or of nbunch of nodes. + If nbunch is ommitted, then return degrees of *all* nodes. + """ + return G.degree(nbunch,weight) + +def neighbors(G,n): + """Return a list of nodes connected to node n. """ + return G.neighbors(n) + +def number_of_nodes(G): + """Return the number of nodes in the graph.""" + return G.number_of_nodes() + +def number_of_edges(G): + """Return the number of edges in the graph. """ + return G.number_of_edges() + +def density(G): + r"""Return the density of a graph. + + The density for undirected graphs is + + .. math:: + + d = \frac{2m}{n(n-1)}, + + and for directed graphs is + + .. math:: + + d = \frac{m}{n(n-1)}, + + where `n` is the number of nodes and `m` is the number of edges in `G`. + + Notes + ----- + The density is 0 for a graph without edges and 1 for a complete graph. + The density of multigraphs can be higher than 1. + + Self loops are counted in the total number of edges so graphs with self + loops can have density higher than 1. + """ + n=number_of_nodes(G) + m=number_of_edges(G) + if m==0 or n <= 1: + d=0.0 + else: + if G.is_directed(): + d=m/float(n*(n-1)) + else: + d= m*2.0/float(n*(n-1)) + return d + +def degree_histogram(G): + """Return a list of the frequency of each degree value. + + Parameters + ---------- + G : Networkx graph + A graph + + Returns + ------- + hist : list + A list of frequencies of degrees. + The degree values are the index in the list. + + Notes + ----- + Note: the bins are width one, hence len(list) can be large + (Order(number_of_edges)) + """ + degseq=list(G.degree().values()) + dmax=max(degseq)+1 + freq= [ 0 for d in range(dmax) ] + for d in degseq: + freq[d] += 1 + return freq + +def is_directed(G): + """ Return True if graph is directed.""" + return G.is_directed() + + +def freeze(G): + """Modify graph to prevent further change by adding or removing + nodes or edges. + + Node and edge data can still be modified. + + Parameters + ----------- + G : graph + A NetworkX graph + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_path([0,1,2,3]) + >>> G=nx.freeze(G) + >>> try: + ... G.add_edge(4,5) + ... except nx.NetworkXError as e: + ... print(str(e)) + Frozen graph can't be modified + + Notes + ----- + To "unfreeze" a graph you must make a copy by creating a new graph object: + + >>> graph = nx.path_graph(4) + >>> frozen_graph = nx.freeze(graph) + >>> unfrozen_graph = nx.Graph(frozen_graph) + >>> nx.is_frozen(unfrozen_graph) + False + + See Also + -------- + is_frozen + """ + def frozen(*args): + raise nx.NetworkXError("Frozen graph can't be modified") + G.add_node=frozen + G.add_nodes_from=frozen + G.remove_node=frozen + G.remove_nodes_from=frozen + G.add_edge=frozen + G.add_edges_from=frozen + G.remove_edge=frozen + G.remove_edges_from=frozen + G.clear=frozen + G.frozen=True + return G + +def is_frozen(G): + """Return True if graph is frozen. + + Parameters + ----------- + G : graph + A NetworkX graph + + See Also + -------- + freeze + """ + try: + return G.frozen + except AttributeError: + return False + +def subgraph(G, nbunch): + """Return the subgraph induced on nodes in nbunch. + + Parameters + ---------- + G : graph + A NetworkX graph + + nbunch : list, iterable + A container of nodes that will be iterated through once (thus + it should be an iterator or be iterable). Each element of the + container should be a valid node type: any hashable type except + None. If nbunch is None, return all edges data in the graph. + Nodes in nbunch that are not in the graph will be (quietly) + ignored. + + Notes + ----- + subgraph(G) calls G.subgraph() + """ + return G.subgraph(nbunch) + +def create_empty_copy(G,with_nodes=True): + """Return a copy of the graph G with all of the edges removed. + + Parameters + ---------- + G : graph + A NetworkX graph + + with_nodes : bool (default=True) + Include nodes. + + Notes + ----- + Graph, node, and edge data is not propagated to the new graph. + """ + H=G.__class__() + if with_nodes: + H.add_nodes_from(G) + return H + + +def info(G, n=None): + """Print short summary of information for the graph G or the node n. + + Parameters + ---------- + G : Networkx graph + A graph + n : node (any hashable) + A node in the graph G + """ + info='' # append this all to a string + if n is None: + info+="Name: %s\n"%G.name + type_name = [type(G).__name__] + info+="Type: %s\n"%",".join(type_name) + info+="Number of nodes: %d\n"%G.number_of_nodes() + info+="Number of edges: %d\n"%G.number_of_edges() + nnodes=G.number_of_nodes() + if len(G) > 0: + if G.is_directed(): + info+="Average in degree: %8.4f\n"%\ + (sum(G.in_degree().values())/float(nnodes)) + info+="Average out degree: %8.4f"%\ + (sum(G.out_degree().values())/float(nnodes)) + else: + s=sum(G.degree().values()) + info+="Average degree: %8.4f"%\ + (float(s)/float(nnodes)) + + else: + if n not in G: + raise nx.NetworkXError("node %s not in graph"%(n,)) + info+="Node % s has the following properties:\n"%n + info+="Degree: %d\n"%G.degree(n) + info+="Neighbors: " + info+=' '.join(str(nbr) for nbr in G.neighbors(n)) + return info + +def set_node_attributes(G,name,attributes): + """Set node attributes from dictionary of nodes and values + + Parameters + ---------- + G : NetworkX Graph + + name : string + Attribute name + + attributes: dict + Dictionary of attributes keyed by node. + + Examples + -------- + >>> G=nx.path_graph(3) + >>> bb=nx.betweenness_centrality(G) + >>> nx.set_node_attributes(G,'betweenness',bb) + >>> G.node[1]['betweenness'] + 1.0 + """ + for node,value in attributes.items(): + G.node[node][name]=value + +def get_node_attributes(G,name): + """Get node attributes from graph + + Parameters + ---------- + G : NetworkX Graph + + name : string + Attribute name + + Returns + ------- + Dictionary of attributes keyed by node. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_nodes_from([1,2,3],color='red') + >>> color=nx.get_node_attributes(G,'color') + >>> color[1] + 'red' + """ + return dict( (n,d[name]) for n,d in G.node.items() if name in d) + + +def set_edge_attributes(G,name,attributes): + """Set edge attributes from dictionary of edge tuples and values + + Parameters + ---------- + G : NetworkX Graph + + name : string + Attribute name + + attributes: dict + Dictionary of attributes keyed by edge (tuple). + + Examples + -------- + >>> G=nx.path_graph(3) + >>> bb=nx.edge_betweenness_centrality(G, normalized=False) + >>> nx.set_edge_attributes(G,'betweenness',bb) + >>> G[1][2]['betweenness'] + 2.0 + """ + for (u,v),value in attributes.items(): + G[u][v][name]=value + +def get_edge_attributes(G,name): + """Get edge attributes from graph + + Parameters + ---------- + G : NetworkX Graph + + name : string + Attribute name + + Returns + ------- + Dictionary of attributes keyed by node. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_path([1,2,3],color='red') + >>> color=nx.get_edge_attributes(G,'color') + >>> color[(1,2)] + 'red' + """ + return dict( ((u,v),d[name]) for u,v,d in G.edges(data=True) if name in d) + + +def all_neighbors(graph, node): + """ Returns all of the neighbors of a node in the graph. + + If the graph is directed returns predecessors as well as successors. + + Parameters + ---------- + graph : NetworkX graph + Graph to find neighbors. + + node : node + The node whose neighbors will be returned. + + Returns + ------- + neighbors : iterator + Iterator of neighbors + """ + if graph.is_directed(): + values = itertools.chain.from_iterable([graph.predecessors_iter(node), + graph.successors_iter(node)]) + else: + values = graph.neighbors_iter(node) + + return values + +def non_neighbors(graph, node): + """Returns the non-neighbors of the node in the graph. + + Parameters + ---------- + graph : NetworkX graph + Graph to find neighbors. + + node : node + The node whose neighbors will be returned. + + Returns + ------- + non_neighbors : iterator + Iterator of nodes in the graph that are not neighbors of the node. + """ + nbors = set(neighbors(graph, node)) | set([node]) + return (nnode for nnode in graph if nnode not in nbors) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/graph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/graph.py new file mode 100644 index 0000000..9ef7c23 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/graph.py @@ -0,0 +1,1816 @@ +"""Base class for undirected graphs. + +The Graph class allows any hashable object as a node +and can associate key/value attribute pairs with each undirected edge. + +Self-loops are allowed but multiple edges are not (see MultiGraph). + +For directed graphs see DiGraph and MultiDiGraph. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from copy import deepcopy +import networkx as nx +from networkx.exception import NetworkXError +import networkx.convert as convert + +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) + +class Graph(object): + """ + Base class for undirected graphs. + + A Graph stores nodes and edges with optional data, or attributes. + + Graphs hold undirected edges. Self loops are allowed but multiple + (parallel) edges are not. + + Nodes can be arbitrary (hashable) Python objects with optional + key/value attributes. + + Edges are represented as links between nodes with optional + key/value attributes. + + Parameters + ---------- + data : input graph + Data to initialize graph. If data=None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to graph as key=value pairs. + + See Also + -------- + DiGraph + MultiGraph + MultiDiGraph + + Examples + -------- + Create an empty graph structure (a "null graph") with no nodes and + no edges. + + >>> G = nx.Graph() + + G can be grown in several ways. + + **Nodes:** + + Add one node at a time: + + >>> G.add_node(1) + + Add the nodes from any container (a list, dict, set or + even the lines from a file or the nodes from another graph). + + >>> G.add_nodes_from([2,3]) + >>> G.add_nodes_from(range(100,110)) + >>> H=nx.Graph() + >>> H.add_path([0,1,2,3,4,5,6,7,8,9]) + >>> G.add_nodes_from(H) + + In addition to strings and integers any hashable Python object + (except None) can represent a node, e.g. a customized node object, + or even another Graph. + + >>> G.add_node(H) + + **Edges:** + + G can also be grown by adding edges. + + Add one edge, + + >>> G.add_edge(1, 2) + + a list of edges, + + >>> G.add_edges_from([(1,2),(1,3)]) + + or a collection of edges, + + >>> G.add_edges_from(H.edges()) + + If some edges connect nodes not yet in the graph, the nodes + are added automatically. There are no errors when adding + nodes or edges that already exist. + + **Attributes:** + + Each graph, node, and edge can hold key/value attribute pairs + in an associated attribute dictionary (the keys must be hashable). + By default these are empty, but can be added or changed using + add_edge, add_node or direct manipulation of the attribute + dictionaries named graph, node and edge respectively. + + >>> G = nx.Graph(day="Friday") + >>> G.graph + {'day': 'Friday'} + + Add node attributes using add_node(), add_nodes_from() or G.node + + >>> G.add_node(1, time='5pm') + >>> G.add_nodes_from([3], time='2pm') + >>> G.node[1] + {'time': '5pm'} + >>> G.node[1]['room'] = 714 + >>> del G.node[1]['room'] # remove attribute + >>> G.nodes(data=True) + [(1, {'time': '5pm'}), (3, {'time': '2pm'})] + + Warning: adding a node to G.node does not add it to the graph. + + Add edge attributes using add_edge(), add_edges_from(), subscript + notation, or G.edge. + + >>> G.add_edge(1, 2, weight=4.7 ) + >>> G.add_edges_from([(3,4),(4,5)], color='red') + >>> G.add_edges_from([(1,2,{'color':'blue'}), (2,3,{'weight':8})]) + >>> G[1][2]['weight'] = 4.7 + >>> G.edge[1][2]['weight'] = 4 + + **Shortcuts:** + + Many common graph features allow python syntax to speed reporting. + + >>> 1 in G # check if node in graph + True + >>> [n for n in G if n<3] # iterate through nodes + [1, 2] + >>> len(G) # number of nodes in graph + 5 + + The fastest way to traverse all edges of a graph is via + adjacency_iter(), but the edges() method is often more convenient. + + >>> for n,nbrsdict in G.adjacency_iter(): + ... for nbr,eattr in nbrsdict.items(): + ... if 'weight' in eattr: + ... (n,nbr,eattr['weight']) + (1, 2, 4) + (2, 1, 4) + (2, 3, 8) + (3, 2, 8) + >>> [ (u,v,edata['weight']) for u,v,edata in G.edges(data=True) if 'weight' in edata ] + [(1, 2, 4), (2, 3, 8)] + + **Reporting:** + + Simple graph information is obtained using methods. + Iterator versions of many reporting methods exist for efficiency. + Methods exist for reporting nodes(), edges(), neighbors() and degree() + as well as the number of nodes and edges. + + For details on these and other miscellaneous methods, see below. + """ + def __init__(self, data=None, **attr): + """Initialize a graph with edges, name, graph attributes. + + Parameters + ---------- + data : input graph + Data to initialize graph. If data=None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + name : string, optional (default='') + An optional name for the graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to graph as key=value pairs. + + See Also + -------- + convert + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G = nx.Graph(name='my graph') + >>> e = [(1,2),(2,3),(3,4)] # list of edges + >>> G = nx.Graph(e) + + Arbitrary graph attribute pairs (key=value) may be assigned + + >>> G=nx.Graph(e, day="Friday") + >>> G.graph + {'day': 'Friday'} + + """ + self.graph = {} # dictionary for graph attributes + self.node = {} # empty node dict (created before convert) + self.adj = {} # empty adjacency dict + # attempt to load graph with data + if data is not None: + convert.to_networkx_graph(data,create_using=self) + # load graph attributes (must be after convert) + self.graph.update(attr) + self.edge = self.adj + + @property + def name(self): + return self.graph.get('name','') + @name.setter + def name(self, s): + self.graph['name']=s + + def __str__(self): + """Return the graph name. + + Returns + ------- + name : string + The name of the graph. + + Examples + -------- + >>> G = nx.Graph(name='foo') + >>> str(G) + 'foo' + """ + return self.name + + def __iter__(self): + """Iterate over the nodes. Use the expression 'for n in G'. + + Returns + ------- + niter : iterator + An iterator over all nodes in the graph. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + """ + return iter(self.node) + + def __contains__(self,n): + """Return True if n is a node, False otherwise. Use the expression + 'n in G'. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> 1 in G + True + """ + try: + return n in self.node + except TypeError: + return False + + def __len__(self): + """Return the number of nodes. Use the expression 'len(G)'. + + Returns + ------- + nnodes : int + The number of nodes in the graph. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> len(G) + 4 + + """ + return len(self.node) + + def __getitem__(self, n): + """Return a dict of neighbors of node n. Use the expression 'G[n]'. + + Parameters + ---------- + n : node + A node in the graph. + + Returns + ------- + adj_dict : dictionary + The adjacency dictionary for nodes connected to n. + + Notes + ----- + G[n] is similar to G.neighbors(n) but the internal data dictionary + is returned instead of a list. + + Assigning G[n] will corrupt the internal graph data structure. + Use G[n] for reading data only. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G[0] + {1: {}} + """ + return self.adj[n] + + + def add_node(self, n, attr_dict=None, **attr): + """Add a single node n and update node attributes. + + Parameters + ---------- + n : node + A node can be any hashable Python object except None. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of node attributes. Key/value pairs will + update existing data associated with the node. + attr : keyword arguments, optional + Set or change attributes using key=value. + + See Also + -------- + add_nodes_from + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_node(1) + >>> G.add_node('Hello') + >>> K3 = nx.Graph([(0,1),(1,2),(2,0)]) + >>> G.add_node(K3) + >>> G.number_of_nodes() + 3 + + Use keywords set/change node attributes: + + >>> G.add_node(1,size=10) + >>> G.add_node(3,weight=0.4,UTM=('13S',382871,3972649)) + + Notes + ----- + A hashable object is one that can be used as a key in a Python + dictionary. This includes strings, numbers, tuples of strings + and numbers, etc. + + On many platforms hashable items also include mutables such as + NetworkX Graphs, though one should be careful that the hash + doesn't change on mutables. + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + if n not in self.node: + self.adj[n] = {} + self.node[n] = attr_dict + else: # update attr even if node already exists + self.node[n].update(attr_dict) + + + def add_nodes_from(self, nodes, **attr): + """Add multiple nodes. + + Parameters + ---------- + nodes : iterable container + A container of nodes (list, dict, set, etc.). + OR + A container of (node, attribute dict) tuples. + Node attributes are updated using the attribute dict. + attr : keyword arguments, optional (default= no attributes) + Update attributes for all nodes in nodes. + Node attributes specified in nodes as a tuple + take precedence over attributes specified generally. + + See Also + -------- + add_node + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_nodes_from('Hello') + >>> K3 = nx.Graph([(0,1),(1,2),(2,0)]) + >>> G.add_nodes_from(K3) + >>> sorted(G.nodes(),key=str) + [0, 1, 2, 'H', 'e', 'l', 'o'] + + Use keywords to update specific node attributes for every node. + + >>> G.add_nodes_from([1,2], size=10) + >>> G.add_nodes_from([3,4], weight=0.4) + + Use (node, attrdict) tuples to update attributes for specific + nodes. + + >>> G.add_nodes_from([(1,dict(size=11)), (2,{'color':'blue'})]) + >>> G.node[1]['size'] + 11 + >>> H = nx.Graph() + >>> H.add_nodes_from(G.nodes(data=True)) + >>> H.node[1]['size'] + 11 + + """ + for n in nodes: + try: + newnode=n not in self.node + except TypeError: + nn,ndict = n + if nn not in self.node: + self.adj[nn] = {} + newdict = attr.copy() + newdict.update(ndict) + self.node[nn] = newdict + else: + olddict = self.node[nn] + olddict.update(attr) + olddict.update(ndict) + continue + if newnode: + self.adj[n] = {} + self.node[n] = attr.copy() + else: + self.node[n].update(attr) + + def remove_node(self,n): + """Remove node n. + + Removes the node n and all adjacent edges. + Attempting to remove a non-existent node will raise an exception. + + Parameters + ---------- + n : node + A node in the graph + + Raises + ------- + NetworkXError + If n is not in the graph. + + See Also + -------- + remove_nodes_from + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> G.edges() + [(0, 1), (1, 2)] + >>> G.remove_node(1) + >>> G.edges() + [] + + """ + adj = self.adj + try: + nbrs = list(adj[n].keys()) # keys handles self-loops (allow mutation later) + del self.node[n] + except KeyError: # NetworkXError if n not in self + raise NetworkXError("The node %s is not in the graph."%(n,)) + for u in nbrs: + del adj[u][n] # remove all edges n-u in graph + del adj[n] # now remove node + + + def remove_nodes_from(self, nodes): + """Remove multiple nodes. + + Parameters + ---------- + nodes : iterable container + A container of nodes (list, dict, set, etc.). If a node + in the container is not in the graph it is silently + ignored. + + See Also + -------- + remove_node + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> e = G.nodes() + >>> e + [0, 1, 2] + >>> G.remove_nodes_from(e) + >>> G.nodes() + [] + + """ + adj = self.adj + for n in nodes: + try: + del self.node[n] + for u in list(adj[n].keys()): # keys() handles self-loops + del adj[u][n] #(allows mutation of dict in loop) + del adj[n] + except KeyError: + pass + + + def nodes_iter(self, data=False): + """Return an iterator over the nodes. + + Parameters + ---------- + data : boolean, optional (default=False) + If False the iterator returns nodes. If True + return a two-tuple of node and node data dictionary + + Returns + ------- + niter : iterator + An iterator over nodes. If data=True the iterator gives + two-tuples containing (node, node data, dictionary) + + Notes + ----- + If the node data is not required it is simpler and equivalent + to use the expression 'for n in G'. + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + + >>> [d for n,d in G.nodes_iter(data=True)] + [{}, {}, {}] + """ + if data: + return iter(self.node.items()) + return iter(self.node) + + def nodes(self, data=False): + """Return a list of the nodes in the graph. + + Parameters + ---------- + data : boolean, optional (default=False) + If False return a list of nodes. If True return a + two-tuple of node and node data dictionary + + Returns + ------- + nlist : list + A list of nodes. If data=True a list of two-tuples containing + (node, node data dictionary). + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> G.nodes() + [0, 1, 2] + >>> G.add_node(1, time='5pm') + >>> G.nodes(data=True) + [(0, {}), (1, {'time': '5pm'}), (2, {})] + """ + return list(self.nodes_iter(data=data)) + + def number_of_nodes(self): + """Return the number of nodes in the graph. + + Returns + ------- + nnodes : int + The number of nodes in the graph. + + See Also + -------- + order, __len__ which are identical + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> len(G) + 3 + """ + return len(self.node) + + def order(self): + """Return the number of nodes in the graph. + + Returns + ------- + nnodes : int + The number of nodes in the graph. + + See Also + -------- + number_of_nodes, __len__ which are identical + + """ + return len(self.node) + + def has_node(self, n): + """Return True if the graph contains the node n. + + Parameters + ---------- + n : node + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2]) + >>> G.has_node(0) + True + + It is more readable and simpler to use + + >>> 0 in G + True + + """ + try: + return n in self.node + except TypeError: + return False + + def add_edge(self, u, v, attr_dict=None, **attr): + """Add an edge between u and v. + + The nodes u and v will be automatically added if they are + not already in the graph. + + Edge attributes can be specified with keywords or by providing + a dictionary with key/value pairs. See examples below. + + Parameters + ---------- + u,v : nodes + Nodes can be, for example, strings or numbers. + Nodes must be hashable (and not None) Python objects. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with the edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + See Also + -------- + add_edges_from : add a collection of edges + + Notes + ----- + Adding an edge that already exists updates the edge data. + + Many NetworkX algorithms designed for weighted graphs use as + the edge weight a numerical value assigned to a keyword + which by default is 'weight'. + + Examples + -------- + The following all add the edge e=(1,2) to graph G: + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> e = (1,2) + >>> G.add_edge(1, 2) # explicit two-node form + >>> G.add_edge(*e) # single edge as tuple of two nodes + >>> G.add_edges_from( [(1,2)] ) # add edges from iterable container + + Associate data to edges using keywords: + + >>> G.add_edge(1, 2, weight=3) + >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7) + """ + # set up attribute dictionary + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + # add nodes + if u not in self.node: + self.adj[u] = {} + self.node[u] = {} + if v not in self.node: + self.adj[v] = {} + self.node[v] = {} + # add the edge + datadict=self.adj[u].get(v,{}) + datadict.update(attr_dict) + self.adj[u][v] = datadict + self.adj[v][u] = datadict + + + def add_edges_from(self, ebunch, attr_dict=None, **attr): + """Add all the edges in ebunch. + + Parameters + ---------- + ebunch : container of edges + Each edge given in the container will be added to the + graph. The edges must be given as as 2-tuples (u,v) or + 3-tuples (u,v,d) where d is a dictionary containing edge + data. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with each edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + + See Also + -------- + add_edge : add a single edge + add_weighted_edges_from : convenient way to add weighted edges + + Notes + ----- + Adding the same edge twice has no effect but any edge data + will be updated when each duplicate edge is added. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edges_from([(0,1),(1,2)]) # using a list of edge tuples + >>> e = zip(range(0,3),range(1,4)) + >>> G.add_edges_from(e) # Add the path graph 0-1-2-3 + + Associate data to edges + + >>> G.add_edges_from([(1,2),(2,3)], weight=3) + >>> G.add_edges_from([(3,4),(1,4)], label='WN2898') + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + # process ebunch + for e in ebunch: + ne=len(e) + if ne==3: + u,v,dd = e + elif ne==2: + u,v = e + dd = {} + else: + raise NetworkXError(\ + "Edge tuple %s must be a 2-tuple or 3-tuple."%(e,)) + if u not in self.node: + self.adj[u] = {} + self.node[u] = {} + if v not in self.node: + self.adj[v] = {} + self.node[v] = {} + datadict=self.adj[u].get(v,{}) + datadict.update(attr_dict) + datadict.update(dd) + self.adj[u][v] = datadict + self.adj[v][u] = datadict + + + def add_weighted_edges_from(self, ebunch, weight='weight', **attr): + """Add all the edges in ebunch as weighted edges with specified + weights. + + Parameters + ---------- + ebunch : container of edges + Each edge given in the list or container will be added + to the graph. The edges must be given as 3-tuples (u,v,w) + where w is a number. + weight : string, optional (default= 'weight') + The attribute name for the edge weights to be added. + attr : keyword arguments, optional (default= no attributes) + Edge attributes to add/update for all edges. + + See Also + -------- + add_edge : add a single edge + add_edges_from : add multiple edges + + Notes + ----- + Adding the same edge twice for Graph/DiGraph simply updates + the edge data. For MultiGraph/MultiDiGraph, duplicate edges + are stored. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_weighted_edges_from([(0,1,3.0),(1,2,7.5)]) + """ + self.add_edges_from(((u,v,{weight:d}) for u,v,d in ebunch),**attr) + + def remove_edge(self, u, v): + """Remove the edge between u and v. + + Parameters + ---------- + u,v: nodes + Remove the edge between nodes u and v. + + Raises + ------ + NetworkXError + If there is not an edge between u and v. + + See Also + -------- + remove_edges_from : remove a collection of edges + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.remove_edge(0,1) + >>> e = (1,2) + >>> G.remove_edge(*e) # unpacks e from an edge tuple + >>> e = (2,3,{'weight':7}) # an edge with attribute data + >>> G.remove_edge(*e[:2]) # select first part of edge tuple + """ + try: + del self.adj[u][v] + if u != v: # self-loop needs only one entry removed + del self.adj[v][u] + except KeyError: + raise NetworkXError("The edge %s-%s is not in the graph"%(u,v)) + + + + def remove_edges_from(self, ebunch): + """Remove all edges specified in ebunch. + + Parameters + ---------- + ebunch: list or container of edge tuples + Each edge given in the list or container will be removed + from the graph. The edges can be: + + - 2-tuples (u,v) edge between u and v. + - 3-tuples (u,v,k) where k is ignored. + + See Also + -------- + remove_edge : remove a single edge + + Notes + ----- + Will fail silently if an edge in ebunch is not in the graph. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> ebunch=[(1,2),(2,3)] + >>> G.remove_edges_from(ebunch) + """ + adj=self.adj + for e in ebunch: + u,v = e[:2] # ignore edge data if present + if u in adj and v in adj[u]: + del adj[u][v] + if u != v: # self loop needs only one entry removed + del adj[v][u] + + + def has_edge(self, u, v): + """Return True if the edge (u,v) is in the graph. + + Parameters + ---------- + u,v : nodes + Nodes can be, for example, strings or numbers. + Nodes must be hashable (and not None) Python objects. + + Returns + ------- + edge_ind : bool + True if edge is in the graph, False otherwise. + + Examples + -------- + Can be called either using two nodes u,v or edge tuple (u,v) + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.has_edge(0,1) # using two nodes + True + >>> e = (0,1) + >>> G.has_edge(*e) # e is a 2-tuple (u,v) + True + >>> e = (0,1,{'weight':7}) + >>> G.has_edge(*e[:2]) # e is a 3-tuple (u,v,data_dictionary) + True + + The following syntax are all equivalent: + + >>> G.has_edge(0,1) + True + >>> 1 in G[0] # though this gives KeyError if 0 not in G + True + + """ + try: + return v in self.adj[u] + except KeyError: + return False + + + def neighbors(self, n): + """Return a list of the nodes connected to the node n. + + Parameters + ---------- + n : node + A node in the graph + + Returns + ------- + nlist : list + A list of nodes that are adjacent to n. + + Raises + ------ + NetworkXError + If the node n is not in the graph. + + Notes + ----- + It is usually more convenient (and faster) to access the + adjacency dictionary as G[n]: + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edge('a','b',weight=7) + >>> G['a'] + {'b': {'weight': 7}} + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.neighbors(0) + [1] + + """ + try: + return list(self.adj[n]) + except KeyError: + raise NetworkXError("The node %s is not in the graph."%(n,)) + + def neighbors_iter(self, n): + """Return an iterator over all neighbors of node n. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> [n for n in G.neighbors_iter(0)] + [1] + + Notes + ----- + It is faster to use the idiom "in G[0]", e.g. + + >>> G = nx.path_graph(4) + >>> [n for n in G[0]] + [1] + """ + try: + return iter(self.adj[n]) + except KeyError: + raise NetworkXError("The node %s is not in the graph."%(n,)) + + def edges(self, nbunch=None, data=False): + """Return a list of edges. + + Edges are returned as tuples with optional data + in the order (node, neighbor, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + Return two tuples (u,v) (False) or three-tuples (u,v,data) (True). + + Returns + -------- + edge_list: list of edge tuples + Edges that are adjacent to any node in nbunch, or a list + of all edges if nbunch is not specified. + + See Also + -------- + edges_iter : return an iterator over the edges + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs this returns the out-edges. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.edges() + [(0, 1), (1, 2), (2, 3)] + >>> G.edges(data=True) # default edge data is {} (empty dictionary) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + >>> G.edges([0,3]) + [(0, 1), (3, 2)] + >>> G.edges(0) + [(0, 1)] + + """ + return list(self.edges_iter(nbunch, data)) + + def edges_iter(self, nbunch=None, data=False): + """Return an iterator over the edges. + + Edges are returned as tuples with optional data + in the order (node, neighbor, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict in 3-tuple (u,v,data). + + Returns + ------- + edge_iter : iterator + An iterator of (u,v) or (u,v,d) tuples of edges. + + See Also + -------- + edges : return a list of edges + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs this returns the out-edges. + + Examples + -------- + >>> G = nx.Graph() # or MultiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> [e for e in G.edges_iter()] + [(0, 1), (1, 2), (2, 3)] + >>> list(G.edges_iter(data=True)) # default data is {} (empty dict) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + >>> list(G.edges_iter([0,3])) + [(0, 1), (3, 2)] + >>> list(G.edges_iter(0)) + [(0, 1)] + + """ + seen={} # helper dict to keep track of multiply stored edges + if nbunch is None: + nodes_nbrs = self.adj.items() + else: + nodes_nbrs=((n,self.adj[n]) for n in self.nbunch_iter(nbunch)) + if data: + for n,nbrs in nodes_nbrs: + for nbr,data in nbrs.items(): + if nbr not in seen: + yield (n,nbr,data) + seen[n]=1 + else: + for n,nbrs in nodes_nbrs: + for nbr in nbrs: + if nbr not in seen: + yield (n,nbr) + seen[n] = 1 + del seen + + + def get_edge_data(self, u, v, default=None): + """Return the attribute dictionary associated with edge (u,v). + + Parameters + ---------- + u,v : nodes + default: any Python object (default=None) + Value to return if the edge (u,v) is not found. + + Returns + ------- + edge_dict : dictionary + The edge attribute dictionary. + + Notes + ----- + It is faster to use G[u][v]. + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G[0][1] + {} + + Warning: Assigning G[u][v] corrupts the graph data structure. + But it is safe to assign attributes to that dictionary, + + >>> G[0][1]['weight'] = 7 + >>> G[0][1]['weight'] + 7 + >>> G[1][0]['weight'] + 7 + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.get_edge_data(0,1) # default edge data is {} + {} + >>> e = (0,1) + >>> G.get_edge_data(*e) # tuple form + {} + >>> G.get_edge_data('a','b',default=0) # edge not in graph, return 0 + 0 + """ + try: + return self.adj[u][v] + except KeyError: + return default + + def adjacency_list(self): + """Return an adjacency list representation of the graph. + + The output adjacency list is in the order of G.nodes(). + For directed graphs, only outgoing adjacencies are included. + + Returns + ------- + adj_list : lists of lists + The adjacency structure of the graph as a list of lists. + + See Also + -------- + adjacency_iter + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.adjacency_list() # in order given by G.nodes() + [[1], [0, 2], [1, 3], [2]] + + """ + return list(map(list,iter(self.adj.values()))) + + def adjacency_iter(self): + """Return an iterator of (node, adjacency dict) tuples for all nodes. + + This is the fastest way to look at every edge. + For directed graphs, only outgoing adjacencies are included. + + Returns + ------- + adj_iter : iterator + An iterator of (node, adjacency dictionary) for all nodes in + the graph. + + See Also + -------- + adjacency_list + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> [(n,nbrdict) for n,nbrdict in G.adjacency_iter()] + [(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})] + + """ + return iter(self.adj.items()) + + def degree(self, nbunch=None, weight=None): + """Return the degree of a node or nodes. + + The node degree is the number of edges adjacent to that node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd : dictionary, or number + A dictionary with nodes as keys and degree as values or + a number if a single node is specified. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.degree(0) + 1 + >>> G.degree([0,1]) + {0: 1, 1: 2} + >>> list(G.degree([0,1]).values()) + [1, 2] + + """ + if nbunch in self: # return a single node + return next(self.degree_iter(nbunch,weight))[1] + else: # return a dict + return dict(self.degree_iter(nbunch,weight)) + + def degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, degree). + + The node degree is the number of edges adjacent to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, degree). + + See Also + -------- + degree + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> list(G.degree_iter(0)) # node 0 with degree 1 + [(0, 1)] + >>> list(G.degree_iter([0,1])) + [(0, 1), (1, 2)] + + """ + if nbunch is None: + nodes_nbrs = self.adj.items() + else: + nodes_nbrs=((n,self.adj[n]) for n in self.nbunch_iter(nbunch)) + + if weight is None: + for n,nbrs in nodes_nbrs: + yield (n,len(nbrs)+(n in nbrs)) # return tuple (n,degree) + else: + # edge weighted graph - degree is sum of nbr edge weights + for n,nbrs in nodes_nbrs: + yield (n, sum((nbrs[nbr].get(weight,1) for nbr in nbrs)) + + (n in nbrs and nbrs[n].get(weight,1))) + + + def clear(self): + """Remove all nodes and edges from the graph. + + This also removes the name, and all graph, node, and edge attributes. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.clear() + >>> G.nodes() + [] + >>> G.edges() + [] + + """ + self.name = '' + self.adj.clear() + self.node.clear() + self.graph.clear() + + def copy(self): + """Return a copy of the graph. + + Returns + ------- + G : Graph + A copy of the graph. + + See Also + -------- + to_directed: return a directed copy of the graph. + + Notes + ----- + This makes a complete copy of the graph including all of the + node or edge attributes. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> H = G.copy() + + """ + return deepcopy(self) + + def is_multigraph(self): + """Return True if graph is a multigraph, False otherwise.""" + return False + + + def is_directed(self): + """Return True if graph is directed, False otherwise.""" + return False + + def to_directed(self): + """Return a directed representation of the graph. + + Returns + ------- + G : DiGraph + A directed graph with the same name, same nodes, and with + each edge (u,v,data) replaced by two directed edges + (u,v,data) and (v,u,data). + + Notes + ----- + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar D=DiGraph(G) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + + Examples + -------- + >>> G = nx.Graph() # or MultiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1), (1, 0)] + + If already directed, return a (deep) copy + + >>> G = nx.DiGraph() # or MultiDiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1)] + """ + from networkx import DiGraph + G=DiGraph() + G.name=self.name + G.add_nodes_from(self) + G.add_edges_from( ((u,v,deepcopy(data)) + for u,nbrs in self.adjacency_iter() + for v,data in nbrs.items()) ) + G.graph=deepcopy(self.graph) + G.node=deepcopy(self.node) + return G + + def to_undirected(self): + """Return an undirected copy of the graph. + + Returns + ------- + G : Graph/MultiGraph + A deepcopy of the graph. + + See Also + -------- + copy, add_edge, add_edges_from + + Notes + ----- + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar G=DiGraph(D) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + + Examples + -------- + >>> G = nx.Graph() # or MultiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1), (1, 0)] + >>> G2 = H.to_undirected() + >>> G2.edges() + [(0, 1)] + """ + return deepcopy(self) + + def subgraph(self, nbunch): + """Return the subgraph induced on nodes in nbunch. + + The induced subgraph of the graph contains the nodes in nbunch + and the edges between those nodes. + + Parameters + ---------- + nbunch : list, iterable + A container of nodes which will be iterated through once. + + Returns + ------- + G : Graph + A subgraph of the graph with the same edge attributes. + + Notes + ----- + The graph, edge or node attributes just point to the original graph. + So changes to the node or edge structure will not be reflected in + the original graph while changes to the attributes will. + + To create a subgraph with its own copy of the edge/node attributes use: + nx.Graph(G.subgraph(nbunch)) + + If edge attributes are containers, a deep copy can be obtained using: + G.subgraph(nbunch).copy() + + For an inplace reduction of a graph to a subgraph you can remove nodes: + G.remove_nodes_from([ n in G if n not in set(nbunch)]) + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> H = G.subgraph([0,1,2]) + >>> H.edges() + [(0, 1), (1, 2)] + """ + bunch =self.nbunch_iter(nbunch) + # create new graph and copy subgraph into it + H = self.__class__() + # copy node and attribute dictionaries + for n in bunch: + H.node[n]=self.node[n] + # namespace shortcuts for speed + H_adj=H.adj + self_adj=self.adj + # add nodes and edges (undirected method) + for n in H.node: + Hnbrs={} + H_adj[n]=Hnbrs + for nbr,d in self_adj[n].items(): + if nbr in H_adj: + # add both representations of edge: n-nbr and nbr-n + Hnbrs[nbr]=d + H_adj[nbr][n]=d + H.graph=self.graph + return H + + + def nodes_with_selfloops(self): + """Return a list of nodes with self loops. + + A node with a self loop has an edge with both ends adjacent + to that node. + + Returns + ------- + nodelist : list + A list of nodes with self loops. + + See Also + -------- + selfloop_edges, number_of_selfloops + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edge(1,1) + >>> G.add_edge(1,2) + >>> G.nodes_with_selfloops() + [1] + """ + return [ n for n,nbrs in self.adj.items() if n in nbrs ] + + def selfloop_edges(self, data=False): + """Return a list of selfloop edges. + + A selfloop edge has the same node at both ends. + + Parameters + ----------- + data : bool, optional (default=False) + Return selfloop edges as two tuples (u,v) (data=False) + or three-tuples (u,v,data) (data=True) + + Returns + ------- + edgelist : list of edge tuples + A list of all selfloop edges. + + See Also + -------- + nodes_with_selfloops, number_of_selfloops + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edge(1,1) + >>> G.add_edge(1,2) + >>> G.selfloop_edges() + [(1, 1)] + >>> G.selfloop_edges(data=True) + [(1, 1, {})] + """ + if data: + return [ (n,n,nbrs[n]) + for n,nbrs in self.adj.items() if n in nbrs ] + else: + return [ (n,n) + for n,nbrs in self.adj.items() if n in nbrs ] + + + def number_of_selfloops(self): + """Return the number of selfloop edges. + + A selfloop edge has the same node at both ends. + + Returns + ------- + nloops : int + The number of selfloops. + + See Also + -------- + nodes_with_selfloops, selfloop_edges + + Examples + -------- + >>> G=nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edge(1,1) + >>> G.add_edge(1,2) + >>> G.number_of_selfloops() + 1 + """ + return len(self.selfloop_edges()) + + + def size(self, weight=None): + """Return the number of edges. + + Parameters + ---------- + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + + Returns + ------- + nedges : int + The number of edges of sum of edge weights in the graph. + + See Also + -------- + number_of_edges + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.size() + 3 + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edge('a','b',weight=2) + >>> G.add_edge('b','c',weight=4) + >>> G.size() + 2 + >>> G.size(weight='weight') + 6.0 + """ + s=sum(self.degree(weight=weight).values())/2 + if weight is None: + return int(s) + else: + return float(s) + + def number_of_edges(self, u=None, v=None): + """Return the number of edges between two nodes. + + Parameters + ---------- + u,v : nodes, optional (default=all edges) + If u and v are specified, return the number of edges between + u and v. Otherwise return the total number of all edges. + + Returns + ------- + nedges : int + The number of edges in the graph. If nodes u and v are specified + return the number of edges between those nodes. + + See Also + -------- + size + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.number_of_edges() + 3 + >>> G.number_of_edges(0,1) + 1 + >>> e = (0,1) + >>> G.number_of_edges(*e) + 1 + """ + if u is None: return int(self.size()) + if v in self.adj[u]: + return 1 + else: + return 0 + + + def add_star(self, nodes, **attr): + """Add a star. + + The first node in nodes is the middle of the star. It is connected + to all other nodes. + + Parameters + ---------- + nodes : iterable container + A container of nodes. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to every edge in star. + + See Also + -------- + add_path, add_cycle + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_star([0,1,2,3]) + >>> G.add_star([10,11,12],weight=2) + + """ + nlist = list(nodes) + v=nlist[0] + edges=((v,n) for n in nlist[1:]) + self.add_edges_from(edges, **attr) + + def add_path(self, nodes, **attr): + """Add a path. + + Parameters + ---------- + nodes : iterable container + A container of nodes. A path will be constructed from + the nodes (in order) and added to the graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to every edge in path. + + See Also + -------- + add_star, add_cycle + + Examples + -------- + >>> G=nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.add_path([10,11,12],weight=7) + + """ + nlist = list(nodes) + edges=zip(nlist[:-1],nlist[1:]) + self.add_edges_from(edges, **attr) + + def add_cycle(self, nodes, **attr): + """Add a cycle. + + Parameters + ---------- + nodes: iterable container + A container of nodes. A cycle will be constructed from + the nodes (in order) and added to the graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to every edge in cycle. + + See Also + -------- + add_path, add_star + + Examples + -------- + >>> G=nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_cycle([0,1,2,3]) + >>> G.add_cycle([10,11,12],weight=7) + + """ + nlist = list(nodes) + edges=zip(nlist,nlist[1:]+[nlist[0]]) + self.add_edges_from(edges, **attr) + + + def nbunch_iter(self, nbunch=None): + """Return an iterator of nodes contained in nbunch that are + also in the graph. + + The nodes in nbunch are checked for membership in the graph + and if not are silently ignored. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + Returns + ------- + niter : iterator + An iterator over nodes in nbunch that are also in the graph. + If nbunch is None, iterate over all nodes in the graph. + + Raises + ------ + NetworkXError + If nbunch is not a node or or sequence of nodes. + If a node in nbunch is not hashable. + + See Also + -------- + Graph.__iter__ + + Notes + ----- + When nbunch is an iterator, the returned iterator yields values + directly from nbunch, becoming exhausted when nbunch is exhausted. + + To test whether nbunch is a single node, one can use + "if nbunch in self:", even after processing with this routine. + + If nbunch is not a node or a (possibly empty) sequence/iterator + or None, a NetworkXError is raised. Also, if any object in + nbunch is not hashable, a NetworkXError is raised. + """ + if nbunch is None: # include all nodes via iterator + bunch=iter(self.adj.keys()) + elif nbunch in self: # if nbunch is a single node + bunch=iter([nbunch]) + else: # if nbunch is a sequence of nodes + def bunch_iter(nlist,adj): + try: + for n in nlist: + if n in adj: + yield n + except TypeError as e: + message=e.args[0] + import sys + sys.stdout.write(message) + # capture error for non-sequence/iterator nbunch. + if 'iter' in message: + raise NetworkXError(\ + "nbunch is not a node or a sequence of nodes.") + # capture error for unhashable node. + elif 'hashable' in message: + raise NetworkXError(\ + "Node %s in the sequence nbunch is not a valid node."%n) + else: + raise + bunch=bunch_iter(nbunch,self.adj) + return bunch diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/multidigraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/multidigraph.py new file mode 100644 index 0000000..392db89 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/multidigraph.py @@ -0,0 +1,851 @@ +"""Base class for MultiDiGraph.""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from copy import deepcopy +import networkx as nx +from networkx.classes.graph import Graph # for doctests +from networkx.classes.digraph import DiGraph +from networkx.classes.multigraph import MultiGraph +from networkx.exception import NetworkXError +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) + +class MultiDiGraph(MultiGraph,DiGraph): + """A directed graph class that can store multiedges. + + Multiedges are multiple edges between two nodes. Each edge + can hold optional data or attributes. + + A MultiDiGraph holds directed edges. Self loops are allowed. + + Nodes can be arbitrary (hashable) Python objects with optional + key/value attributes. + + Edges are represented as links between nodes with optional + key/value attributes. + + Parameters + ---------- + data : input graph + Data to initialize graph. If data=None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to graph as key=value pairs. + + See Also + -------- + Graph + DiGraph + MultiGraph + + Examples + -------- + Create an empty graph structure (a "null graph") with no nodes and + no edges. + + >>> G = nx.MultiDiGraph() + + G can be grown in several ways. + + **Nodes:** + + Add one node at a time: + + >>> G.add_node(1) + + Add the nodes from any container (a list, dict, set or + even the lines from a file or the nodes from another graph). + + >>> G.add_nodes_from([2,3]) + >>> G.add_nodes_from(range(100,110)) + >>> H=nx.Graph() + >>> H.add_path([0,1,2,3,4,5,6,7,8,9]) + >>> G.add_nodes_from(H) + + In addition to strings and integers any hashable Python object + (except None) can represent a node, e.g. a customized node object, + or even another Graph. + + >>> G.add_node(H) + + **Edges:** + + G can also be grown by adding edges. + + Add one edge, + + >>> G.add_edge(1, 2) + + a list of edges, + + >>> G.add_edges_from([(1,2),(1,3)]) + + or a collection of edges, + + >>> G.add_edges_from(H.edges()) + + If some edges connect nodes not yet in the graph, the nodes + are added automatically. If an edge already exists, an additional + edge is created and stored using a key to identify the edge. + By default the key is the lowest unused integer. + + >>> G.add_edges_from([(4,5,dict(route=282)), (4,5,dict(route=37))]) + >>> G[4] + {5: {0: {}, 1: {'route': 282}, 2: {'route': 37}}} + + **Attributes:** + + Each graph, node, and edge can hold key/value attribute pairs + in an associated attribute dictionary (the keys must be hashable). + By default these are empty, but can be added or changed using + add_edge, add_node or direct manipulation of the attribute + dictionaries named graph, node and edge respectively. + + >>> G = nx.MultiDiGraph(day="Friday") + >>> G.graph + {'day': 'Friday'} + + Add node attributes using add_node(), add_nodes_from() or G.node + + >>> G.add_node(1, time='5pm') + >>> G.add_nodes_from([3], time='2pm') + >>> G.node[1] + {'time': '5pm'} + >>> G.node[1]['room'] = 714 + >>> del G.node[1]['room'] # remove attribute + >>> G.nodes(data=True) + [(1, {'time': '5pm'}), (3, {'time': '2pm'})] + + Warning: adding a node to G.node does not add it to the graph. + + Add edge attributes using add_edge(), add_edges_from(), subscript + notation, or G.edge. + + >>> G.add_edge(1, 2, weight=4.7 ) + >>> G.add_edges_from([(3,4),(4,5)], color='red') + >>> G.add_edges_from([(1,2,{'color':'blue'}), (2,3,{'weight':8})]) + >>> G[1][2][0]['weight'] = 4.7 + >>> G.edge[1][2][0]['weight'] = 4 + + **Shortcuts:** + + Many common graph features allow python syntax to speed reporting. + + >>> 1 in G # check if node in graph + True + >>> [n for n in G if n<3] # iterate through nodes + [1, 2] + >>> len(G) # number of nodes in graph + 5 + >>> G[1] # adjacency dict keyed by neighbor to edge attributes + ... # Note: you should not change this dict manually! + {2: {0: {'weight': 4}, 1: {'color': 'blue'}}} + + The fastest way to traverse all edges of a graph is via + adjacency_iter(), but the edges() method is often more convenient. + + >>> for n,nbrsdict in G.adjacency_iter(): + ... for nbr,keydict in nbrsdict.items(): + ... for key,eattr in keydict.items(): + ... if 'weight' in eattr: + ... (n,nbr,eattr['weight']) + (1, 2, 4) + (2, 3, 8) + >>> [ (u,v,edata['weight']) for u,v,edata in G.edges(data=True) if 'weight' in edata ] + [(1, 2, 4), (2, 3, 8)] + + **Reporting:** + + Simple graph information is obtained using methods. + Iterator versions of many reporting methods exist for efficiency. + Methods exist for reporting nodes(), edges(), neighbors() and degree() + as well as the number of nodes and edges. + + For details on these and other miscellaneous methods, see below. + """ + def add_edge(self, u, v, key=None, attr_dict=None, **attr): + """Add an edge between u and v. + + The nodes u and v will be automatically added if they are + not already in the graph. + + Edge attributes can be specified with keywords or by providing + a dictionary with key/value pairs. See examples below. + + Parameters + ---------- + u,v : nodes + Nodes can be, for example, strings or numbers. + Nodes must be hashable (and not None) Python objects. + key : hashable identifier, optional (default=lowest unused integer) + Used to distinguish multiedges between a pair of nodes. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with the edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + See Also + -------- + add_edges_from : add a collection of edges + + Notes + ----- + To replace/update edge data, use the optional key argument + to identify a unique edge. Otherwise a new edge will be created. + + NetworkX algorithms designed for weighted graphs cannot use + multigraphs directly because it is not clear how to handle + multiedge weights. Convert to Graph using edge attribute + 'weight' to enable weighted graph algorithms. + + Examples + -------- + The following all add the edge e=(1,2) to graph G: + + >>> G = nx.MultiDiGraph() + >>> e = (1,2) + >>> G.add_edge(1, 2) # explicit two-node form + >>> G.add_edge(*e) # single edge as tuple of two nodes + >>> G.add_edges_from( [(1,2)] ) # add edges from iterable container + + Associate data to edges using keywords: + + >>> G.add_edge(1, 2, weight=3) + >>> G.add_edge(1, 2, key=0, weight=4) # update data for key=0 + >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7) + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + # add nodes + if u not in self.succ: + self.succ[u] = {} + self.pred[u] = {} + self.node[u] = {} + if v not in self.succ: + self.succ[v] = {} + self.pred[v] = {} + self.node[v] = {} + if v in self.succ[u]: + keydict=self.adj[u][v] + if key is None: + # find a unique integer key + # other methods might be better here? + key=len(keydict) + while key in keydict: + key+=1 + datadict=keydict.get(key,{}) + datadict.update(attr_dict) + keydict[key]=datadict + else: + # selfloops work this way without special treatment + if key is None: + key=0 + datadict={} + datadict.update(attr_dict) + keydict={key:datadict} + self.succ[u][v] = keydict + self.pred[v][u] = keydict + + def remove_edge(self, u, v, key=None): + """Remove an edge between u and v. + + Parameters + ---------- + u,v: nodes + Remove an edge between nodes u and v. + key : hashable identifier, optional (default=None) + Used to distinguish multiple edges between a pair of nodes. + If None remove a single (abritrary) edge between u and v. + + Raises + ------ + NetworkXError + If there is not an edge between u and v, or + if there is no edge with the specified key. + + See Also + -------- + remove_edges_from : remove a collection of edges + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_path([0,1,2,3]) + >>> G.remove_edge(0,1) + >>> e = (1,2) + >>> G.remove_edge(*e) # unpacks e from an edge tuple + + For multiple edges + + >>> G = nx.MultiDiGraph() + >>> G.add_edges_from([(1,2),(1,2),(1,2)]) + >>> G.remove_edge(1,2) # remove a single (arbitrary) edge + + For edges with keys + + >>> G = nx.MultiDiGraph() + >>> G.add_edge(1,2,key='first') + >>> G.add_edge(1,2,key='second') + >>> G.remove_edge(1,2,key='second') + + """ + try: + d=self.adj[u][v] + except (KeyError): + raise NetworkXError( + "The edge %s-%s is not in the graph."%(u,v)) + # remove the edge with specified data + if key is None: + d.popitem() + else: + try: + del d[key] + except (KeyError): + raise NetworkXError( + "The edge %s-%s with key %s is not in the graph."%(u,v,key)) + if len(d)==0: + # remove the key entries if last edge + del self.succ[u][v] + del self.pred[v][u] + + + def edges_iter(self, nbunch=None, data=False, keys=False): + """Return an iterator over the edges. + + Edges are returned as tuples with optional data and keys + in the order (node, neighbor, key, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict with each edge. + keys : bool, optional (default=False) + If True, return edge keys with each edge. + + Returns + ------- + edge_iter : iterator + An iterator of (u,v), (u,v,d) or (u,v,key,d) tuples of edges. + + See Also + -------- + edges : return a list of edges + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs this returns the out-edges. + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_path([0,1,2,3]) + >>> [e for e in G.edges_iter()] + [(0, 1), (1, 2), (2, 3)] + >>> list(G.edges_iter(data=True)) # default data is {} (empty dict) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + >>> list(G.edges_iter([0,2])) + [(0, 1), (2, 3)] + >>> list(G.edges_iter(0)) + [(0, 1)] + + """ + if nbunch is None: + nodes_nbrs = self.adj.items() + else: + nodes_nbrs=((n,self.adj[n]) for n in self.nbunch_iter(nbunch)) + if data: + for n,nbrs in nodes_nbrs: + for nbr,keydict in nbrs.items(): + for key,data in keydict.items(): + if keys: + yield (n,nbr,key,data) + else: + yield (n,nbr,data) + else: + for n,nbrs in nodes_nbrs: + for nbr,keydict in nbrs.items(): + for key,data in keydict.items(): + if keys: + yield (n,nbr,key) + else: + yield (n,nbr) + + # alias out_edges to edges + out_edges_iter=edges_iter + + def out_edges(self, nbunch=None, keys=False, data=False): + """Return a list of the outgoing edges. + + Edges are returned as tuples with optional data and keys + in the order (node, neighbor, key, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict with each edge. + keys : bool, optional (default=False) + If True, return edge keys with each edge. + + Returns + ------- + out_edges : list + An listr of (u,v), (u,v,d) or (u,v,key,d) tuples of edges. + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs edges() is the same as out_edges(). + + See Also + -------- + in_edges: return a list of incoming edges + """ + return list(self.out_edges_iter(nbunch, keys=keys, data=data)) + + + def in_edges_iter(self, nbunch=None, data=False, keys=False): + """Return an iterator over the incoming edges. + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict with each edge. + keys : bool, optional (default=False) + If True, return edge keys with each edge. + + Returns + ------- + in_edge_iter : iterator + An iterator of (u,v), (u,v,d) or (u,v,key,d) tuples of edges. + + See Also + -------- + edges_iter : return an iterator of edges + """ + if nbunch is None: + nodes_nbrs=self.pred.items() + else: + nodes_nbrs=((n,self.pred[n]) for n in self.nbunch_iter(nbunch)) + if data: + for n,nbrs in nodes_nbrs: + for nbr,keydict in nbrs.items(): + for key,data in keydict.items(): + if keys: + yield (nbr,n,key,data) + else: + yield (nbr,n,data) + else: + for n,nbrs in nodes_nbrs: + for nbr,keydict in nbrs.items(): + for key,data in keydict.items(): + if keys: + yield (nbr,n,key) + else: + yield (nbr,n) + + def in_edges(self, nbunch=None, keys=False, data=False): + """Return a list of the incoming edges. + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict with each edge. + keys : bool, optional (default=False) + If True, return edge keys with each edge. + + Returns + ------- + in_edges : list + A list of (u,v), (u,v,d) or (u,v,key,d) tuples of edges. + + See Also + -------- + out_edges: return a list of outgoing edges + """ + return list(self.in_edges_iter(nbunch, keys=keys, data=data)) + + + def degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, degree). + + The node degree is the number of edges adjacent to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, degree). + + See Also + -------- + degree + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_path([0,1,2,3]) + >>> list(G.degree_iter(0)) # node 0 with degree 1 + [(0, 1)] + >>> list(G.degree_iter([0,1])) + [(0, 1), (1, 2)] + + """ + if nbunch is None: + nodes_nbrs=zip(iter(self.succ.items()),iter(self.pred.items())) + else: + nodes_nbrs=zip( + ((n,self.succ[n]) for n in self.nbunch_iter(nbunch)), + ((n,self.pred[n]) for n in self.nbunch_iter(nbunch))) + + if weight is None: + for (n,succ),(n2,pred) in nodes_nbrs: + indeg = sum([len(data) for data in pred.values()]) + outdeg = sum([len(data) for data in succ.values()]) + yield (n, indeg + outdeg) + else: + # edge weighted graph - degree is sum of nbr edge weights + for (n,succ),(n2,pred) in nodes_nbrs: + deg = sum([d.get(weight,1) + for data in pred.values() + for d in data.values()]) + deg += sum([d.get(weight,1) + for data in succ.values() + for d in data.values()]) + yield (n, deg) + + + def in_degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, in-degree). + + The node in-degree is the number of edges pointing in to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, in-degree). + + See Also + -------- + degree, in_degree, out_degree, out_degree_iter + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_path([0,1,2,3]) + >>> list(G.in_degree_iter(0)) # node 0 with degree 0 + [(0, 0)] + >>> list(G.in_degree_iter([0,1])) + [(0, 0), (1, 1)] + + """ + if nbunch is None: + nodes_nbrs=self.pred.items() + else: + nodes_nbrs=((n,self.pred[n]) for n in self.nbunch_iter(nbunch)) + + if weight is None: + for n,nbrs in nodes_nbrs: + yield (n, sum([len(data) for data in nbrs.values()]) ) + else: + # edge weighted graph - degree is sum of nbr edge weights + for n,pred in nodes_nbrs: + deg = sum([d.get(weight,1) + for data in pred.values() + for d in data.values()]) + yield (n, deg) + + + def out_degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, out-degree). + + The node out-degree is the number of edges pointing out of the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, out-degree). + + See Also + -------- + degree, in_degree, out_degree, in_degree_iter + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_path([0,1,2,3]) + >>> list(G.out_degree_iter(0)) # node 0 with degree 1 + [(0, 1)] + >>> list(G.out_degree_iter([0,1])) + [(0, 1), (1, 1)] + + """ + if nbunch is None: + nodes_nbrs=self.succ.items() + else: + nodes_nbrs=((n,self.succ[n]) for n in self.nbunch_iter(nbunch)) + + if weight is None: + for n,nbrs in nodes_nbrs: + yield (n, sum([len(data) for data in nbrs.values()]) ) + else: + for n,succ in nodes_nbrs: + deg = sum([d.get(weight,1) + for data in succ.values() + for d in data.values()]) + yield (n, deg) + + def is_multigraph(self): + """Return True if graph is a multigraph, False otherwise.""" + return True + + def is_directed(self): + """Return True if graph is directed, False otherwise.""" + return True + + def to_directed(self): + """Return a directed copy of the graph. + + Returns + ------- + G : MultiDiGraph + A deepcopy of the graph. + + Notes + ----- + If edges in both directions (u,v) and (v,u) exist in the + graph, attributes for the new undirected edge will be a combination of + the attributes of the directed edges. The edge data is updated + in the (arbitrary) order that the edges are encountered. For + more customized control of the edge attributes use add_edge(). + + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar G=DiGraph(D) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + + Examples + -------- + >>> G = nx.Graph() # or MultiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1), (1, 0)] + + If already directed, return a (deep) copy + + >>> G = nx.MultiDiGraph() + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1)] + """ + return deepcopy(self) + + def to_undirected(self, reciprocal=False): + """Return an undirected representation of the digraph. + + Parameters + ---------- + reciprocal : bool (optional) + If True only keep edges that appear in both directions + in the original digraph. + + Returns + ------- + G : MultiGraph + An undirected graph with the same name and nodes and + with edge (u,v,data) if either (u,v,data) or (v,u,data) + is in the digraph. If both edges exist in digraph and + their edge data is different, only one edge is created + with an arbitrary choice of which edge data to use. + You must check and correct for this manually if desired. + + Notes + ----- + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar D=DiGraph(G) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + """ + H=MultiGraph() + H.name=self.name + H.add_nodes_from(self) + if reciprocal is True: + H.add_edges_from( (u,v,key,deepcopy(data)) + for u,nbrs in self.adjacency_iter() + for v,keydict in nbrs.items() + for key,data in keydict.items() + if self.has_edge(v,u,key)) + else: + H.add_edges_from( (u,v,key,deepcopy(data)) + for u,nbrs in self.adjacency_iter() + for v,keydict in nbrs.items() + for key,data in keydict.items()) + H.graph=deepcopy(self.graph) + H.node=deepcopy(self.node) + return H + + def subgraph(self, nbunch): + """Return the subgraph induced on nodes in nbunch. + + The induced subgraph of the graph contains the nodes in nbunch + and the edges between those nodes. + + Parameters + ---------- + nbunch : list, iterable + A container of nodes which will be iterated through once. + + Returns + ------- + G : Graph + A subgraph of the graph with the same edge attributes. + + Notes + ----- + The graph, edge or node attributes just point to the original graph. + So changes to the node or edge structure will not be reflected in + the original graph while changes to the attributes will. + + To create a subgraph with its own copy of the edge/node attributes use: + nx.Graph(G.subgraph(nbunch)) + + If edge attributes are containers, a deep copy can be obtained using: + G.subgraph(nbunch).copy() + + For an inplace reduction of a graph to a subgraph you can remove nodes: + G.remove_nodes_from([ n in G if n not in set(nbunch)]) + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> H = G.subgraph([0,1,2]) + >>> H.edges() + [(0, 1), (1, 2)] + """ + bunch = self.nbunch_iter(nbunch) + # create new graph and copy subgraph into it + H = self.__class__() + # copy node and attribute dictionaries + for n in bunch: + H.node[n]=self.node[n] + # namespace shortcuts for speed + H_succ=H.succ + H_pred=H.pred + self_succ=self.succ + self_pred=self.pred + # add nodes + for n in H: + H_succ[n]={} + H_pred[n]={} + # add edges + for u in H_succ: + Hnbrs=H_succ[u] + for v,edgedict in self_succ[u].items(): + if v in H_succ: + # add both representations of edge: u-v and v-u + # they share the same edgedict + ed=edgedict.copy() + Hnbrs[v]=ed + H_pred[v][u]=ed + H.graph=self.graph + return H + + def reverse(self, copy=True): + """Return the reverse of the graph. + + The reverse is a graph with the same nodes and edges + but with the directions of the edges reversed. + + Parameters + ---------- + copy : bool optional (default=True) + If True, return a new DiGraph holding the reversed edges. + If False, reverse the reverse graph is created using + the original graph (this changes the original graph). + """ + if copy: + H = self.__class__(name="Reverse of (%s)"%self.name) + H.add_nodes_from(self) + H.add_edges_from( (v,u,k,deepcopy(d)) for u,v,k,d + in self.edges(keys=True, data=True) ) + H.graph=deepcopy(self.graph) + H.node=deepcopy(self.node) + else: + self.pred,self.succ=self.succ,self.pred + self.adj=self.succ + H=self + return H diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/multigraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/multigraph.py new file mode 100644 index 0000000..63bdf0f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/multigraph.py @@ -0,0 +1,966 @@ +"""Base class for MultiGraph.""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from copy import deepcopy +import networkx as nx +from networkx.classes.graph import Graph +from networkx import NetworkXError +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) + +class MultiGraph(Graph): + """ + An undirected graph class that can store multiedges. + + Multiedges are multiple edges between two nodes. Each edge + can hold optional data or attributes. + + A MultiGraph holds undirected edges. Self loops are allowed. + + Nodes can be arbitrary (hashable) Python objects with optional + key/value attributes. + + Edges are represented as links between nodes with optional + key/value attributes. + + Parameters + ---------- + data : input graph + Data to initialize graph. If data=None (default) an empty + graph is created. The data can be an edge list, or any + NetworkX graph object. If the corresponding optional Python + packages are installed the data can also be a NumPy matrix + or 2d ndarray, a SciPy sparse matrix, or a PyGraphviz graph. + attr : keyword arguments, optional (default= no attributes) + Attributes to add to graph as key=value pairs. + + See Also + -------- + Graph + DiGraph + MultiDiGraph + + Examples + -------- + Create an empty graph structure (a "null graph") with no nodes and + no edges. + + >>> G = nx.MultiGraph() + + G can be grown in several ways. + + **Nodes:** + + Add one node at a time: + + >>> G.add_node(1) + + Add the nodes from any container (a list, dict, set or + even the lines from a file or the nodes from another graph). + + >>> G.add_nodes_from([2,3]) + >>> G.add_nodes_from(range(100,110)) + >>> H=nx.Graph() + >>> H.add_path([0,1,2,3,4,5,6,7,8,9]) + >>> G.add_nodes_from(H) + + In addition to strings and integers any hashable Python object + (except None) can represent a node, e.g. a customized node object, + or even another Graph. + + >>> G.add_node(H) + + **Edges:** + + G can also be grown by adding edges. + + Add one edge, + + >>> G.add_edge(1, 2) + + a list of edges, + + >>> G.add_edges_from([(1,2),(1,3)]) + + or a collection of edges, + + >>> G.add_edges_from(H.edges()) + + If some edges connect nodes not yet in the graph, the nodes + are added automatically. If an edge already exists, an additional + edge is created and stored using a key to identify the edge. + By default the key is the lowest unused integer. + + >>> G.add_edges_from([(4,5,dict(route=282)), (4,5,dict(route=37))]) + >>> G[4] + {3: {0: {}}, 5: {0: {}, 1: {'route': 282}, 2: {'route': 37}}} + + **Attributes:** + + Each graph, node, and edge can hold key/value attribute pairs + in an associated attribute dictionary (the keys must be hashable). + By default these are empty, but can be added or changed using + add_edge, add_node or direct manipulation of the attribute + dictionaries named graph, node and edge respectively. + + >>> G = nx.MultiGraph(day="Friday") + >>> G.graph + {'day': 'Friday'} + + Add node attributes using add_node(), add_nodes_from() or G.node + + >>> G.add_node(1, time='5pm') + >>> G.add_nodes_from([3], time='2pm') + >>> G.node[1] + {'time': '5pm'} + >>> G.node[1]['room'] = 714 + >>> del G.node[1]['room'] # remove attribute + >>> G.nodes(data=True) + [(1, {'time': '5pm'}), (3, {'time': '2pm'})] + + Warning: adding a node to G.node does not add it to the graph. + + Add edge attributes using add_edge(), add_edges_from(), subscript + notation, or G.edge. + + >>> G.add_edge(1, 2, weight=4.7 ) + >>> G.add_edges_from([(3,4),(4,5)], color='red') + >>> G.add_edges_from([(1,2,{'color':'blue'}), (2,3,{'weight':8})]) + >>> G[1][2][0]['weight'] = 4.7 + >>> G.edge[1][2][0]['weight'] = 4 + + **Shortcuts:** + + Many common graph features allow python syntax to speed reporting. + + >>> 1 in G # check if node in graph + True + >>> [n for n in G if n<3] # iterate through nodes + [1, 2] + >>> len(G) # number of nodes in graph + 5 + >>> G[1] # adjacency dict keyed by neighbor to edge attributes + ... # Note: you should not change this dict manually! + {2: {0: {'weight': 4}, 1: {'color': 'blue'}}} + + The fastest way to traverse all edges of a graph is via + adjacency_iter(), but the edges() method is often more convenient. + + >>> for n,nbrsdict in G.adjacency_iter(): + ... for nbr,keydict in nbrsdict.items(): + ... for key,eattr in keydict.items(): + ... if 'weight' in eattr: + ... (n,nbr,eattr['weight']) + (1, 2, 4) + (2, 1, 4) + (2, 3, 8) + (3, 2, 8) + >>> [ (u,v,edata['weight']) for u,v,edata in G.edges(data=True) if 'weight' in edata ] + [(1, 2, 4), (2, 3, 8)] + + **Reporting:** + + Simple graph information is obtained using methods. + Iterator versions of many reporting methods exist for efficiency. + Methods exist for reporting nodes(), edges(), neighbors() and degree() + as well as the number of nodes and edges. + + For details on these and other miscellaneous methods, see below. + """ + def add_edge(self, u, v, key=None, attr_dict=None, **attr): + """Add an edge between u and v. + + The nodes u and v will be automatically added if they are + not already in the graph. + + Edge attributes can be specified with keywords or by providing + a dictionary with key/value pairs. See examples below. + + Parameters + ---------- + u,v : nodes + Nodes can be, for example, strings or numbers. + Nodes must be hashable (and not None) Python objects. + key : hashable identifier, optional (default=lowest unused integer) + Used to distinguish multiedges between a pair of nodes. + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with the edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + See Also + -------- + add_edges_from : add a collection of edges + + Notes + ----- + To replace/update edge data, use the optional key argument + to identify a unique edge. Otherwise a new edge will be created. + + NetworkX algorithms designed for weighted graphs cannot use + multigraphs directly because it is not clear how to handle + multiedge weights. Convert to Graph using edge attribute + 'weight' to enable weighted graph algorithms. + + Examples + -------- + The following all add the edge e=(1,2) to graph G: + + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> e = (1,2) + >>> G.add_edge(1, 2) # explicit two-node form + >>> G.add_edge(*e) # single edge as tuple of two nodes + >>> G.add_edges_from( [(1,2)] ) # add edges from iterable container + + Associate data to edges using keywords: + + >>> G.add_edge(1, 2, weight=3) + >>> G.add_edge(1, 2, key=0, weight=4) # update data for key=0 + >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7) + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + # add nodes + if u not in self.adj: + self.adj[u] = {} + self.node[u] = {} + if v not in self.adj: + self.adj[v] = {} + self.node[v] = {} + if v in self.adj[u]: + keydict=self.adj[u][v] + if key is None: + # find a unique integer key + # other methods might be better here? + key=len(keydict) + while key in keydict: + key+=1 + datadict=keydict.get(key,{}) + datadict.update(attr_dict) + keydict[key]=datadict + else: + # selfloops work this way without special treatment + if key is None: + key=0 + datadict={} + datadict.update(attr_dict) + keydict={key:datadict} + self.adj[u][v] = keydict + self.adj[v][u] = keydict + + + def add_edges_from(self, ebunch, attr_dict=None, **attr): + """Add all the edges in ebunch. + + Parameters + ---------- + ebunch : container of edges + Each edge given in the container will be added to the + graph. The edges can be: + + - 2-tuples (u,v) or + - 3-tuples (u,v,d) for an edge attribute dict d, or + - 4-tuples (u,v,k,d) for an edge identified by key k + + attr_dict : dictionary, optional (default= no attributes) + Dictionary of edge attributes. Key/value pairs will + update existing data associated with each edge. + attr : keyword arguments, optional + Edge data (or labels or objects) can be assigned using + keyword arguments. + + + See Also + -------- + add_edge : add a single edge + add_weighted_edges_from : convenient way to add weighted edges + + Notes + ----- + Adding the same edge twice has no effect but any edge data + will be updated when each duplicate edge is added. + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_edges_from([(0,1),(1,2)]) # using a list of edge tuples + >>> e = zip(range(0,3),range(1,4)) + >>> G.add_edges_from(e) # Add the path graph 0-1-2-3 + + Associate data to edges + + >>> G.add_edges_from([(1,2),(2,3)], weight=3) + >>> G.add_edges_from([(3,4),(1,4)], label='WN2898') + """ + # set up attribute dict + if attr_dict is None: + attr_dict=attr + else: + try: + attr_dict.update(attr) + except AttributeError: + raise NetworkXError(\ + "The attr_dict argument must be a dictionary.") + # process ebunch + for e in ebunch: + ne=len(e) + if ne==4: + u,v,key,dd = e + elif ne==3: + u,v,dd = e + key=None + elif ne==2: + u,v = e + dd = {} + key=None + else: + raise NetworkXError(\ + "Edge tuple %s must be a 2-tuple, 3-tuple or 4-tuple."%(e,)) + if u in self.adj: + keydict=self.adj[u].get(v,{}) + else: + keydict={} + if key is None: + # find a unique integer key + # other methods might be better here? + key=len(keydict) + while key in keydict: + key+=1 + datadict=keydict.get(key,{}) + datadict.update(attr_dict) + datadict.update(dd) + self.add_edge(u,v,key=key,attr_dict=datadict) + + + def remove_edge(self, u, v, key=None): + """Remove an edge between u and v. + + Parameters + ---------- + u,v: nodes + Remove an edge between nodes u and v. + key : hashable identifier, optional (default=None) + Used to distinguish multiple edges between a pair of nodes. + If None remove a single (abritrary) edge between u and v. + + Raises + ------ + NetworkXError + If there is not an edge between u and v, or + if there is no edge with the specified key. + + See Also + -------- + remove_edges_from : remove a collection of edges + + Examples + -------- + >>> G = nx.MultiGraph() + >>> G.add_path([0,1,2,3]) + >>> G.remove_edge(0,1) + >>> e = (1,2) + >>> G.remove_edge(*e) # unpacks e from an edge tuple + + For multiple edges + + >>> G = nx.MultiGraph() # or MultiDiGraph, etc + >>> G.add_edges_from([(1,2),(1,2),(1,2)]) + >>> G.remove_edge(1,2) # remove a single (arbitrary) edge + + For edges with keys + + >>> G = nx.MultiGraph() # or MultiDiGraph, etc + >>> G.add_edge(1,2,key='first') + >>> G.add_edge(1,2,key='second') + >>> G.remove_edge(1,2,key='second') + + """ + try: + d=self.adj[u][v] + except (KeyError): + raise NetworkXError( + "The edge %s-%s is not in the graph."%(u,v)) + # remove the edge with specified data + if key is None: + d.popitem() + else: + try: + del d[key] + except (KeyError): + raise NetworkXError( + "The edge %s-%s with key %s is not in the graph."%(u,v,key)) + if len(d)==0: + # remove the key entries if last edge + del self.adj[u][v] + if u!=v: # check for selfloop + del self.adj[v][u] + + + def remove_edges_from(self, ebunch): + """Remove all edges specified in ebunch. + + Parameters + ---------- + ebunch: list or container of edge tuples + Each edge given in the list or container will be removed + from the graph. The edges can be: + + - 2-tuples (u,v) All edges between u and v are removed. + - 3-tuples (u,v,key) The edge identified by key is removed. + - 4-tuples (u,v,key,data) where data is ignored. + + See Also + -------- + remove_edge : remove a single edge + + Notes + ----- + Will fail silently if an edge in ebunch is not in the graph. + + Examples + -------- + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> ebunch=[(1,2),(2,3)] + >>> G.remove_edges_from(ebunch) + + Removing multiple copies of edges + + >>> G = nx.MultiGraph() + >>> G.add_edges_from([(1,2),(1,2),(1,2)]) + >>> G.remove_edges_from([(1,2),(1,2)]) + >>> G.edges() + [(1, 2)] + >>> G.remove_edges_from([(1,2),(1,2)]) # silently ignore extra copy + >>> G.edges() # now empty graph + [] + """ + for e in ebunch: + try: + self.remove_edge(*e[:3]) + except NetworkXError: + pass + + + def has_edge(self, u, v, key=None): + """Return True if the graph has an edge between nodes u and v. + + Parameters + ---------- + u,v : nodes + Nodes can be, for example, strings or numbers. + + key : hashable identifier, optional (default=None) + If specified return True only if the edge with + key is found. + + Returns + ------- + edge_ind : bool + True if edge is in the graph, False otherwise. + + Examples + -------- + Can be called either using two nodes u,v, an edge tuple (u,v), + or an edge tuple (u,v,key). + + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> G.has_edge(0,1) # using two nodes + True + >>> e = (0,1) + >>> G.has_edge(*e) # e is a 2-tuple (u,v) + True + >>> G.add_edge(0,1,key='a') + >>> G.has_edge(0,1,key='a') # specify key + True + >>> e=(0,1,'a') + >>> G.has_edge(*e) # e is a 3-tuple (u,v,'a') + True + + The following syntax are equivalent: + + >>> G.has_edge(0,1) + True + >>> 1 in G[0] # though this gives KeyError if 0 not in G + True + + + + """ + try: + if key is None: + return v in self.adj[u] + else: + return key in self.adj[u][v] + except KeyError: + return False + + def edges(self, nbunch=None, data=False, keys=False): + """Return a list of edges. + + Edges are returned as tuples with optional data and keys + in the order (node, neighbor, key, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + Return two tuples (u,v) (False) or three-tuples (u,v,data) (True). + keys : bool, optional (default=False) + Return two tuples (u,v) (False) or three-tuples (u,v,key) (True). + + Returns + -------- + edge_list: list of edge tuples + Edges that are adjacent to any node in nbunch, or a list + of all edges if nbunch is not specified. + + See Also + -------- + edges_iter : return an iterator over the edges + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs this returns the out-edges. + + Examples + -------- + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> G.edges() + [(0, 1), (1, 2), (2, 3)] + >>> G.edges(data=True) # default edge data is {} (empty dictionary) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + >>> G.edges(keys=True) # default keys are integers + [(0, 1, 0), (1, 2, 0), (2, 3, 0)] + >>> G.edges(data=True,keys=True) # default keys are integers + [(0, 1, 0, {}), (1, 2, 0, {}), (2, 3, 0, {})] + >>> G.edges([0,3]) + [(0, 1), (3, 2)] + >>> G.edges(0) + [(0, 1)] + + """ + return list(self.edges_iter(nbunch, data=data,keys=keys)) + + def edges_iter(self, nbunch=None, data=False, keys=False): + """Return an iterator over the edges. + + Edges are returned as tuples with optional data and keys + in the order (node, neighbor, key, data). + + Parameters + ---------- + nbunch : iterable container, optional (default= all nodes) + A container of nodes. The container will be iterated + through once. + data : bool, optional (default=False) + If True, return edge attribute dict with each edge. + keys : bool, optional (default=False) + If True, return edge keys with each edge. + + Returns + ------- + edge_iter : iterator + An iterator of (u,v), (u,v,d) or (u,v,key,d) tuples of edges. + + See Also + -------- + edges : return a list of edges + + Notes + ----- + Nodes in nbunch that are not in the graph will be (quietly) ignored. + For directed graphs this returns the out-edges. + + Examples + -------- + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> [e for e in G.edges_iter()] + [(0, 1), (1, 2), (2, 3)] + >>> list(G.edges_iter(data=True)) # default data is {} (empty dict) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + >>> list(G.edges(keys=True)) # default keys are integers + [(0, 1, 0), (1, 2, 0), (2, 3, 0)] + >>> list(G.edges(data=True,keys=True)) # default keys are integers + [(0, 1, 0, {}), (1, 2, 0, {}), (2, 3, 0, {})] + >>> list(G.edges_iter([0,3])) + [(0, 1), (3, 2)] + >>> list(G.edges_iter(0)) + [(0, 1)] + + """ + seen={} # helper dict to keep track of multiply stored edges + if nbunch is None: + nodes_nbrs = self.adj.items() + else: + nodes_nbrs=((n,self.adj[n]) for n in self.nbunch_iter(nbunch)) + if data: + for n,nbrs in nodes_nbrs: + for nbr,keydict in nbrs.items(): + if nbr not in seen: + for key,data in keydict.items(): + if keys: + yield (n,nbr,key,data) + else: + yield (n,nbr,data) + seen[n]=1 + else: + for n,nbrs in nodes_nbrs: + for nbr,keydict in nbrs.items(): + if nbr not in seen: + for key,data in keydict.items(): + if keys: + yield (n,nbr,key) + else: + yield (n,nbr) + + seen[n] = 1 + del seen + + + def get_edge_data(self, u, v, key=None, default=None): + """Return the attribute dictionary associated with edge (u,v). + + Parameters + ---------- + u,v : nodes + default: any Python object (default=None) + Value to return if the edge (u,v) is not found. + key : hashable identifier, optional (default=None) + Return data only for the edge with specified key. + + Returns + ------- + edge_dict : dictionary + The edge attribute dictionary. + + Notes + ----- + It is faster to use G[u][v][key]. + + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_edge(0,1,key='a',weight=7) + >>> G[0][1]['a'] # key='a' + {'weight': 7} + + Warning: Assigning G[u][v][key] corrupts the graph data structure. + But it is safe to assign attributes to that dictionary, + + >>> G[0][1]['a']['weight'] = 10 + >>> G[0][1]['a']['weight'] + 10 + >>> G[1][0]['a']['weight'] + 10 + + Examples + -------- + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_path([0,1,2,3]) + >>> G.get_edge_data(0,1) + {0: {}} + >>> e = (0,1) + >>> G.get_edge_data(*e) # tuple form + {0: {}} + >>> G.get_edge_data('a','b',default=0) # edge not in graph, return 0 + 0 + """ + try: + if key is None: + return self.adj[u][v] + else: + return self.adj[u][v][key] + except KeyError: + return default + + def degree_iter(self, nbunch=None, weight=None): + """Return an iterator for (node, degree). + + The node degree is the number of edges adjacent to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + nd_iter : an iterator + The iterator returns two-tuples of (node, degree). + + See Also + -------- + degree + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> list(G.degree_iter(0)) # node 0 with degree 1 + [(0, 1)] + >>> list(G.degree_iter([0,1])) + [(0, 1), (1, 2)] + + """ + if nbunch is None: + nodes_nbrs = self.adj.items() + else: + nodes_nbrs=((n,self.adj[n]) for n in self.nbunch_iter(nbunch)) + + if weight is None: + for n,nbrs in nodes_nbrs: + deg = sum([len(data) for data in nbrs.values()]) + yield (n, deg+(n in nbrs and len(nbrs[n]))) + else: + # edge weighted graph - degree is sum of nbr edge weights + for n,nbrs in nodes_nbrs: + deg = sum([d.get(weight,1) + for data in nbrs.values() + for d in data.values()]) + if n in nbrs: + deg += sum([d.get(weight,1) + for key,d in nbrs[n].items()]) + yield (n, deg) + + + def is_multigraph(self): + """Return True if graph is a multigraph, False otherwise.""" + return True + + def is_directed(self): + """Return True if graph is directed, False otherwise.""" + return False + + def to_directed(self): + """Return a directed representation of the graph. + + Returns + ------- + G : MultiDiGraph + A directed graph with the same name, same nodes, and with + each edge (u,v,data) replaced by two directed edges + (u,v,data) and (v,u,data). + + Notes + ----- + This returns a "deepcopy" of the edge, node, and + graph attributes which attempts to completely copy + all of the data and references. + + This is in contrast to the similar D=DiGraph(G) which returns a + shallow copy of the data. + + See the Python copy module for more information on shallow + and deep copies, http://docs.python.org/library/copy.html. + + + Examples + -------- + >>> G = nx.Graph() # or MultiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1), (1, 0)] + + If already directed, return a (deep) copy + + >>> G = nx.DiGraph() # or MultiDiGraph, etc + >>> G.add_path([0,1]) + >>> H = G.to_directed() + >>> H.edges() + [(0, 1)] + """ + from networkx.classes.multidigraph import MultiDiGraph + G=MultiDiGraph() + G.add_nodes_from(self) + G.add_edges_from( (u,v,key,deepcopy(datadict)) + for u,nbrs in self.adjacency_iter() + for v,keydict in nbrs.items() + for key,datadict in keydict.items() ) + G.graph=deepcopy(self.graph) + G.node=deepcopy(self.node) + return G + + + def selfloop_edges(self, data=False, keys=False): + """Return a list of selfloop edges. + + A selfloop edge has the same node at both ends. + + Parameters + ----------- + data : bool, optional (default=False) + Return selfloop edges as two tuples (u,v) (data=False) + or three-tuples (u,v,data) (data=True) + keys : bool, optional (default=False) + If True, return edge keys with each edge. + + Returns + ------- + edgelist : list of edge tuples + A list of all selfloop edges. + + See Also + -------- + nodes_with_selfloops, number_of_selfloops + + Examples + -------- + >>> G = nx.MultiGraph() # or MultiDiGraph + >>> G.add_edge(1,1) + >>> G.add_edge(1,2) + >>> G.selfloop_edges() + [(1, 1)] + >>> G.selfloop_edges(data=True) + [(1, 1, {})] + >>> G.selfloop_edges(keys=True) + [(1, 1, 0)] + >>> G.selfloop_edges(keys=True, data=True) + [(1, 1, 0, {})] + """ + if data: + if keys: + return [ (n,n,k,d) + for n,nbrs in self.adj.items() + if n in nbrs for k,d in nbrs[n].items()] + else: + return [ (n,n,d) + for n,nbrs in self.adj.items() + if n in nbrs for d in nbrs[n].values()] + else: + if keys: + return [ (n,n,k) + for n,nbrs in self.adj.items() + if n in nbrs for k in nbrs[n].keys()] + + else: + return [ (n,n) + for n,nbrs in self.adj.items() + if n in nbrs for d in nbrs[n].values()] + + + def number_of_edges(self, u=None, v=None): + """Return the number of edges between two nodes. + + Parameters + ---------- + u,v : nodes, optional (default=all edges) + If u and v are specified, return the number of edges between + u and v. Otherwise return the total number of all edges. + + Returns + ------- + nedges : int + The number of edges in the graph. If nodes u and v are specified + return the number of edges between those nodes. + + See Also + -------- + size + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> G.number_of_edges() + 3 + >>> G.number_of_edges(0,1) + 1 + >>> e = (0,1) + >>> G.number_of_edges(*e) + 1 + """ + if u is None: return self.size() + try: + edgedata=self.adj[u][v] + except KeyError: + return 0 # no such edge + return len(edgedata) + + + def subgraph(self, nbunch): + """Return the subgraph induced on nodes in nbunch. + + The induced subgraph of the graph contains the nodes in nbunch + and the edges between those nodes. + + Parameters + ---------- + nbunch : list, iterable + A container of nodes which will be iterated through once. + + Returns + ------- + G : Graph + A subgraph of the graph with the same edge attributes. + + Notes + ----- + The graph, edge or node attributes just point to the original graph. + So changes to the node or edge structure will not be reflected in + the original graph while changes to the attributes will. + + To create a subgraph with its own copy of the edge/node attributes use: + nx.Graph(G.subgraph(nbunch)) + + If edge attributes are containers, a deep copy can be obtained using: + G.subgraph(nbunch).copy() + + For an inplace reduction of a graph to a subgraph you can remove nodes: + G.remove_nodes_from([ n in G if n not in set(nbunch)]) + + Examples + -------- + >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc + >>> G.add_path([0,1,2,3]) + >>> H = G.subgraph([0,1,2]) + >>> H.edges() + [(0, 1), (1, 2)] + """ + bunch =self.nbunch_iter(nbunch) + # create new graph and copy subgraph into it + H = self.__class__() + # copy node and attribute dictionaries + for n in bunch: + H.node[n]=self.node[n] + # namespace shortcuts for speed + H_adj=H.adj + self_adj=self.adj + # add nodes and edges (undirected method) + for n in H: + Hnbrs={} + H_adj[n]=Hnbrs + for nbr,edgedict in self_adj[n].items(): + if nbr in H_adj: + # add both representations of edge: n-nbr and nbr-n + # they share the same edgedict + ed=edgedict.copy() + Hnbrs[nbr]=ed + H_adj[nbr][n]=ed + H.graph=self.graph + return H diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/historical_tests.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/historical_tests.py new file mode 100644 index 0000000..5dd398c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/historical_tests.py @@ -0,0 +1,477 @@ +#!/usr/bin/env python +"""Original NetworkX graph tests""" +from nose.tools import * +import networkx +import networkx as nx +from networkx import convert_node_labels_to_integers as cnlti +from networkx.testing import * + +class HistoricalTests(object): + + def setUp(self): + self.null=nx.null_graph() + self.P1=cnlti(nx.path_graph(1),first_label=1) + self.P3=cnlti(nx.path_graph(3),first_label=1) + self.P10=cnlti(nx.path_graph(10),first_label=1) + self.K1=cnlti(nx.complete_graph(1),first_label=1) + self.K3=cnlti(nx.complete_graph(3),first_label=1) + self.K4=cnlti(nx.complete_graph(4),first_label=1) + self.K5=cnlti(nx.complete_graph(5),first_label=1) + self.K10=cnlti(nx.complete_graph(10),first_label=1) + self.G=nx.Graph + + def test_name(self): + G=self.G(name="test") + assert_equal(str(G),'test') + assert_equal(G.name,'test') + H=self.G() + assert_equal(H.name,'') + + # Nodes + + def test_add_remove_node(self): + G=self.G() + G.add_node('A') + assert_true(G.has_node('A')) + G.remove_node('A') + assert_false(G.has_node('A')) + + def test_nonhashable_node(self): + # Test if a non-hashable object is in the Graph. A python dict will + # raise a TypeError, but for a Graph class a simple False should be + # returned (see Graph __contains__). If it cannot be a node then it is + # not a node. + G=self.G() + assert_false(G.has_node(['A'])) + assert_false(G.has_node({'A':1})) + + def test_add_nodes_from(self): + G=self.G() + G.add_nodes_from(list("ABCDEFGHIJKL")) + assert_true(G.has_node("L")) + G.remove_nodes_from(['H','I','J','K','L']) + G.add_nodes_from([1,2,3,4]) + assert_equal(sorted(G.nodes(),key=str), + [1, 2, 3, 4, 'A', 'B', 'C', 'D', 'E', 'F', 'G']) + # test __iter__ + assert_equal(sorted(G,key=str), + [1, 2, 3, 4, 'A', 'B', 'C', 'D', 'E', 'F', 'G']) + + + def test_contains(self): + G=self.G() + G.add_node('A') + assert_true('A' in G) + assert_false([] in G) # never raise a Key or TypeError in this test + assert_false({1:1} in G) + + def test_add_remove(self): + # Test add_node and remove_node acting for various nbunch + G=self.G() + G.add_node('m') + assert_true(G.has_node('m')) + G.add_node('m') # no complaints + assert_raises(nx.NetworkXError,G.remove_node,'j') + G.remove_node('m') + assert_equal(G.nodes(),[]) + + def test_nbunch_is_list(self): + G=self.G() + G.add_nodes_from(list("ABCD")) + G.add_nodes_from(self.P3) # add nbunch of nodes (nbunch=Graph) + assert_equal(sorted(G.nodes(),key=str), + [1, 2, 3, 'A', 'B', 'C', 'D']) + G.remove_nodes_from(self.P3) # remove nbunch of nodes (nbunch=Graph) + assert_equal(sorted(G.nodes(),key=str), + ['A', 'B', 'C', 'D']) + + def test_nbunch_is_set(self): + G=self.G() + nbunch=set("ABCDEFGHIJKL") + G.add_nodes_from(nbunch) + assert_true(G.has_node("L")) + + def test_nbunch_dict(self): + # nbunch is a dict with nodes as keys + G=self.G() + nbunch=set("ABCDEFGHIJKL") + G.add_nodes_from(nbunch) + nbunch={'I':"foo",'J':2,'K':True,'L':"spam"} + G.remove_nodes_from(nbunch) + assert_true(sorted(G.nodes(),key=str), + ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + + def test_nbunch_iterator(self): + G=self.G() + G.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + n_iter=self.P3.nodes_iter() + G.add_nodes_from(n_iter) + assert_equal(sorted(G.nodes(),key=str), + [1, 2, 3, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + n_iter=self.P3.nodes_iter() # rebuild same iterator + G.remove_nodes_from(n_iter) # remove nbunch of nodes (nbunch=iterator) + assert_equal(sorted(G.nodes(),key=str), + ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + + def test_nbunch_graph(self): + G=self.G() + G.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + nbunch=self.K3 + G.add_nodes_from(nbunch) + assert_true(sorted(G.nodes(),key=str), + [1, 2, 3, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + + + + # Edges + + def test_add_edge(self): + G=self.G() + assert_raises(TypeError,G.add_edge,'A') + + G.add_edge('A','B') # testing add_edge() + G.add_edge('A','B') # should fail silently + assert_true(G.has_edge('A','B')) + assert_false(G.has_edge('A','C')) + assert_true(G.has_edge( *('A','B') )) + if G.is_directed(): + assert_false(G.has_edge('B','A')) + else: + # G is undirected, so B->A is an edge + assert_true(G.has_edge('B','A')) + + + G.add_edge('A','C') # test directedness + G.add_edge('C','A') + G.remove_edge('C','A') + if G.is_directed(): + assert_true(G.has_edge('A','C')) + else: + assert_false(G.has_edge('A','C')) + assert_false(G.has_edge('C','A')) + + + def test_self_loop(self): + G=self.G() + G.add_edge('A','A') # test self loops + assert_true(G.has_edge('A','A')) + G.remove_edge('A','A') + G.add_edge('X','X') + assert_true(G.has_node('X')) + G.remove_node('X') + G.add_edge('A','Z') # should add the node silently + assert_true(G.has_node('Z')) + + def test_add_edges_from(self): + G=self.G() + G.add_edges_from([('B','C')]) # test add_edges_from() + assert_true(G.has_edge('B','C')) + if G.is_directed(): + assert_false(G.has_edge('C','B')) + else: + assert_true(G.has_edge('C','B')) # undirected + + G.add_edges_from([('D','F'),('B','D')]) + assert_true(G.has_edge('D','F')) + assert_true(G.has_edge('B','D')) + + if G.is_directed(): + assert_false(G.has_edge('D','B')) + else: + assert_true(G.has_edge('D','B')) # undirected + + def test_add_edges_from2(self): + G=self.G() + # after failing silently, should add 2nd edge + G.add_edges_from([tuple('IJ'),list('KK'),tuple('JK')]) + assert_true(G.has_edge(*('I','J'))) + assert_true(G.has_edge(*('K','K'))) + assert_true(G.has_edge(*('J','K'))) + if G.is_directed(): + assert_false(G.has_edge(*('K','J'))) + else: + assert_true(G.has_edge(*('K','J'))) + + def test_add_edges_from3(self): + G=self.G() + G.add_edges_from(zip(list('ACD'),list('CDE'))) + assert_true(G.has_edge('D','E')) + assert_false(G.has_edge('E','C')) + + def test_remove_edge(self): + G=self.G() + G.add_nodes_from([1, 2, 3, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + + G.add_edges_from(zip(list('MNOP'),list('NOPM'))) + assert_true(G.has_edge('O','P')) + assert_true( G.has_edge('P','M')) + G.remove_node('P') # tests remove_node()'s handling of edges. + assert_false(G.has_edge('P','M')) + assert_raises(TypeError,G.remove_edge,'M') + + G.add_edge('N','M') + assert_true(G.has_edge('M','N')) + G.remove_edge('M','N') + assert_false(G.has_edge('M','N')) + + # self loop fails silently + G.remove_edges_from([list('HI'),list('DF'), + tuple('KK'),tuple('JK')]) + assert_false(G.has_edge('H','I')) + assert_false(G.has_edge('J','K')) + G.remove_edges_from([list('IJ'),list('KK'),list('JK')]) + assert_false(G.has_edge('I','J')) + G.remove_nodes_from(set('ZEFHIMNO')) + G.add_edge('J','K') + + + def test_edges_nbunch(self): + # Test G.edges(nbunch) with various forms of nbunch + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + # node not in nbunch should be quietly ignored + assert_raises(nx.NetworkXError,G.edges,6) + assert_equals(G.edges('Z'),[]) # iterable non-node + # nbunch can be an empty list + assert_equals(G.edges([]),[]) + if G.is_directed(): + elist=[('A', 'B'), ('A', 'C'), ('B', 'D')] + else: + elist=[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')] + # nbunch can be a list + assert_edges_equal(G.edges(['A','B']),elist) + # nbunch can be a set + assert_edges_equal(G.edges(set(['A','B'])),elist) + # nbunch can be a graph + G1=self.G() + G1.add_nodes_from('AB') + assert_edges_equal(G.edges(G1),elist) + # nbunch can be a dict with nodes as keys + ndict={'A': "thing1", 'B': "thing2"} + assert_edges_equal(G.edges(ndict),elist) + # nbunch can be a single node + assert_edges_equal(G.edges('A'), [('A', 'B'), ('A', 'C')]) + + assert_edges_equal(G.nodes_iter(), ['A', 'B', 'C', 'D']) + + def test_edges_iter_nbunch(self): + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + # Test G.edges_iter(nbunch) with various forms of nbunch + # node not in nbunch should be quietly ignored + assert_equals(list(G.edges_iter('Z')),[]) + # nbunch can be an empty list + assert_equals(sorted(G.edges_iter([])),[]) + if G.is_directed(): + elist=[('A', 'B'), ('A', 'C'), ('B', 'D')] + else: + elist=[('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')] + # nbunch can be a list + assert_edges_equal(G.edges_iter(['A','B']),elist) + # nbunch can be a set + assert_edges_equal(G.edges_iter(set(['A','B'])),elist) + # nbunch can be a graph + G1=self.G() + G1.add_nodes_from(['A','B']) + assert_edges_equal(G.edges_iter(G1),elist) + # nbunch can be a dict with nodes as keys + ndict={'A': "thing1", 'B': "thing2"} + assert_edges_equal(G.edges_iter(ndict),elist) + # nbunch can be a single node + assert_edges_equal(G.edges_iter('A'), [('A', 'B'), ('A', 'C')]) + + # nbunch can be nothing (whole graph) + assert_edges_equal(G.edges_iter(), [('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + + + def test_degree(self): + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + assert_equal(G.degree('A'),2) + + # degree of single node in iterable container must return dict + assert_equal(list(G.degree(['A']).values()),[2]) + assert_equal(G.degree(['A']),{'A': 2}) + assert_equal(sorted(G.degree(['A','B']).values()),[2, 3]) + assert_equal(G.degree(['A','B']),{'A': 2, 'B': 3}) + assert_equal(sorted(G.degree().values()),[2, 2, 3, 3]) + assert_equal(sorted([v for k,v in G.degree_iter()]), + [2, 2, 3, 3]) + + def test_degree2(self): + H=self.G() + H.add_edges_from([(1,24),(1,2)]) + assert_equal(sorted(H.degree([1,24]).values()),[1, 2]) + + def test_degree_graph(self): + P3=nx.path_graph(3) + P5=nx.path_graph(5) + # silently ignore nodes not in P3 + assert_equal(P3.degree(['A','B']),{}) + # nbunch can be a graph + assert_equal(sorted(P5.degree(P3).values()),[1, 2, 2]) + # nbunch can be a graph thats way to big + assert_equal(sorted(P3.degree(P5).values()),[1, 1, 2]) + assert_equal(P5.degree([]),{}) + assert_equal(list(P5.degree_iter([])),[]) + assert_equal(dict(P5.degree_iter([])),{}) + + def test_null(self): + null=nx.null_graph() + assert_equal(null.degree(),{}) + assert_equal(list(null.degree_iter()),[]) + assert_equal(dict(null.degree_iter()),{}) + + def test_order_size(self): + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + assert_equal(G.order(),4) + assert_equal(G.size(),5) + assert_equal(G.number_of_edges(),5) + assert_equal(G.number_of_edges('A','B'),1) + assert_equal(G.number_of_edges('A','D'),0) + + def test_copy(self): + G=self.G() + H=G.copy() # copy + assert_equal(H.adj,G.adj) + assert_equal(H.name,G.name) + assert_not_equal(H,G) + + def test_subgraph(self): + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + SG=G.subgraph(['A','B','D']) + assert_nodes_equal(SG.nodes(),['A', 'B', 'D']) + assert_edges_equal(SG.edges(),[('A', 'B'), ('B', 'D')]) + + def test_to_directed(self): + G=self.G() + if not G.is_directed(): + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + + DG=G.to_directed() + assert_not_equal(DG,G) # directed copy or copy + + assert_true(DG.is_directed()) + assert_equal(DG.name,G.name) + assert_equal(DG.adj,G.adj) + assert_equal(sorted(DG.out_edges(list('AB'))), + [('A', 'B'), ('A', 'C'), ('B', 'A'), + ('B', 'C'), ('B', 'D')]) + DG.remove_edge('A','B') + assert_true(DG.has_edge('B','A')) # this removes B-A but not A-B + assert_false(DG.has_edge('A','B')) + + def test_to_undirected(self): + G=self.G() + if G.is_directed(): + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + UG=G.to_undirected() # to_undirected + assert_not_equal(UG,G) + assert_false(UG.is_directed()) + assert_true(G.is_directed()) + assert_equal(UG.name,G.name) + assert_not_equal(UG.adj,G.adj) + assert_equal(sorted(UG.edges(list('AB'))), + [('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]) + assert_equal(sorted(UG.edges(['A','B'])), + [('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')]) + UG.remove_edge('A','B') + assert_false(UG.has_edge('B','A')) + assert_false( UG.has_edge('A','B')) + + + + def test_neighbors(self): + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + G.add_nodes_from('GJK') + assert_equal(sorted(G['A']),['B', 'C']) + assert_equal(sorted(G.neighbors('A')),['B', 'C']) + assert_equal(sorted(G.neighbors_iter('A')),['B', 'C']) + assert_equal(sorted(G.neighbors('G')),[]) + assert_raises(nx.NetworkXError,G.neighbors,'j') + + def test_iterators(self): + G=self.G() + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('C', 'B'), ('C', 'D')]) + G.add_nodes_from('GJK') + assert_equal(sorted(G.nodes_iter()), + ['A', 'B', 'C', 'D', 'G', 'J', 'K']) + assert_edges_equal(G.edges_iter(), + [('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'B'), ('C', 'D')]) + + assert_equal(sorted([v for k,v in G.degree_iter()]), + [0, 0, 0, 2, 2, 3, 3]) + assert_equal(sorted(G.degree_iter(),key=str), + [('A', 2), ('B', 3), ('C', 3), ('D', 2), + ('G', 0), ('J', 0), ('K', 0)]) + assert_equal(sorted(G.neighbors_iter('A')),['B', 'C']) + assert_raises(nx.NetworkXError,G.neighbors_iter,'X') + G.clear() + assert_equal(nx.number_of_nodes(G),0) + assert_equal(nx.number_of_edges(G),0) + + + def test_null_subgraph(self): + # Subgraph of a null graph is a null graph + nullgraph=nx.null_graph() + G=nx.null_graph() + H=G.subgraph([]) + assert_true(nx.is_isomorphic(H,nullgraph)) + + def test_empty_subgraph(self): + # Subgraph of an empty graph is an empty graph. test 1 + nullgraph=nx.null_graph() + E5=nx.empty_graph(5) + E10=nx.empty_graph(10) + H=E10.subgraph([]) + assert_true(nx.is_isomorphic(H,nullgraph)) + H=E10.subgraph([1,2,3,4,5]) + assert_true(nx.is_isomorphic(H,E5)) + + def test_complete_subgraph(self): + # Subgraph of a complete graph is a complete graph + K1=nx.complete_graph(1) + K3=nx.complete_graph(3) + K5=nx.complete_graph(5) + H=K5.subgraph([1,2,3]) + assert_true(nx.is_isomorphic(H,K3)) + + def test_subgraph_nbunch(self): + nullgraph=nx.null_graph() + K1=nx.complete_graph(1) + K3=nx.complete_graph(3) + K5=nx.complete_graph(5) + # Test G.subgraph(nbunch), where nbunch is a single node + H=K5.subgraph(1) + assert_true(nx.is_isomorphic(H,K1)) + # Test G.subgraph(nbunch), where nbunch is a set + H=K5.subgraph(set([1])) + assert_true(nx.is_isomorphic(H,K1)) + # Test G.subgraph(nbunch), where nbunch is an iterator + H=K5.subgraph(iter(K3)) + assert_true(nx.is_isomorphic(H,K3)) + # Test G.subgraph(nbunch), where nbunch is another graph + H=K5.subgraph(K3) + assert_true(nx.is_isomorphic(H,K3)) + H=K5.subgraph([9]) + assert_true(nx.is_isomorphic(H,nullgraph)) + + def test_node_tuple_error(self): + H=self.G() + # Test error handling of tuple as a node + assert_raises(nx.NetworkXError,H.remove_node,(1,2)) + H.remove_nodes_from([(1,2)]) # no error + assert_raises(nx.NetworkXError,H.neighbors,(1,2)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph.py new file mode 100644 index 0000000..9ac3d3e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx +from test_graph import BaseGraphTester, BaseAttrGraphTester, TestGraph + +class BaseDiGraphTester(BaseGraphTester): + def test_has_successor(self): + G=self.K3 + assert_equal(G.has_successor(0,1),True) + assert_equal(G.has_successor(0,-1),False) + + def test_successors(self): + G=self.K3 + assert_equal(sorted(G.successors(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.successors,-1) + + def test_successors_iter(self): + G=self.K3 + assert_equal(sorted(G.successors_iter(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.successors_iter,-1) + + def test_has_predecessor(self): + G=self.K3 + assert_equal(G.has_predecessor(0,1),True) + assert_equal(G.has_predecessor(0,-1),False) + + def test_predecessors(self): + G=self.K3 + assert_equal(sorted(G.predecessors(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.predecessors,-1) + + def test_predecessors_iter(self): + G=self.K3 + assert_equal(sorted(G.predecessors_iter(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.predecessors_iter,-1) + + def test_edges(self): + G=self.K3 + assert_equal(sorted(G.edges()),[(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.edges(0)),[(0,1),(0,2)]) + assert_raises((KeyError,networkx.NetworkXError), G.edges,-1) + + def test_edges_iter(self): + G=self.K3 + assert_equal(sorted(G.edges_iter()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.edges_iter(0)),[(0,1),(0,2)]) + + def test_edges_data(self): + G=self.K3 + assert_equal(sorted(G.edges(data=True)), + [(0,1,{}),(0,2,{}),(1,0,{}),(1,2,{}),(2,0,{}),(2,1,{})]) + assert_equal(sorted(G.edges(0,data=True)),[(0,1,{}),(0,2,{})]) + assert_raises((KeyError,networkx.NetworkXError), G.edges,-1) + + def test_out_edges(self): + G=self.K3 + assert_equal(sorted(G.out_edges()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.out_edges(0)),[(0,1),(0,2)]) + assert_raises((KeyError,networkx.NetworkXError), G.out_edges,-1) + + def test_out_edges_iter(self): + G=self.K3 + assert_equal(sorted(G.out_edges_iter()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.edges_iter(0)),[(0,1),(0,2)]) + + def test_out_edges_dir(self): + G=self.P3 + assert_equal(sorted(G.out_edges()),[(0, 1), (1, 2)]) + assert_equal(sorted(G.out_edges(0)),[(0, 1)]) + assert_equal(sorted(G.out_edges(2)),[]) + + def test_out_edges_iter_dir(self): + G=self.P3 + assert_equal(sorted(G.out_edges_iter()),[(0, 1), (1, 2)]) + assert_equal(sorted(G.out_edges_iter(0)),[(0, 1)]) + assert_equal(sorted(G.out_edges_iter(2)),[]) + + def test_in_edges_dir(self): + G=self.P3 + assert_equal(sorted(G.in_edges()),[(0, 1), (1, 2)]) + assert_equal(sorted(G.in_edges(0)),[]) + assert_equal(sorted(G.in_edges(2)),[(1,2)]) + + def test_in_edges_iter_dir(self): + G=self.P3 + assert_equal(sorted(G.in_edges_iter()),[(0, 1), (1, 2)]) + assert_equal(sorted(G.in_edges_iter(0)),[]) + assert_equal(sorted(G.in_edges_iter(2)),[(1,2)]) + + def test_degree(self): + G=self.K3 + assert_equal(list(G.degree().values()),[4,4,4]) + assert_equal(G.degree(),{0:4,1:4,2:4}) + assert_equal(G.degree(0),4) + assert_equal(G.degree([0]),{0:4}) + assert_raises((KeyError,networkx.NetworkXError), G.degree,-1) + + def test_degree_iter(self): + G=self.K3 + assert_equal(list(G.degree_iter()),[(0,4),(1,4),(2,4)]) + assert_equal(dict(G.degree_iter()),{0:4,1:4,2:4}) + assert_equal(list(G.degree_iter(0)),[(0,4)]) + + def test_in_degree(self): + G=self.K3 + assert_equal(list(G.in_degree().values()),[2,2,2]) + assert_equal(G.in_degree(),{0:2,1:2,2:2}) + assert_equal(G.in_degree(0),2) + assert_equal(G.in_degree([0]),{0:2}) + assert_raises((KeyError,networkx.NetworkXError), G.in_degree,-1) + + def test_in_degree_iter(self): + G=self.K3 + assert_equal(list(G.in_degree_iter()),[(0,2),(1,2),(2,2)]) + assert_equal(dict(G.in_degree_iter()),{0:2,1:2,2:2}) + assert_equal(list(G.in_degree_iter(0)),[(0,2)]) + + def test_in_degree_iter_weighted(self): + G=self.K3 + G.add_edge(0,1,weight=0.3,other=1.2) + assert_equal(list(G.in_degree_iter(weight='weight')),[(0,2),(1,1.3),(2,2)]) + assert_equal(dict(G.in_degree_iter(weight='weight')),{0:2,1:1.3,2:2}) + assert_equal(list(G.in_degree_iter(1,weight='weight')),[(1,1.3)]) + assert_equal(list(G.in_degree_iter(weight='other')),[(0,2),(1,2.2),(2,2)]) + assert_equal(dict(G.in_degree_iter(weight='other')),{0:2,1:2.2,2:2}) + assert_equal(list(G.in_degree_iter(1,weight='other')),[(1,2.2)]) + + def test_out_degree(self): + G=self.K3 + assert_equal(list(G.out_degree().values()),[2,2,2]) + assert_equal(G.out_degree(),{0:2,1:2,2:2}) + assert_equal(G.out_degree(0),2) + assert_equal(G.out_degree([0]),{0:2}) + assert_raises((KeyError,networkx.NetworkXError), G.out_degree,-1) + + def test_out_degree_iter_weighted(self): + G=self.K3 + G.add_edge(0,1,weight=0.3,other=1.2) + assert_equal(list(G.out_degree_iter(weight='weight')),[(0,1.3),(1,2),(2,2)]) + assert_equal(dict(G.out_degree_iter(weight='weight')),{0:1.3,1:2,2:2}) + assert_equal(list(G.out_degree_iter(0,weight='weight')),[(0,1.3)]) + assert_equal(list(G.out_degree_iter(weight='other')),[(0,2.2),(1,2),(2,2)]) + assert_equal(dict(G.out_degree_iter(weight='other')),{0:2.2,1:2,2:2}) + assert_equal(list(G.out_degree_iter(0,weight='other')),[(0,2.2)]) + + def test_out_degree_iter(self): + G=self.K3 + assert_equal(list(G.out_degree_iter()),[(0,2),(1,2),(2,2)]) + assert_equal(dict(G.out_degree_iter()),{0:2,1:2,2:2}) + assert_equal(list(G.out_degree_iter(0)),[(0,2)]) + + def test_size(self): + G=self.K3 + assert_equal(G.size(),6) + assert_equal(G.number_of_edges(),6) + + def test_to_undirected_reciprocal(self): + G=self.Graph() + G.add_edge(1,2) + assert_true(G.to_undirected().has_edge(1,2)) + assert_false(G.to_undirected(reciprocal=True).has_edge(1,2)) + G.add_edge(2,1) + assert_true(G.to_undirected(reciprocal=True).has_edge(1,2)) + + def test_reverse_copy(self): + G=networkx.DiGraph([(0,1),(1,2)]) + R=G.reverse() + assert_equal(sorted(R.edges()),[(1,0),(2,1)]) + R.remove_edge(1,0) + assert_equal(sorted(R.edges()),[(2,1)]) + assert_equal(sorted(G.edges()),[(0,1),(1,2)]) + + def test_reverse_nocopy(self): + G=networkx.DiGraph([(0,1),(1,2)]) + R=G.reverse(copy=False) + assert_equal(sorted(R.edges()),[(1,0),(2,1)]) + R.remove_edge(1,0) + assert_equal(sorted(R.edges()),[(2,1)]) + assert_equal(sorted(G.edges()),[(2,1)]) + +class BaseAttrDiGraphTester(BaseDiGraphTester,BaseAttrGraphTester): + pass + + +class TestDiGraph(BaseAttrDiGraphTester,TestGraph): + """Tests specific to dict-of-dict-of-dict digraph data structure""" + def setUp(self): + self.Graph=networkx.DiGraph + # build dict-of-dict-of-dict K3 + ed1,ed2,ed3,ed4,ed5,ed6 = ({},{},{},{},{},{}) + self.k3adj={0: {1: ed1, 2: ed2}, 1: {0: ed3, 2: ed4}, 2: {0: ed5, 1:ed6}} + self.k3edges=[(0, 1), (0, 2), (1, 2)] + self.k3nodes=[0, 1, 2] + self.K3=self.Graph() + self.K3.adj = self.K3.succ = self.K3.edge = self.k3adj + self.K3.pred={0: {1: ed3, 2: ed5}, 1: {0: ed1, 2: ed6}, 2: {0: ed2, 1:ed4}} + + ed1,ed2 = ({},{}) + self.P3=self.Graph() + self.P3.adj={0: {1: ed1}, 1: {2: ed2}, 2: {}} + self.P3.succ=self.P3.adj + self.P3.pred={0: {}, 1: {0: ed1}, 2: {1: ed2}} + self.K3.node={} + self.K3.node[0]={} + self.K3.node[1]={} + self.K3.node[2]={} + self.P3.node={} + self.P3.node[0]={} + self.P3.node[1]={} + self.P3.node[2]={} + + def test_data_input(self): + G=self.Graph(data={1:[2],2:[1]}, name="test") + assert_equal(G.name,"test") + assert_equal(sorted(G.adj.items()),[(1, {2: {}}), (2, {1: {}})]) + assert_equal(sorted(G.succ.items()),[(1, {2: {}}), (2, {1: {}})]) + assert_equal(sorted(G.pred.items()),[(1, {2: {}}), (2, {1: {}})]) + + def test_add_edge(self): + G=self.Graph() + G.add_edge(0,1) + assert_equal(G.adj,{0: {1: {}}, 1: {}}) + assert_equal(G.succ,{0: {1: {}}, 1: {}}) + assert_equal(G.pred,{0: {}, 1: {0:{}}}) + G=self.Graph() + G.add_edge(*(0,1)) + assert_equal(G.adj,{0: {1: {}}, 1: {}}) + assert_equal(G.succ,{0: {1: {}}, 1: {}}) + assert_equal(G.pred,{0: {}, 1: {0:{}}}) + + def test_add_edges_from(self): + G=self.Graph() + G.add_edges_from([(0,1),(0,2,{'data':3})],data=2) + assert_equal(G.adj,{0: {1: {'data':2}, 2: {'data':3}}, 1: {}, 2: {}}) + assert_equal(G.succ,{0: {1: {'data':2}, 2: {'data':3}}, 1: {}, 2: {}}) + assert_equal(G.pred,{0: {}, 1: {0: {'data':2}}, 2: {0: {'data':3}}}) + + assert_raises(networkx.NetworkXError, G.add_edges_from,[(0,)]) # too few in tuple + assert_raises(networkx.NetworkXError, G.add_edges_from,[(0,1,2,3)]) # too many in tuple + assert_raises(TypeError, G.add_edges_from,[0]) # not a tuple + + def test_remove_edge(self): + G=self.K3 + G.remove_edge(0,1) + assert_equal(G.succ,{0:{2:{}},1:{0:{},2:{}},2:{0:{},1:{}}}) + assert_equal(G.pred,{0:{1:{}, 2:{}}, 1:{2:{}}, 2:{0:{},1:{}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,-1,0) + + def test_remove_edges_from(self): + G=self.K3 + G.remove_edges_from([(0,1)]) + assert_equal(G.succ,{0:{2:{}},1:{0:{},2:{}},2:{0:{},1:{}}}) + assert_equal(G.pred,{0:{1:{}, 2:{}}, 1:{2:{}}, 2:{0:{},1: {}}}) + G.remove_edges_from([(0,0)]) # silent fail diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph_historical.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph_historical.py new file mode 100644 index 0000000..6bf295c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_digraph_historical.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +"""Original NetworkX graph tests""" +from nose.tools import * +import networkx +import networkx as nx + +from historical_tests import HistoricalTests + +class TestDiGraphHistorical(HistoricalTests): + + def setUp(self): + HistoricalTests.setUp(self) + self.G=nx.DiGraph + + + def test_in_degree(self): + G=self.G() + G.add_nodes_from('GJK') + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('B', 'C'), ('C', 'D')]) + + assert_equal(sorted(G.in_degree().values()),[0, 0, 0, 0, 1, 2, 2]) + assert_equal(G.in_degree(), + {'A': 0, 'C': 2, 'B': 1, 'D': 2, 'G': 0, 'K': 0, 'J': 0}) + assert_equal(sorted([v for k,v in G.in_degree_iter()]), + [0, 0, 0, 0, 1, 2, 2]) + assert_equal(dict(G.in_degree_iter()), + {'A': 0, 'C': 2, 'B': 1, 'D': 2, 'G': 0, 'K': 0, 'J': 0}) + + + def test_out_degree(self): + G=self.G() + G.add_nodes_from('GJK') + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('B', 'C'), ('C', 'D')]) + assert_equal(sorted(G.out_degree().values()),[0, 0, 0, 0, 1, 2, 2]) + assert_equal(G.out_degree(), + {'A': 2, 'C': 1, 'B': 2, 'D': 0, 'G': 0, 'K': 0, 'J': 0}) + assert_equal(sorted([v for k,v in G.in_degree_iter()]), + [0, 0, 0, 0, 1, 2, 2]) + assert_equal(dict(G.out_degree_iter()), + {'A': 2, 'C': 1, 'B': 2, 'D': 0, 'G': 0, 'K': 0, 'J': 0}) + + + def test_degree_digraph(self): + H=nx.DiGraph() + H.add_edges_from([(1,24),(1,2)]) + assert_equal(sorted(H.in_degree([1,24]).values()),[0, 1]) + assert_equal(sorted(H.out_degree([1,24]).values()),[0, 2]) + assert_equal(sorted(H.degree([1,24]).values()),[1, 2]) + + + def test_neighbors(self): + G=self.G() + G.add_nodes_from('GJK') + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('B', 'C'), ('C', 'D')]) + + assert_equal(sorted(G.neighbors('C')),['D']) + assert_equal(sorted(G['C']),['D']) + assert_equal(sorted(G.neighbors('A')),['B', 'C']) + assert_equal(sorted(G.neighbors_iter('A')),['B', 'C']) + assert_equal(sorted(G.neighbors_iter('C')),['D']) + assert_equal(sorted(G.neighbors('A')),['B', 'C']) + assert_raises(nx.NetworkXError,G.neighbors,'j') + assert_raises(nx.NetworkXError,G.neighbors_iter,'j') + + def test_successors(self): + G=self.G() + G.add_nodes_from('GJK') + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('B', 'C'), ('C', 'D')]) + assert_equal(sorted(G.successors('A')),['B', 'C']) + assert_equal(sorted(G.successors_iter('A')),['B', 'C']) + assert_equal(sorted(G.successors('G')),[]) + assert_equal(sorted(G.successors('D')),[]) + assert_equal(sorted(G.successors_iter('G')),[]) + assert_raises(nx.NetworkXError,G.successors,'j') + assert_raises(nx.NetworkXError,G.successors_iter,'j') + + + def test_predecessors(self): + G=self.G() + G.add_nodes_from('GJK') + G.add_edges_from([('A', 'B'), ('A', 'C'), ('B', 'D'), + ('B', 'C'), ('C', 'D')]) + assert_equal(sorted(G.predecessors('C')),['A', 'B']) + assert_equal(sorted(G.predecessors_iter('C')),['A', 'B']) + assert_equal(sorted(G.predecessors('G')),[]) + assert_equal(sorted(G.predecessors('A')),[]) + assert_equal(sorted(G.predecessors_iter('G')),[]) + assert_equal(sorted(G.predecessors_iter('A')),[]) + assert_equal(sorted(G.successors_iter('D')),[]) + + assert_raises(nx.NetworkXError,G.predecessors,'j') + assert_raises(nx.NetworkXError,G.predecessors,'j') + + + + def test_reverse(self): + G=nx.complete_graph(10) + H=G.to_directed() + HR=H.reverse() + assert_true(nx.is_isomorphic(H,HR)) + assert_equal(sorted(H.edges()),sorted(HR.edges())) + + def test_reverse2(self): + H=nx.DiGraph() + foo=[H.add_edge(u,u+1) for u in range(0,5)] + HR=H.reverse() + for u in range(0,5): + assert_true(HR.has_edge(u+1,u)) + + def test_reverse3(self): + H=nx.DiGraph() + H.add_nodes_from([1,2,3,4]) + HR=H.reverse() + assert_equal(sorted(HR.nodes()),[1, 2, 3, 4]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_function.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_function.py new file mode 100644 index 0000000..2c12178 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_function.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +import random +from nose.tools import * +import networkx +import networkx as nx + +class TestFunction(object): + def setUp(self): + self.G=networkx.Graph({0:[1,2,3], 1:[1,2,0], 4:[]}, name='Test') + self.Gdegree={0:3, 1:2, 2:2, 3:1, 4:0} + self.Gnodes=list(range(5)) + self.Gedges=[(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)] + self.DG=networkx.DiGraph({0:[1,2,3], 1:[1,2,0], 4:[]}) + self.DGin_degree={0:1, 1:2, 2:2, 3:1, 4:0} + self.DGout_degree={0:3, 1:3, 2:0, 3:0, 4:0} + self.DGnodes=list(range(5)) + self.DGedges=[(0,1),(0,2),(0,3),(1,0),(1,1),(1,2)] + + def test_nodes(self): + assert_equal(self.G.nodes(),networkx.nodes(self.G)) + assert_equal(self.DG.nodes(),networkx.nodes(self.DG)) + def test_edges(self): + assert_equal(self.G.edges(),networkx.edges(self.G)) + assert_equal(self.DG.edges(),networkx.edges(self.DG)) + assert_equal(self.G.edges(nbunch=[0,1,3]),networkx.edges(self.G,nbunch=[0,1,3])) + assert_equal(self.DG.edges(nbunch=[0,1,3]),networkx.edges(self.DG,nbunch=[0,1,3])) + def test_nodes_iter(self): + assert_equal(list(self.G.nodes_iter()),list(networkx.nodes_iter(self.G))) + assert_equal(list(self.DG.nodes_iter()),list(networkx.nodes_iter(self.DG))) + def test_edges_iter(self): + assert_equal(list(self.G.edges_iter()),list(networkx.edges_iter(self.G))) + assert_equal(list(self.DG.edges_iter()),list(networkx.edges_iter(self.DG))) + assert_equal(list(self.G.edges_iter(nbunch=[0,1,3])),list(networkx.edges_iter(self.G,nbunch=[0,1,3]))) + assert_equal(list(self.DG.edges_iter(nbunch=[0,1,3])),list(networkx.edges_iter(self.DG,nbunch=[0,1,3]))) + def test_degree(self): + assert_equal(self.G.degree(),networkx.degree(self.G)) + assert_equal(self.DG.degree(),networkx.degree(self.DG)) + assert_equal(self.G.degree(nbunch=[0,1]),networkx.degree(self.G,nbunch=[0,1])) + assert_equal(self.DG.degree(nbunch=[0,1]),networkx.degree(self.DG,nbunch=[0,1])) + assert_equal(self.G.degree(weight='weight'),networkx.degree(self.G,weight='weight')) + assert_equal(self.DG.degree(weight='weight'),networkx.degree(self.DG,weight='weight')) + def test_neighbors(self): + assert_equal(self.G.neighbors(1),networkx.neighbors(self.G,1)) + assert_equal(self.DG.neighbors(1),networkx.neighbors(self.DG,1)) + def test_number_of_nodes(self): + assert_equal(self.G.number_of_nodes(),networkx.number_of_nodes(self.G)) + assert_equal(self.DG.number_of_nodes(),networkx.number_of_nodes(self.DG)) + def test_number_of_edges(self): + assert_equal(self.G.number_of_edges(),networkx.number_of_edges(self.G)) + assert_equal(self.DG.number_of_edges(),networkx.number_of_edges(self.DG)) + def test_is_directed(self): + assert_equal(self.G.is_directed(),networkx.is_directed(self.G)) + assert_equal(self.DG.is_directed(),networkx.is_directed(self.DG)) + def test_subgraph(self): + assert_equal(self.G.subgraph([0,1,2,4]).adj,networkx.subgraph(self.G,[0,1,2,4]).adj) + assert_equal(self.DG.subgraph([0,1,2,4]).adj,networkx.subgraph(self.DG,[0,1,2,4]).adj) + + def test_create_empty_copy(self): + G=networkx.create_empty_copy(self.G, with_nodes=False) + assert_equal(G.nodes(),[]) + assert_equal(G.graph,{}) + assert_equal(G.node,{}) + assert_equal(G.edge,{}) + G=networkx.create_empty_copy(self.G) + assert_equal(G.nodes(),self.G.nodes()) + assert_equal(G.graph,{}) + assert_equal(G.node,{}.fromkeys(self.G.nodes(),{})) + assert_equal(G.edge,{}.fromkeys(self.G.nodes(),{})) + + def test_degree_histogram(self): + assert_equal(networkx.degree_histogram(self.G), [1,1,1,1,1]) + + def test_density(self): + assert_equal(networkx.density(self.G), 0.5) + assert_equal(networkx.density(self.DG), 0.3) + G=networkx.Graph() + G.add_node(1) + assert_equal(networkx.density(G), 0.0) + + def test_density_selfloop(self): + G = nx.Graph() + G.add_edge(1,1) + assert_equal(networkx.density(G), 0.0) + G.add_edge(1,2) + assert_equal(networkx.density(G), 2.0) + + def test_freeze(self): + G=networkx.freeze(self.G) + assert_equal(G.frozen,True) + assert_raises(networkx.NetworkXError, G.add_node, 1) + assert_raises(networkx.NetworkXError, G.add_nodes_from, [1]) + assert_raises(networkx.NetworkXError, G.remove_node, 1) + assert_raises(networkx.NetworkXError, G.remove_nodes_from, [1]) + assert_raises(networkx.NetworkXError, G.add_edge, 1,2) + assert_raises(networkx.NetworkXError, G.add_edges_from, [(1,2)]) + assert_raises(networkx.NetworkXError, G.remove_edge, 1,2) + assert_raises(networkx.NetworkXError, G.remove_edges_from, [(1,2)]) + assert_raises(networkx.NetworkXError, G.clear) + + def test_is_frozen(self): + assert_equal(networkx.is_frozen(self.G), False) + G=networkx.freeze(self.G) + assert_equal(G.frozen, networkx.is_frozen(self.G)) + assert_equal(G.frozen,True) + + def test_info(self): + G=networkx.path_graph(5) + info=networkx.info(G) + expected_graph_info='\n'.join(['Name: path_graph(5)', + 'Type: Graph', + 'Number of nodes: 5', + 'Number of edges: 4', + 'Average degree: 1.6000']) + assert_equal(info,expected_graph_info) + + info=networkx.info(G,n=1) + expected_node_info='\n'.join( + ['Node 1 has the following properties:', + 'Degree: 2', + 'Neighbors: 0 2']) + assert_equal(info,expected_node_info) + + def test_info_digraph(self): + G=networkx.DiGraph(name='path_graph(5)') + G.add_path([0,1,2,3,4]) + info=networkx.info(G) + expected_graph_info='\n'.join(['Name: path_graph(5)', + 'Type: DiGraph', + 'Number of nodes: 5', + 'Number of edges: 4', + 'Average in degree: 0.8000', + 'Average out degree: 0.8000']) + assert_equal(info,expected_graph_info) + + info=networkx.info(G,n=1) + expected_node_info='\n'.join( + ['Node 1 has the following properties:', + 'Degree: 2', + 'Neighbors: 2']) + assert_equal(info,expected_node_info) + + assert_raises(networkx.NetworkXError,networkx.info,G,n=-1) + + def test_neighbors(self): + graph = nx.complete_graph(100) + pop = random.sample(graph.nodes(), 1) + nbors = list(nx.neighbors(graph, pop[0])) + # should be all the other vertices in the graph + assert_equal(len(nbors), len(graph) - 1) + + graph = nx.path_graph(100) + node = random.sample(graph.nodes(), 1)[0] + nbors = list(nx.neighbors(graph, node)) + # should be all the other vertices in the graph + if node != 0 and node != 99: + assert_equal(len(nbors), 2) + else: + assert_equal(len(nbors), 1) + + # create a star graph with 99 outer nodes + graph = nx.star_graph(99) + nbors = list(nx.neighbors(graph, 0)) + assert_equal(len(nbors), 99) + + def test_non_neighbors(self): + graph = nx.complete_graph(100) + pop = random.sample(graph.nodes(), 1) + nbors = list(nx.non_neighbors(graph, pop[0])) + # should be all the other vertices in the graph + assert_equal(len(nbors), 0) + + graph = nx.path_graph(100) + node = random.sample(graph.nodes(), 1)[0] + nbors = list(nx.non_neighbors(graph, node)) + # should be all the other vertices in the graph + if node != 0 and node != 99: + assert_equal(len(nbors), 97) + else: + assert_equal(len(nbors), 98) + + # create a star graph with 99 outer nodes + graph = nx.star_graph(99) + nbors = list(nx.non_neighbors(graph, 0)) + assert_equal(len(nbors), 0) + + # disconnected graph + graph = nx.Graph() + graph.add_nodes_from(range(10)) + nbors = list(nx.non_neighbors(graph, 0)) + assert_equal(len(nbors), 9) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph.py new file mode 100644 index 0000000..077dead --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph.py @@ -0,0 +1,602 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx + +class BaseGraphTester(object): + """ Tests for data-structure independent graph class features.""" + def test_contains(self): + G=self.K3 + assert(1 in G ) + assert(4 not in G ) + assert('b' not in G ) + assert([] not in G ) # no exception for nonhashable + assert({1:1} not in G) # no exception for nonhashable + + def test_order(self): + G=self.K3 + assert_equal(len(G),3) + assert_equal(G.order(),3) + assert_equal(G.number_of_nodes(),3) + + def test_nodes_iter(self): + G=self.K3 + assert_equal(sorted(G.nodes_iter()),self.k3nodes) + assert_equal(sorted(G.nodes_iter(data=True)),[(0,{}),(1,{}),(2,{})]) + + def test_nodes(self): + G=self.K3 + assert_equal(sorted(G.nodes()),self.k3nodes) + assert_equal(sorted(G.nodes(data=True)),[(0,{}),(1,{}),(2,{})]) + + def test_has_node(self): + G=self.K3 + assert(G.has_node(1)) + assert(not G.has_node(4)) + assert(not G.has_node([])) # no exception for nonhashable + assert(not G.has_node({1:1})) # no exception for nonhashable + + def test_has_edge(self): + G=self.K3 + assert_equal(G.has_edge(0,1),True) + assert_equal(G.has_edge(0,-1),False) + + def test_neighbors(self): + G=self.K3 + assert_equal(sorted(G.neighbors(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.neighbors,-1) + + def test_neighbors_iter(self): + G=self.K3 + assert_equal(sorted(G.neighbors_iter(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.neighbors_iter,-1) + + def test_edges(self): + G=self.K3 + assert_equal(sorted(G.edges()),[(0,1),(0,2),(1,2)]) + assert_equal(sorted(G.edges(0)),[(0,1),(0,2)]) + assert_raises((KeyError,networkx.NetworkXError), G.edges,-1) + + def test_edges_iter(self): + G=self.K3 + assert_equal(sorted(G.edges_iter()),[(0,1),(0,2),(1,2)]) + assert_equal(sorted(G.edges_iter(0)),[(0,1),(0,2)]) + f=lambda x:list(G.edges_iter(x)) + assert_raises((KeyError,networkx.NetworkXError), f, -1) + + def test_adjacency_list(self): + G=self.K3 + assert_equal(G.adjacency_list(),[[1,2],[0,2],[0,1]]) + + def test_degree(self): + G=self.K3 + assert_equal(list(G.degree().values()),[2,2,2]) + assert_equal(G.degree(),{0:2,1:2,2:2}) + assert_equal(G.degree(0),2) + assert_equal(G.degree([0]),{0:2}) + assert_raises((KeyError,networkx.NetworkXError), G.degree,-1) + + def test_weighted_degree(self): + G=self.Graph() + G.add_edge(1,2,weight=2) + G.add_edge(2,3,weight=3) + assert_equal(list(G.degree(weight='weight').values()),[2,5,3]) + assert_equal(G.degree(weight='weight'),{1:2,2:5,3:3}) + assert_equal(G.degree(1,weight='weight'),2) + assert_equal(G.degree([1],weight='weight'),{1:2}) + + def test_degree_iter(self): + G=self.K3 + assert_equal(list(G.degree_iter()),[(0,2),(1,2),(2,2)]) + assert_equal(dict(G.degree_iter()),{0:2,1:2,2:2}) + assert_equal(list(G.degree_iter(0)),[(0,2)]) + + def test_size(self): + G=self.K3 + assert_equal(G.size(),3) + assert_equal(G.number_of_edges(),3) + + def test_add_star(self): + G=self.K3.copy() + nlist=[12,13,14,15] + G.add_star(nlist) + assert_equal(sorted(G.edges(nlist)),[(12,13),(12,14),(12,15)]) + G=self.K3.copy() + G.add_star(nlist,weight=2.0) + assert_equal(sorted(G.edges(nlist,data=True)),\ + [(12,13,{'weight':2.}), + (12,14,{'weight':2.}), + (12,15,{'weight':2.})]) + + def test_add_path(self): + G=self.K3.copy() + nlist=[12,13,14,15] + G.add_path(nlist) + assert_equal(sorted(G.edges(nlist)),[(12,13),(13,14),(14,15)]) + G=self.K3.copy() + G.add_path(nlist,weight=2.0) + assert_equal(sorted(G.edges(nlist,data=True)),\ + [(12,13,{'weight':2.}), + (13,14,{'weight':2.}), + (14,15,{'weight':2.})]) + + def test_add_cycle(self): + G=self.K3.copy() + nlist=[12,13,14,15] + oklists=[ [(12,13),(12,15),(13,14),(14,15)], \ + [(12,13),(13,14),(14,15),(15,12)] ] + G.add_cycle(nlist) + assert_true(sorted(G.edges(nlist)) in oklists) + G=self.K3.copy() + oklists=[ [(12,13,{'weight':1.}),\ + (12,15,{'weight':1.}),\ + (13,14,{'weight':1.}),\ + (14,15,{'weight':1.})], \ + \ + [(12,13,{'weight':1.}),\ + (13,14,{'weight':1.}),\ + (14,15,{'weight':1.}),\ + (15,12,{'weight':1.})] \ + ] + + G.add_cycle(nlist,weight=1.0) + assert_true(sorted(G.edges(nlist,data=True)) in oklists) + + def test_nbunch_iter(self): + G=self.K3 + assert_equal(list(G.nbunch_iter()),self.k3nodes) # all nodes + assert_equal(list(G.nbunch_iter(0)),[0]) # single node + assert_equal(list(G.nbunch_iter([0,1])),[0,1]) # sequence + # sequence with none in graph + assert_equal(list(G.nbunch_iter([-1])),[]) + # string sequence with none in graph + assert_equal(list(G.nbunch_iter("foo")),[]) + # node not in graph doesn't get caught upon creation of iterator + bunch=G.nbunch_iter(-1) + # but gets caught when iterator used + assert_raises(networkx.NetworkXError,list,bunch) + # unhashable doesn't get caught upon creation of iterator + bunch=G.nbunch_iter([0,1,2,{}]) + # but gets caught when iterator hits the unhashable + assert_raises(networkx.NetworkXError,list,bunch) + + def test_selfloop_degree(self): + G=self.Graph() + G.add_edge(1,1) + assert_equal(list(G.degree().values()),[2]) + assert_equal(G.degree(),{1:2}) + assert_equal(G.degree(1),2) + assert_equal(G.degree([1]),{1:2}) + assert_equal(G.degree([1],weight='weight'),{1:2}) + + def test_selfloops(self): + G=self.K3.copy() + G.add_edge(0,0) + assert_equal(G.nodes_with_selfloops(),[0]) + assert_equal(G.selfloop_edges(),[(0,0)]) + assert_equal(G.number_of_selfloops(),1) + G.remove_edge(0,0) + G.add_edge(0,0) + G.remove_edges_from([(0,0)]) + G.add_edge(1,1) + G.remove_node(1) + G.add_edge(0,0) + G.add_edge(1,1) + G.remove_nodes_from([0,1]) + + +class BaseAttrGraphTester(BaseGraphTester): + """ Tests of graph class attribute features.""" + def test_weighted_degree(self): + G=self.Graph() + G.add_edge(1,2,weight=2,other=3) + G.add_edge(2,3,weight=3,other=4) + assert_equal(list(G.degree(weight='weight').values()),[2,5,3]) + assert_equal(G.degree(weight='weight'),{1:2,2:5,3:3}) + assert_equal(G.degree(1,weight='weight'),2) + assert_equal(G.degree([1],weight='weight'),{1:2}) + + assert_equal(list(G.degree(weight='other').values()),[3,7,4]) + assert_equal(G.degree(weight='other'),{1:3,2:7,3:4}) + assert_equal(G.degree(1,weight='other'),3) + assert_equal(G.degree([1],weight='other'),{1:3}) + + def add_attributes(self,G): + G.graph['foo']=[] + G.node[0]['foo']=[] + G.remove_edge(1,2) + ll=[] + G.add_edge(1,2,foo=ll) + G.add_edge(2,1,foo=ll) + # attr_dict must be dict + assert_raises(networkx.NetworkXError,G.add_edge,0,1,attr_dict=[]) + + def test_name(self): + G=self.Graph(name='') + assert_equal(G.name,"") + G=self.Graph(name='test') + assert_equal(G.__str__(),"test") + assert_equal(G.name,"test") + + def test_copy(self): + G=self.K3 + self.add_attributes(G) + H=G.copy() + self.is_deepcopy(H,G) + H=G.__class__(G) + self.is_shallow_copy(H,G) + + def test_copy_attr(self): + G=self.Graph(foo=[]) + G.add_node(0,foo=[]) + G.add_edge(1,2,foo=[]) + H=G.copy() + self.is_deepcopy(H,G) + H=G.__class__(G) # just copy + self.is_shallow_copy(H,G) + + def is_deepcopy(self,H,G): + self.graphs_equal(H,G) + self.different_attrdict(H,G) + self.deep_copy_attrdict(H,G) + + def deep_copy_attrdict(self,H,G): + self.deepcopy_graph_attr(H,G) + self.deepcopy_node_attr(H,G) + self.deepcopy_edge_attr(H,G) + + def deepcopy_graph_attr(self,H,G): + assert_equal(G.graph['foo'],H.graph['foo']) + G.graph['foo'].append(1) + assert_not_equal(G.graph['foo'],H.graph['foo']) + + def deepcopy_node_attr(self,H,G): + assert_equal(G.node[0]['foo'],H.node[0]['foo']) + G.node[0]['foo'].append(1) + assert_not_equal(G.node[0]['foo'],H.node[0]['foo']) + + def deepcopy_edge_attr(self,H,G): + assert_equal(G[1][2]['foo'],H[1][2]['foo']) + G[1][2]['foo'].append(1) + assert_not_equal(G[1][2]['foo'],H[1][2]['foo']) + + def is_shallow_copy(self,H,G): + self.graphs_equal(H,G) + self.different_attrdict(H,G) + self.shallow_copy_attrdict(H,G) + + def shallow_copy_attrdict(self,H,G): + self.shallow_copy_graph_attr(H,G) + self.shallow_copy_node_attr(H,G) + self.shallow_copy_edge_attr(H,G) + + def shallow_copy_graph_attr(self,H,G): + assert_equal(G.graph['foo'],H.graph['foo']) + G.graph['foo'].append(1) + assert_equal(G.graph['foo'],H.graph['foo']) + + def shallow_copy_node_attr(self,H,G): + assert_equal(G.node[0]['foo'],H.node[0]['foo']) + G.node[0]['foo'].append(1) + assert_equal(G.node[0]['foo'],H.node[0]['foo']) + + def shallow_copy_edge_attr(self,H,G): + assert_equal(G[1][2]['foo'],H[1][2]['foo']) + G[1][2]['foo'].append(1) + assert_equal(G[1][2]['foo'],H[1][2]['foo']) + + def same_attrdict(self, H, G): + old_foo=H[1][2]['foo'] + H.add_edge(1,2,foo='baz') + assert_equal(G.edge,H.edge) + H.add_edge(1,2,foo=old_foo) + assert_equal(G.edge,H.edge) + old_foo=H.node[0]['foo'] + H.node[0]['foo']='baz' + assert_equal(G.node,H.node) + H.node[0]['foo']=old_foo + assert_equal(G.node,H.node) + + def different_attrdict(self, H, G): + old_foo=H[1][2]['foo'] + H.add_edge(1,2,foo='baz') + assert_not_equal(G.edge,H.edge) + H.add_edge(1,2,foo=old_foo) + assert_equal(G.edge,H.edge) + old_foo=H.node[0]['foo'] + H.node[0]['foo']='baz' + assert_not_equal(G.node,H.node) + H.node[0]['foo']=old_foo + assert_equal(G.node,H.node) + + def graphs_equal(self,H,G): + assert_equal(G.adj,H.adj) + assert_equal(G.edge,H.edge) + assert_equal(G.node,H.node) + assert_equal(G.graph,H.graph) + assert_equal(G.name,H.name) + if not G.is_directed() and not H.is_directed(): + assert_true(H.adj[1][2] is H.adj[2][1]) + assert_true(G.adj[1][2] is G.adj[2][1]) + else: # at least one is directed + if not G.is_directed(): + G.pred=G.adj + G.succ=G.adj + if not H.is_directed(): + H.pred=H.adj + H.succ=H.adj + assert_equal(G.pred,H.pred) + assert_equal(G.succ,H.succ) + assert_true(H.succ[1][2] is H.pred[2][1]) + assert_true(G.succ[1][2] is G.pred[2][1]) + + def test_graph_attr(self): + G=self.K3 + G.graph['foo']='bar' + assert_equal(G.graph['foo'], 'bar') + del G.graph['foo'] + assert_equal(G.graph, {}) + H=self.Graph(foo='bar') + assert_equal(H.graph['foo'], 'bar') + + def test_node_attr(self): + G=self.K3 + G.add_node(1,foo='bar') + assert_equal(G.nodes(), [0,1,2]) + assert_equal(G.nodes(data=True), [(0,{}),(1,{'foo':'bar'}),(2,{})]) + G.node[1]['foo']='baz' + assert_equal(G.nodes(data=True), [(0,{}),(1,{'foo':'baz'}),(2,{})]) + + def test_node_attr2(self): + G=self.K3 + a={'foo':'bar'} + G.add_node(3,attr_dict=a) + assert_equal(G.nodes(), [0,1,2,3]) + assert_equal(G.nodes(data=True), + [(0,{}),(1,{}),(2,{}),(3,{'foo':'bar'})]) + + def test_edge_attr(self): + G=self.Graph() + G.add_edge(1,2,foo='bar') + assert_equal(G.edges(data=True), [(1,2,{'foo':'bar'})]) + + def test_edge_attr2(self): + G=self.Graph() + G.add_edges_from([(1,2),(3,4)],foo='foo') + assert_equal(sorted(G.edges(data=True)), + [(1,2,{'foo':'foo'}),(3,4,{'foo':'foo'})]) + + def test_edge_attr3(self): + G=self.Graph() + G.add_edges_from([(1,2,{'weight':32}),(3,4,{'weight':64})],foo='foo') + assert_equal(G.edges(data=True), + [(1,2,{'foo':'foo','weight':32}),\ + (3,4,{'foo':'foo','weight':64})]) + + G.remove_edges_from([(1,2),(3,4)]) + G.add_edge(1,2,data=7,spam='bar',bar='foo') + assert_equal(G.edges(data=True), + [(1,2,{'data':7,'spam':'bar','bar':'foo'})]) + + def test_edge_attr4(self): + G=self.Graph() + G.add_edge(1,2,data=7,spam='bar',bar='foo') + assert_equal(G.edges(data=True), + [(1,2,{'data':7,'spam':'bar','bar':'foo'})]) + G[1][2]['data']=10 # OK to set data like this + assert_equal(G.edges(data=True), + [(1,2,{'data':10,'spam':'bar','bar':'foo'})]) + + G.edge[1][2]['data']=20 # another spelling, "edge" + assert_equal(G.edges(data=True), + [(1,2,{'data':20,'spam':'bar','bar':'foo'})]) + G.edge[1][2]['listdata']=[20,200] + G.edge[1][2]['weight']=20 + assert_equal(G.edges(data=True), + [(1,2,{'data':20,'spam':'bar', + 'bar':'foo','listdata':[20,200],'weight':20})]) + + def test_attr_dict_not_dict(self): + # attr_dict must be dict + G=self.Graph() + edges=[(1,2)] + assert_raises(networkx.NetworkXError,G.add_edges_from,edges, + attr_dict=[]) + + def test_to_undirected(self): + G=self.K3 + self.add_attributes(G) + H=networkx.Graph(G) + self.is_shallow_copy(H,G) + H=G.to_undirected() + self.is_deepcopy(H,G) + + def test_to_directed(self): + G=self.K3 + self.add_attributes(G) + H=networkx.DiGraph(G) + self.is_shallow_copy(H,G) + H=G.to_directed() + self.is_deepcopy(H,G) + + def test_subgraph(self): + G=self.K3 + self.add_attributes(G) + H=G.subgraph([0,1,2,5]) +# assert_equal(H.name, 'Subgraph of ('+G.name+')') + H.name=G.name + self.graphs_equal(H,G) + self.same_attrdict(H,G) + self.shallow_copy_attrdict(H,G) + + H=G.subgraph(0) + assert_equal(H.adj,{0:{}}) + H=G.subgraph([]) + assert_equal(H.adj,{}) + assert_not_equal(G.adj,{}) + + def test_selfloops_attr(self): + G=self.K3.copy() + G.add_edge(0,0) + G.add_edge(1,1,weight=2) + assert_equal(G.selfloop_edges(data=True), + [(0,0,{}),(1,1,{'weight':2})]) + + +class TestGraph(BaseAttrGraphTester): + """Tests specific to dict-of-dict-of-dict graph data structure""" + def setUp(self): + self.Graph=networkx.Graph + # build dict-of-dict-of-dict K3 + ed1,ed2,ed3 = ({},{},{}) + self.k3adj={0: {1: ed1, 2: ed2}, + 1: {0: ed1, 2: ed3}, + 2: {0: ed2, 1: ed3}} + self.k3edges=[(0, 1), (0, 2), (1, 2)] + self.k3nodes=[0, 1, 2] + self.K3=self.Graph() + self.K3.adj=self.K3.edge=self.k3adj + self.K3.node={} + self.K3.node[0]={} + self.K3.node[1]={} + self.K3.node[2]={} + + def test_data_input(self): + G=self.Graph(data={1:[2],2:[1]}, name="test") + assert_equal(G.name,"test") + assert_equal(sorted(G.adj.items()),[(1, {2: {}}), (2, {1: {}})]) + G=self.Graph({1:[2],2:[1]}, name="test") + assert_equal(G.name,"test") + assert_equal(sorted(G.adj.items()),[(1, {2: {}}), (2, {1: {}})]) + + def test_adjacency_iter(self): + G=self.K3 + assert_equal(dict(G.adjacency_iter()), + {0: {1: {}, 2: {}}, 1: {0: {}, 2: {}}, 2: {0: {}, 1: {}}}) + + def test_getitem(self): + G=self.K3 + assert_equal(G[0],{1: {}, 2: {}}) + assert_raises(KeyError, G.__getitem__, 'j') + assert_raises((TypeError,networkx.NetworkXError), G.__getitem__, ['A']) + + def test_add_node(self): + G=self.Graph() + G.add_node(0) + assert_equal(G.adj,{0:{}}) + # test add attributes + G.add_node(1,c='red') + G.add_node(2,{'c':'blue'}) + G.add_node(3,{'c':'blue'},c='red') + assert_raises(networkx.NetworkXError, G.add_node, 4, []) + assert_raises(networkx.NetworkXError, G.add_node, 4, 4) + assert_equal(G.node[1]['c'],'red') + assert_equal(G.node[2]['c'],'blue') + assert_equal(G.node[3]['c'],'red') + # test updating attributes + G.add_node(1,c='blue') + G.add_node(2,{'c':'red'}) + G.add_node(3,{'c':'red'},c='blue') + assert_equal(G.node[1]['c'],'blue') + assert_equal(G.node[2]['c'],'red') + assert_equal(G.node[3]['c'],'blue') + + def test_add_nodes_from(self): + G=self.Graph() + G.add_nodes_from([0,1,2]) + assert_equal(G.adj,{0:{},1:{},2:{}}) + # test add attributes + G.add_nodes_from([0,1,2],c='red') + assert_equal(G.node[0]['c'],'red') + assert_equal(G.node[2]['c'],'red') + # test that attribute dicts are not the same + assert(G.node[0] is not G.node[1]) + # test updating attributes + G.add_nodes_from([0,1,2],c='blue') + assert_equal(G.node[0]['c'],'blue') + assert_equal(G.node[2]['c'],'blue') + assert(G.node[0] is not G.node[1]) + # test tuple input + H=self.Graph() + H.add_nodes_from(G.nodes(data=True)) + assert_equal(H.node[0]['c'],'blue') + assert_equal(H.node[2]['c'],'blue') + assert(H.node[0] is not H.node[1]) + # specific overrides general + H.add_nodes_from([0,(1,{'c':'green'}),(3,{'c':'cyan'})],c='red') + assert_equal(H.node[0]['c'],'red') + assert_equal(H.node[1]['c'],'green') + assert_equal(H.node[2]['c'],'blue') + assert_equal(H.node[3]['c'],'cyan') + + def test_remove_node(self): + G=self.K3 + G.remove_node(0) + assert_equal(G.adj,{1:{2:{}},2:{1:{}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_node,-1) + + # generator here to implement list,set,string... + def test_remove_nodes_from(self): + G=self.K3 + G.remove_nodes_from([0,1]) + assert_equal(G.adj,{2:{}}) + G.remove_nodes_from([-1]) # silent fail + + def test_add_edge(self): + G=self.Graph() + G.add_edge(0,1) + assert_equal(G.adj,{0: {1: {}}, 1: {0: {}}}) + G=self.Graph() + G.add_edge(*(0,1)) + assert_equal(G.adj,{0: {1: {}}, 1: {0: {}}}) + + def test_add_edges_from(self): + G=self.Graph() + G.add_edges_from([(0,1),(0,2,{'weight':3})]) + assert_equal(G.adj,{0: {1:{}, 2:{'weight':3}}, 1: {0:{}}, \ + 2:{0:{'weight':3}}}) + G=self.Graph() + G.add_edges_from([(0,1),(0,2,{'weight':3}),(1,2,{'data':4})],data=2) + assert_equal(G.adj,{\ + 0: {1:{'data':2}, 2:{'weight':3,'data':2}}, \ + 1: {0:{'data':2}, 2:{'data':4}}, \ + 2: {0:{'weight':3,'data':2}, 1:{'data':4}} \ + }) + + assert_raises(networkx.NetworkXError, + G.add_edges_from,[(0,)]) # too few in tuple + assert_raises(networkx.NetworkXError, + G.add_edges_from,[(0,1,2,3)]) # too many in tuple + assert_raises(TypeError, G.add_edges_from,[0]) # not a tuple + + + def test_remove_edge(self): + G=self.K3 + G.remove_edge(0,1) + assert_equal(G.adj,{0:{2:{}},1:{2:{}},2:{0:{},1:{}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,-1,0) + + def test_remove_edges_from(self): + G=self.K3 + G.remove_edges_from([(0,1)]) + assert_equal(G.adj,{0:{2:{}},1:{2:{}},2:{0:{},1:{}}}) + G.remove_edges_from([(0,0)]) # silent fail + + def test_clear(self): + G=self.K3 + G.clear() + assert_equal(G.adj,{}) + + def test_edges_data(self): + G=self.K3 + assert_equal(sorted(G.edges(data=True)),[(0,1,{}),(0,2,{}),(1,2,{})]) + assert_equal(sorted(G.edges(0,data=True)),[(0,1,{}),(0,2,{})]) + assert_raises((KeyError,networkx.NetworkXError), G.edges,-1) + + + def test_get_edge_data(self): + G=self.K3 + assert_equal(G.get_edge_data(0,1),{}) + assert_equal(G[0][1],{}) + assert_equal(G.get_edge_data(10,20),None) + assert_equal(G.get_edge_data(-1,0),None) + assert_equal(G.get_edge_data(-1,0,default=1),1) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph_historical.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph_historical.py new file mode 100644 index 0000000..0ade19a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_graph_historical.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +"""Original NetworkX graph tests""" +from nose.tools import * +import networkx +import networkx as nx + +from historical_tests import HistoricalTests + +class TestGraphHistorical(HistoricalTests): + + def setUp(self): + HistoricalTests.setUp(self) + self.G=nx.Graph + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multidigraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multidigraph.py new file mode 100644 index 0000000..e8c2543 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multidigraph.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx +from test_multigraph import BaseMultiGraphTester, TestMultiGraph + +class BaseMultiDiGraphTester(BaseMultiGraphTester): + def test_edges(self): + G=self.K3 + assert_equal(sorted(G.edges()),[(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.edges(0)),[(0,1),(0,2)]) + assert_raises((KeyError,networkx.NetworkXError), G.edges,-1) + + def test_edges_data(self): + G=self.K3 + assert_equal(sorted(G.edges(data=True)), + [(0,1,{}),(0,2,{}),(1,0,{}),(1,2,{}),(2,0,{}),(2,1,{})]) + assert_equal(sorted(G.edges(0,data=True)),[(0,1,{}),(0,2,{})]) + assert_raises((KeyError,networkx.NetworkXError), G.neighbors,-1) + + + def test_edges_iter(self): + G=self.K3 + assert_equal(sorted(G.edges_iter()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.edges_iter(0)),[(0,1),(0,2)]) + G.add_edge(0,1) + assert_equal(sorted(G.edges_iter()), + [(0,1),(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + + def test_out_edges(self): + G=self.K3 + assert_equal(sorted(G.out_edges()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.out_edges(0)),[(0,1),(0,2)]) + assert_raises((KeyError,networkx.NetworkXError), G.out_edges,-1) + assert_equal(sorted(G.out_edges(0,keys=True)),[(0,1,0),(0,2,0)]) + + def test_out_edges_iter(self): + G=self.K3 + assert_equal(sorted(G.out_edges_iter()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.out_edges_iter(0)),[(0,1),(0,2)]) + G.add_edge(0,1,2) + assert_equal(sorted(G.out_edges_iter()), + [(0,1),(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + + def test_in_edges(self): + G=self.K3 + assert_equal(sorted(G.in_edges()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.in_edges(0)),[(1,0),(2,0)]) + assert_raises((KeyError,networkx.NetworkXError), G.in_edges,-1) + G.add_edge(0,1,2) + assert_equal(sorted(G.in_edges()), + [(0,1),(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.in_edges(0,keys=True)),[(1,0,0),(2,0,0)]) + + def test_in_edges_iter(self): + G=self.K3 + assert_equal(sorted(G.in_edges_iter()), + [(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + assert_equal(sorted(G.in_edges_iter(0)),[(1,0),(2,0)]) + G.add_edge(0,1,2) + assert_equal(sorted(G.in_edges_iter()), + [(0,1),(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)]) + + assert_equal(sorted(G.in_edges_iter(data=True,keys=False)), + [(0,1,{}),(0,1,{}),(0,2,{}),(1,0,{}),(1,2,{}), + (2,0,{}),(2,1,{})]) + + + def is_shallow(self,H,G): + # graph + assert_equal(G.graph['foo'],H.graph['foo']) + G.graph['foo'].append(1) + assert_equal(G.graph['foo'],H.graph['foo']) + # node + assert_equal(G.node[0]['foo'],H.node[0]['foo']) + G.node[0]['foo'].append(1) + assert_equal(G.node[0]['foo'],H.node[0]['foo']) + # edge + assert_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + G[1][2][0]['foo'].append(1) + assert_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + + def is_deep(self,H,G): + # graph + assert_equal(G.graph['foo'],H.graph['foo']) + G.graph['foo'].append(1) + assert_not_equal(G.graph['foo'],H.graph['foo']) + # node + assert_equal(G.node[0]['foo'],H.node[0]['foo']) + G.node[0]['foo'].append(1) + assert_not_equal(G.node[0]['foo'],H.node[0]['foo']) + # edge + assert_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + G[1][2][0]['foo'].append(1) + assert_not_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + + def test_to_undirected(self): + # MultiDiGraph -> MultiGraph changes number of edges so it is + # not a copy operation... use is_shallow, not is_shallow_copy + G=self.K3 + self.add_attributes(G) + H=networkx.MultiGraph(G) + self.is_shallow(H,G) + H=G.to_undirected() + self.is_deep(H,G) + + def test_has_successor(self): + G=self.K3 + assert_equal(G.has_successor(0,1),True) + assert_equal(G.has_successor(0,-1),False) + + def test_successors(self): + G=self.K3 + assert_equal(sorted(G.successors(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.successors,-1) + + def test_successors_iter(self): + G=self.K3 + assert_equal(sorted(G.successors_iter(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.successors_iter,-1) + + def test_has_predecessor(self): + G=self.K3 + assert_equal(G.has_predecessor(0,1),True) + assert_equal(G.has_predecessor(0,-1),False) + + def test_predecessors(self): + G=self.K3 + assert_equal(sorted(G.predecessors(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.predecessors,-1) + + def test_predecessors_iter(self): + G=self.K3 + assert_equal(sorted(G.predecessors_iter(0)),[1,2]) + assert_raises((KeyError,networkx.NetworkXError), G.predecessors_iter,-1) + + + def test_degree(self): + G=self.K3 + assert_equal(list(G.degree().values()),[4,4,4]) + assert_equal(G.degree(),{0:4,1:4,2:4}) + assert_equal(G.degree(0),4) + assert_equal(G.degree([0]),{0:4}) + assert_raises((KeyError,networkx.NetworkXError), G.degree,-1) + + def test_degree_iter(self): + G=self.K3 + assert_equal(list(G.degree_iter()),[(0,4),(1,4),(2,4)]) + assert_equal(dict(G.degree_iter()),{0:4,1:4,2:4}) + assert_equal(list(G.degree_iter(0)),[(0,4)]) + G.add_edge(0,1,weight=0.3,other=1.2) + assert_equal(list(G.degree_iter(weight='weight')),[(0,4.3),(1,4.3),(2,4)]) + assert_equal(list(G.degree_iter(weight='other')),[(0,5.2),(1,5.2),(2,4)]) + + + def test_in_degree(self): + G=self.K3 + assert_equal(list(G.in_degree().values()),[2,2,2]) + assert_equal(G.in_degree(),{0:2,1:2,2:2}) + assert_equal(G.in_degree(0),2) + assert_equal(G.in_degree([0]),{0:2}) + assert_raises((KeyError,networkx.NetworkXError), G.in_degree,-1) + + def test_in_degree_iter(self): + G=self.K3 + assert_equal(list(G.in_degree_iter()),[(0,2),(1,2),(2,2)]) + assert_equal(dict(G.in_degree_iter()),{0:2,1:2,2:2}) + assert_equal(list(G.in_degree_iter(0)),[(0,2)]) + assert_equal(list(G.in_degree_iter(0,weight='weight')),[(0,2)]) + + def test_out_degree(self): + G=self.K3 + assert_equal(list(G.out_degree().values()),[2,2,2]) + assert_equal(G.out_degree(),{0:2,1:2,2:2}) + assert_equal(G.out_degree(0),2) + assert_equal(G.out_degree([0]),{0:2}) + assert_raises((KeyError,networkx.NetworkXError), G.out_degree,-1) + + def test_out_degree_iter(self): + G=self.K3 + assert_equal(list(G.out_degree_iter()),[(0,2),(1,2),(2,2)]) + assert_equal(dict(G.out_degree_iter()),{0:2,1:2,2:2}) + assert_equal(list(G.out_degree_iter(0)),[(0,2)]) + assert_equal(list(G.out_degree_iter(0,weight='weight')),[(0,2)]) + + + def test_size(self): + G=self.K3 + assert_equal(G.size(),6) + assert_equal(G.number_of_edges(),6) + G.add_edge(0,1,weight=0.3,other=1.2) + assert_equal(G.size(weight='weight'),6.3) + assert_equal(G.size(weight='other'),7.2) + + def test_to_undirected_reciprocal(self): + G=self.Graph() + G.add_edge(1,2) + assert_true(G.to_undirected().has_edge(1,2)) + assert_false(G.to_undirected(reciprocal=True).has_edge(1,2)) + G.add_edge(2,1) + assert_true(G.to_undirected(reciprocal=True).has_edge(1,2)) + + def test_reverse_copy(self): + G=networkx.MultiDiGraph([(0,1),(0,1)]) + R=G.reverse() + assert_equal(sorted(R.edges()),[(1,0),(1,0)]) + R.remove_edge(1,0) + assert_equal(sorted(R.edges()),[(1,0)]) + assert_equal(sorted(G.edges()),[(0,1),(0,1)]) + + def test_reverse_nocopy(self): + G=networkx.MultiDiGraph([(0,1),(0,1)]) + R=G.reverse(copy=False) + assert_equal(sorted(R.edges()),[(1,0),(1,0)]) + R.remove_edge(1,0) + assert_equal(sorted(R.edges()),[(1,0)]) + assert_equal(sorted(G.edges()),[(1,0)]) + + +class TestMultiDiGraph(BaseMultiDiGraphTester,TestMultiGraph): + def setUp(self): + self.Graph=networkx.MultiDiGraph + # build K3 + self.k3edges=[(0, 1), (0, 2), (1, 2)] + self.k3nodes=[0, 1, 2] + self.K3=self.Graph() + self.K3.adj={0:{},1:{},2:{}} + self.K3.succ=self.K3.adj + self.K3.pred={0:{},1:{},2:{}} + for u in self.k3nodes: + for v in self.k3nodes: + if u==v: continue + d={0:{}} + self.K3.succ[u][v]=d + self.K3.pred[v][u]=d + self.K3.adj=self.K3.succ + self.K3.edge=self.K3.adj + self.K3.node={} + self.K3.node[0]={} + self.K3.node[1]={} + self.K3.node[2]={} + + + def test_add_edge(self): + G=self.Graph() + G.add_edge(0,1) + assert_equal(G.adj,{0: {1: {0:{}}}, 1: {}}) + assert_equal(G.succ,{0: {1: {0:{}}}, 1: {}}) + assert_equal(G.pred,{0: {}, 1: {0:{0:{}}}}) + G=self.Graph() + G.add_edge(*(0,1)) + assert_equal(G.adj,{0: {1: {0:{}}}, 1: {}}) + assert_equal(G.succ,{0: {1: {0:{}}}, 1: {}}) + assert_equal(G.pred,{0: {}, 1: {0:{0:{}}}}) + + def test_add_edges_from(self): + G=self.Graph() + G.add_edges_from([(0,1),(0,1,{'weight':3})]) + assert_equal(G.adj,{0: {1: {0:{},1:{'weight':3}}}, 1: {}}) + assert_equal(G.succ,{0: {1: {0:{},1:{'weight':3}}}, 1: {}}) + assert_equal(G.pred,{0: {}, 1: {0:{0:{},1:{'weight':3}}}}) + + G.add_edges_from([(0,1),(0,1,{'weight':3})],weight=2) + assert_equal(G.succ,{0: {1: {0:{}, + 1:{'weight':3}, + 2:{'weight':2}, + 3:{'weight':3}}}, + 1: {}}) + assert_equal(G.pred,{0: {}, 1: {0:{0:{},1:{'weight':3}, + 2:{'weight':2}, + 3:{'weight':3}}}}) + + assert_raises(networkx.NetworkXError, G.add_edges_from,[(0,)]) # too few in tuple + assert_raises(networkx.NetworkXError, G.add_edges_from,[(0,1,2,3,4)]) # too many in tuple + assert_raises(TypeError, G.add_edges_from,[0]) # not a tuple + + def test_remove_edge(self): + G=self.K3 + G.remove_edge(0,1) + assert_equal(G.succ,{0:{2:{0:{}}}, + 1:{0:{0:{}},2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + assert_equal(G.pred,{0:{1:{0:{}}, 2:{0:{}}}, + 1:{2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,-1,0) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,0,2, + key=1) + + + def test_remove_multiedge(self): + G=self.K3 + G.add_edge(0,1,key='parallel edge') + G.remove_edge(0,1,key='parallel edge') + assert_equal(G.adj,{0: {1: {0:{}}, 2: {0:{}}}, + 1: {0: {0:{}}, 2: {0:{}}}, + 2: {0: {0:{}}, 1: {0:{}}}}) + + assert_equal(G.succ,{0: {1: {0:{}}, 2: {0:{}}}, + 1: {0: {0:{}}, 2: {0:{}}}, + 2: {0: {0:{}}, 1: {0:{}}}}) + + assert_equal(G.pred,{0:{1: {0:{}},2:{0:{}}}, + 1:{0:{0:{}},2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + G.remove_edge(0,1) + assert_equal(G.succ,{0:{2:{0:{}}}, + 1:{0:{0:{}},2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + assert_equal(G.pred,{0:{1:{0:{}}, 2:{0:{}}}, + 1:{2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,-1,0) + + def test_remove_edges_from(self): + G=self.K3 + G.remove_edges_from([(0,1)]) + assert_equal(G.succ,{0:{2:{0:{}}}, + 1:{0:{0:{}},2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + assert_equal(G.pred,{0:{1:{0:{}}, 2:{0:{}}}, + 1:{2:{0:{}}}, + 2:{0:{0:{}},1:{0:{}}}}) + G.remove_edges_from([(0,0)]) # silent fail diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multigraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multigraph.py new file mode 100644 index 0000000..6c9adbc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/classes/tests/test_multigraph.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx +from test_graph import BaseAttrGraphTester, TestGraph + +class BaseMultiGraphTester(BaseAttrGraphTester): + def test_has_edge(self): + G=self.K3 + assert_equal(G.has_edge(0,1),True) + assert_equal(G.has_edge(0,-1),False) + assert_equal(G.has_edge(0,1,0),True) + assert_equal(G.has_edge(0,1,1),False) + + def test_get_edge_data(self): + G=self.K3 + assert_equal(G.get_edge_data(0,1),{0:{}}) + assert_equal(G[0][1],{0:{}}) + assert_equal(G[0][1][0],{}) + assert_equal(G.get_edge_data(10,20),None) + assert_equal(G.get_edge_data(0,1,0),{}) + + + def test_adjacency_iter(self): + G=self.K3 + assert_equal(dict(G.adjacency_iter()), + {0: {1: {0:{}}, 2: {0:{}}}, + 1: {0: {0:{}}, 2: {0:{}}}, + 2: {0: {0:{}}, 1: {0:{}}}}) + + def deepcopy_edge_attr(self,H,G): + assert_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + G[1][2][0]['foo'].append(1) + assert_not_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + + def shallow_copy_edge_attr(self,H,G): + assert_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + G[1][2][0]['foo'].append(1) + assert_equal(G[1][2][0]['foo'],H[1][2][0]['foo']) + + def same_attrdict(self, H, G): + # same attrdict in the edgedata + old_foo=H[1][2][0]['foo'] + H.add_edge(1,2,0,foo='baz') + assert_equal(G.edge,H.edge) + H.add_edge(1,2,0,foo=old_foo) + assert_equal(G.edge,H.edge) + # but not same edgedata dict + H.add_edge(1,2,foo='baz') + assert_not_equal(G.edge,H.edge) + + old_foo=H.node[0]['foo'] + H.node[0]['foo']='baz' + assert_equal(G.node,H.node) + H.node[0]['foo']=old_foo + assert_equal(G.node,H.node) + + def different_attrdict(self, H, G): + # used by graph_equal_but_different + old_foo=H[1][2][0]['foo'] + H.add_edge(1,2,0,foo='baz') + assert_not_equal(G.edge,H.edge) + H.add_edge(1,2,0,foo=old_foo) + assert_equal(G.edge,H.edge) + HH=H.copy() + H.add_edge(1,2,foo='baz') + assert_not_equal(G.edge,H.edge) + H=HH + old_foo=H.node[0]['foo'] + H.node[0]['foo']='baz' + assert_not_equal(G.node,H.node) + H.node[0]['foo']=old_foo + assert_equal(G.node,H.node) + + def test_to_undirected(self): + G=self.K3 + self.add_attributes(G) + H=networkx.MultiGraph(G) + self.is_shallow_copy(H,G) + H=G.to_undirected() + self.is_deepcopy(H,G) + + def test_to_directed(self): + G=self.K3 + self.add_attributes(G) + H=networkx.MultiDiGraph(G) + self.is_shallow_copy(H,G) + H=G.to_directed() + self.is_deepcopy(H,G) + + def test_selfloops(self): + G=self.K3 + G.add_edge(0,0) + assert_equal(G.nodes_with_selfloops(),[0]) + assert_equal(G.selfloop_edges(),[(0,0)]) + assert_equal(G.selfloop_edges(data=True),[(0,0,{})]) + assert_equal(G.number_of_selfloops(),1) + + def test_selfloops2(self): + G=self.K3 + G.add_edge(0,0) + G.add_edge(0,0) + G.add_edge(0,0,key='parallel edge') + G.remove_edge(0,0,key='parallel edge') + assert_equal(G.number_of_edges(0,0),2) + G.remove_edge(0,0) + assert_equal(G.number_of_edges(0,0),1) + + def test_edge_attr4(self): + G=self.Graph() + G.add_edge(1,2,key=0,data=7,spam='bar',bar='foo') + assert_equal(G.edges(data=True), + [(1,2,{'data':7,'spam':'bar','bar':'foo'})]) + G[1][2][0]['data']=10 # OK to set data like this + assert_equal(G.edges(data=True), + [(1,2,{'data':10,'spam':'bar','bar':'foo'})]) + + G.edge[1][2][0]['data']=20 # another spelling, "edge" + assert_equal(G.edges(data=True), + [(1,2,{'data':20,'spam':'bar','bar':'foo'})]) + G.edge[1][2][0]['listdata']=[20,200] + G.edge[1][2][0]['weight']=20 + assert_equal(G.edges(data=True), + [(1,2,{'data':20,'spam':'bar', + 'bar':'foo','listdata':[20,200],'weight':20})]) + + +class TestMultiGraph(BaseMultiGraphTester,TestGraph): + def setUp(self): + self.Graph=networkx.MultiGraph + # build K3 + ed1,ed2,ed3 = ({0:{}},{0:{}},{0:{}}) + self.k3adj={0: {1: ed1, 2: ed2}, + 1: {0: ed1, 2: ed3}, + 2: {0: ed2, 1: ed3}} + self.k3edges=[(0, 1), (0, 2), (1, 2)] + self.k3nodes=[0, 1, 2] + self.K3=self.Graph() + self.K3.adj = self.K3.edge = self.k3adj + self.K3.node={} + self.K3.node[0]={} + self.K3.node[1]={} + self.K3.node[2]={} + + def test_data_input(self): + G=self.Graph(data={1:[2],2:[1]}, name="test") + assert_equal(G.name,"test") + assert_equal(sorted(G.adj.items()),[(1, {2: {0:{}}}), (2, {1: {0:{}}})]) + + def test_getitem(self): + G=self.K3 + assert_equal(G[0],{1: {0:{}}, 2: {0:{}}}) + assert_raises(KeyError, G.__getitem__, 'j') + assert_raises((TypeError,networkx.NetworkXError), G.__getitem__, ['A']) + + def test_remove_node(self): + G=self.K3 + G.remove_node(0) + assert_equal(G.adj,{1:{2:{0:{}}},2:{1:{0:{}}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_node,-1) + + def test_add_edge(self): + G=self.Graph() + G.add_edge(0,1) + assert_equal(G.adj,{0: {1: {0:{}}}, 1: {0: {0:{}}}}) + G=self.Graph() + G.add_edge(*(0,1)) + assert_equal(G.adj,{0: {1: {0:{}}}, 1: {0: {0:{}}}}) + + def test_add_edge_conflicting_key(self): + G=self.Graph() + G.add_edge(0,1,key=1) + G.add_edge(0,1) + assert_equal(G.number_of_edges(),2) + G=self.Graph() + G.add_edges_from([(0,1,1,{})]) + G.add_edges_from([(0,1)]) + assert_equal(G.number_of_edges(),2) + + + + def test_add_edges_from(self): + G=self.Graph() + G.add_edges_from([(0,1),(0,1,{'weight':3})]) + assert_equal(G.adj,{0: {1: {0:{},1:{'weight':3}}}, + 1: {0: {0:{},1:{'weight':3}}}}) + G.add_edges_from([(0,1),(0,1,{'weight':3})],weight=2) + assert_equal(G.adj,{0: {1: {0:{},1:{'weight':3}, + 2:{'weight':2},3:{'weight':3}}}, + 1: {0: {0:{},1:{'weight':3}, + 2:{'weight':2},3:{'weight':3}}}}) + + # too few in tuple + assert_raises(networkx.NetworkXError, G.add_edges_from,[(0,)]) + # too many in tuple + assert_raises(networkx.NetworkXError, G.add_edges_from,[(0,1,2,3,4)]) + assert_raises(TypeError, G.add_edges_from,[0]) # not a tuple + + + def test_remove_edge(self): + G=self.K3 + G.remove_edge(0,1) + assert_equal(G.adj,{0: {2: {0: {}}}, + 1: {2: {0: {}}}, + 2: {0: {0: {}}, + 1: {0: {}}}}) + + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,-1,0) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,0,2, + key=1) + + + + def test_remove_edges_from(self): + G=self.K3.copy() + G.remove_edges_from([(0,1)]) + assert_equal(G.adj,{0:{2:{0:{}}},1:{2:{0:{}}},2:{0:{0:{}},1:{0:{}}}}) + G.remove_edges_from([(0,0)]) # silent fail + self.K3.add_edge(0,1) + G=self.K3.copy() + G.remove_edges_from(G.edges(data=True,keys=True)) + assert_equal(G.adj,{0:{},1:{},2:{}}) + G=self.K3.copy() + G.remove_edges_from(G.edges(data=False,keys=True)) + assert_equal(G.adj,{0:{},1:{},2:{}}) + G=self.K3.copy() + G.remove_edges_from(G.edges(data=False,keys=False)) + assert_equal(G.adj,{0:{},1:{},2:{}}) + G=self.K3.copy() + G.remove_edges_from([(0,1,0),(0,2,0,{}),(1,2)]) + assert_equal(G.adj,{0:{1:{1:{}}},1:{0:{1:{}}},2:{}}) + + + + def test_remove_multiedge(self): + G=self.K3 + G.add_edge(0,1,key='parallel edge') + G.remove_edge(0,1,key='parallel edge') + assert_equal(G.adj,{0: {1: {0:{}}, 2: {0:{}}}, + 1: {0: {0:{}}, 2: {0:{}}}, + 2: {0: {0:{}}, 1: {0:{}}}}) + G.remove_edge(0,1) + assert_equal(G.adj,{0:{2:{0:{}}},1:{2:{0:{}}},2:{0:{0:{}},1:{0:{}}}}) + assert_raises((KeyError,networkx.NetworkXError), G.remove_edge,-1,0) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/convert.py b/lib/python2.7/site-packages/setoolsgui/networkx/convert.py new file mode 100644 index 0000000..6333b9f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/convert.py @@ -0,0 +1,847 @@ +""" +This module provides functions to convert +NetworkX graphs to and from other formats. + +The preferred way of converting data to a NetworkX graph +is through the graph constuctor. The constructor calls +the to_networkx_graph() function which attempts to guess the +input type and convert it automatically. + +Examples +-------- + +Create a 10 node random graph from a numpy matrix + +>>> import numpy +>>> a=numpy.reshape(numpy.random.random_integers(0,1,size=100),(10,10)) +>>> D=nx.DiGraph(a) + +or equivalently + +>>> D=nx.to_networkx_graph(a,create_using=nx.DiGraph()) + +Create a graph with a single edge from a dictionary of dictionaries + +>>> d={0: {1: 1}} # dict-of-dicts single edge (0,1) +>>> G=nx.Graph(d) + + +See Also +-------- +nx_pygraphviz, nx_pydot + +""" +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) +# Copyright (C) 2006-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +import warnings +import networkx as nx + +__all__ = ['to_networkx_graph', + 'from_dict_of_dicts', 'to_dict_of_dicts', + 'from_dict_of_lists', 'to_dict_of_lists', + 'from_edgelist', 'to_edgelist', + 'from_numpy_matrix', 'to_numpy_matrix', + 'to_numpy_recarray', + 'from_scipy_sparse_matrix', 'to_scipy_sparse_matrix'] + +def _prep_create_using(create_using): + """Return a graph object ready to be populated. + + If create_using is None return the default (just networkx.Graph()) + If create_using.clear() works, assume it returns a graph object. + Otherwise raise an exception because create_using is not a networkx graph. + + """ + if create_using is None: + G=nx.Graph() + else: + G=create_using + try: + G.clear() + except: + raise TypeError("Input graph is not a networkx graph type") + return G + +def to_networkx_graph(data,create_using=None,multigraph_input=False): + """Make a NetworkX graph from a known data structure. + + The preferred way to call this is automatically + from the class constructor + + >>> d={0: {1: {'weight':1}}} # dict-of-dicts single edge (0,1) + >>> G=nx.Graph(d) + + instead of the equivalent + + >>> G=nx.from_dict_of_dicts(d) + + Parameters + ---------- + data : a object to be converted + Current known types are: + any NetworkX graph + dict-of-dicts + dist-of-lists + list of edges + numpy matrix + numpy ndarray + scipy sparse matrix + pygraphviz agraph + + create_using : NetworkX graph + Use specified graph for result. Otherwise a new graph is created. + + multigraph_input : bool (default False) + If True and data is a dict_of_dicts, + try to create a multigraph assuming dict_of_dict_of_lists. + If data and create_using are both multigraphs then create + a multigraph from a multigraph. + + """ + # NX graph + if hasattr(data,"adj"): + try: + result= from_dict_of_dicts(data.adj,\ + create_using=create_using,\ + multigraph_input=data.is_multigraph()) + if hasattr(data,'graph') and isinstance(data.graph,dict): + result.graph=data.graph.copy() + if hasattr(data,'node') and isinstance(data.node,dict): + result.node=dict( (n,dd.copy()) for n,dd in data.node.items() ) + return result + except: + raise nx.NetworkXError("Input is not a correct NetworkX graph.") + + # pygraphviz agraph + if hasattr(data,"is_strict"): + try: + return nx.from_agraph(data,create_using=create_using) + except: + raise nx.NetworkXError("Input is not a correct pygraphviz graph.") + + # dict of dicts/lists + if isinstance(data,dict): + try: + return from_dict_of_dicts(data,create_using=create_using,\ + multigraph_input=multigraph_input) + except: + try: + return from_dict_of_lists(data,create_using=create_using) + except: + raise TypeError("Input is not known type.") + + # list or generator of edges + if (isinstance(data,list) + or hasattr(data,'next') + or hasattr(data, '__next__')): + try: + return from_edgelist(data,create_using=create_using) + except: + raise nx.NetworkXError("Input is not a valid edge list") + + # numpy matrix or ndarray + try: + import numpy + if isinstance(data,numpy.matrix) or \ + isinstance(data,numpy.ndarray): + try: + return from_numpy_matrix(data,create_using=create_using) + except: + raise nx.NetworkXError(\ + "Input is not a correct numpy matrix or array.") + except ImportError: + warnings.warn('numpy not found, skipping conversion test.', + ImportWarning) + + # scipy sparse matrix - any format + try: + import scipy + if hasattr(data,"format"): + try: + return from_scipy_sparse_matrix(data,create_using=create_using) + except: + raise nx.NetworkXError(\ + "Input is not a correct scipy sparse matrix type.") + except ImportError: + warnings.warn('scipy not found, skipping conversion test.', + ImportWarning) + + + raise nx.NetworkXError(\ + "Input is not a known data type for conversion.") + + return + + +def convert_to_undirected(G): + """Return a new undirected representation of the graph G.""" + return G.to_undirected() + + +def convert_to_directed(G): + """Return a new directed representation of the graph G.""" + return G.to_directed() + + +def to_dict_of_lists(G,nodelist=None): + """Return adjacency representation of graph as a dictionary of lists. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list + Use only nodes specified in nodelist + + Notes + ----- + Completely ignores edge data for MultiGraph and MultiDiGraph. + + """ + if nodelist is None: + nodelist=G + + d = {} + for n in nodelist: + d[n]=[nbr for nbr in G.neighbors(n) if nbr in nodelist] + return d + +def from_dict_of_lists(d,create_using=None): + """Return a graph from a dictionary of lists. + + Parameters + ---------- + d : dictionary of lists + A dictionary of lists adjacency representation. + + create_using : NetworkX graph + Use specified graph for result. Otherwise a new graph is created. + + Examples + -------- + >>> dol= {0:[1]} # single edge (0,1) + >>> G=nx.from_dict_of_lists(dol) + + or + >>> G=nx.Graph(dol) # use Graph constructor + + """ + G=_prep_create_using(create_using) + G.add_nodes_from(d) + if G.is_multigraph() and not G.is_directed(): + # a dict_of_lists can't show multiedges. BUT for undirected graphs, + # each edge shows up twice in the dict_of_lists. + # So we need to treat this case separately. + seen={} + for node,nbrlist in d.items(): + for nbr in nbrlist: + if nbr not in seen: + G.add_edge(node,nbr) + seen[node]=1 # don't allow reverse edge to show up + else: + G.add_edges_from( ((node,nbr) for node,nbrlist in d.items() + for nbr in nbrlist) ) + return G + + +def to_dict_of_dicts(G,nodelist=None,edge_data=None): + """Return adjacency representation of graph as a dictionary of dictionaries. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list + Use only nodes specified in nodelist + + edge_data : list, optional + If provided, the value of the dictionary will be + set to edge_data for all edges. This is useful to make + an adjacency matrix type representation with 1 as the edge data. + If edgedata is None, the edgedata in G is used to fill the values. + If G is a multigraph, the edgedata is a dict for each pair (u,v). + """ + dod={} + if nodelist is None: + if edge_data is None: + for u,nbrdict in G.adjacency_iter(): + dod[u]=nbrdict.copy() + else: # edge_data is not None + for u,nbrdict in G.adjacency_iter(): + dod[u]=dod.fromkeys(nbrdict, edge_data) + else: # nodelist is not None + if edge_data is None: + for u in nodelist: + dod[u]={} + for v,data in ((v,data) for v,data in G[u].items() if v in nodelist): + dod[u][v]=data + else: # nodelist and edge_data are not None + for u in nodelist: + dod[u]={} + for v in ( v for v in G[u] if v in nodelist): + dod[u][v]=edge_data + return dod + +def from_dict_of_dicts(d,create_using=None,multigraph_input=False): + """Return a graph from a dictionary of dictionaries. + + Parameters + ---------- + d : dictionary of dictionaries + A dictionary of dictionaries adjacency representation. + + create_using : NetworkX graph + Use specified graph for result. Otherwise a new graph is created. + + multigraph_input : bool (default False) + When True, the values of the inner dict are assumed + to be containers of edge data for multiple edges. + Otherwise this routine assumes the edge data are singletons. + + Examples + -------- + >>> dod= {0: {1:{'weight':1}}} # single edge (0,1) + >>> G=nx.from_dict_of_dicts(dod) + + or + >>> G=nx.Graph(dod) # use Graph constructor + + """ + G=_prep_create_using(create_using) + G.add_nodes_from(d) + # is dict a MultiGraph or MultiDiGraph? + if multigraph_input: + # make a copy of the list of edge data (but not the edge data) + if G.is_directed(): + if G.is_multigraph(): + G.add_edges_from( (u,v,key,data) + for u,nbrs in d.items() + for v,datadict in nbrs.items() + for key,data in datadict.items() + ) + else: + G.add_edges_from( (u,v,data) + for u,nbrs in d.items() + for v,datadict in nbrs.items() + for key,data in datadict.items() + ) + else: # Undirected + if G.is_multigraph(): + seen=set() # don't add both directions of undirected graph + for u,nbrs in d.items(): + for v,datadict in nbrs.items(): + if (u,v) not in seen: + G.add_edges_from( (u,v,key,data) + for key,data in datadict.items() + ) + seen.add((v,u)) + else: + seen=set() # don't add both directions of undirected graph + for u,nbrs in d.items(): + for v,datadict in nbrs.items(): + if (u,v) not in seen: + G.add_edges_from( (u,v,data) + for key,data in datadict.items() ) + seen.add((v,u)) + + else: # not a multigraph to multigraph transfer + if G.is_multigraph() and not G.is_directed(): + # d can have both representations u-v, v-u in dict. Only add one. + # We don't need this check for digraphs since we add both directions, + # or for Graph() since it is done implicitly (parallel edges not allowed) + seen=set() + for u,nbrs in d.items(): + for v,data in nbrs.items(): + if (u,v) not in seen: + G.add_edge(u,v,attr_dict=data) + seen.add((v,u)) + else: + G.add_edges_from( ( (u,v,data) + for u,nbrs in d.items() + for v,data in nbrs.items()) ) + return G + +def to_edgelist(G,nodelist=None): + """Return a list of edges in the graph. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list + Use only nodes specified in nodelist + + """ + if nodelist is None: + return G.edges(data=True) + else: + return G.edges(nodelist,data=True) + +def from_edgelist(edgelist,create_using=None): + """Return a graph from a list of edges. + + Parameters + ---------- + edgelist : list or iterator + Edge tuples + + create_using : NetworkX graph + Use specified graph for result. Otherwise a new graph is created. + + Examples + -------- + >>> edgelist= [(0,1)] # single edge (0,1) + >>> G=nx.from_edgelist(edgelist) + + or + >>> G=nx.Graph(edgelist) # use Graph constructor + + """ + G=_prep_create_using(create_using) + G.add_edges_from(edgelist) + return G + +def to_numpy_matrix(G, nodelist=None, dtype=None, order=None, + multigraph_weight=sum, weight='weight'): + """Return the graph adjacency matrix as a NumPy matrix. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy matrix. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by G.nodes(). + + dtype : NumPy data type, optional + A valid single NumPy data type used to initialize the array. + This must be a simple type such as int or numpy.float64 and + not a compound data type (see to_numpy_recarray) + If None, then the NumPy default is used. + + order : {'C', 'F'}, optional + Whether to store multidimensional data in C- or Fortran-contiguous + (row- or column-wise) order in memory. If None, then the NumPy default + is used. + + multigraph_weight : {sum, min, max}, optional + An operator that determines how weights in multigraphs are handled. + The default is to sum the weights of the multiple edges. + + weight : string or None optional (default='weight') + The edge attribute that holds the numerical value used for + the edge weight. If None then all edge weights are 1. + + + Returns + ------- + M : NumPy matrix + Graph adjacency matrix. + + See Also + -------- + to_numpy_recarray, from_numpy_matrix + + Notes + ----- + The matrix entries are assigned with weight edge attribute. When + an edge does not have the weight attribute, the value of the entry is 1. + For multiple edges, the values of the entries are the sums of the edge + attributes for each edge. + + When `nodelist` does not contain every node in `G`, the matrix is built + from the subgraph of `G` that is induced by the nodes in `nodelist`. + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_edge(0,1,weight=2) + >>> G.add_edge(1,0) + >>> G.add_edge(2,2,weight=3) + >>> G.add_edge(2,2) + >>> nx.to_numpy_matrix(G, nodelist=[0,1,2]) + matrix([[ 0., 2., 0.], + [ 1., 0., 0.], + [ 0., 0., 4.]]) + """ + try: + import numpy as np + except ImportError: + raise ImportError(\ + "to_numpy_matrix() requires numpy: http://scipy.org/ ") + + if nodelist is None: + nodelist = G.nodes() + + nodeset = set(nodelist) + if len(nodelist) != len(nodeset): + msg = "Ambiguous ordering: `nodelist` contained duplicates." + raise nx.NetworkXError(msg) + + nlen=len(nodelist) + undirected = not G.is_directed() + index=dict(zip(nodelist,range(nlen))) + + if G.is_multigraph(): + # Handle MultiGraphs and MultiDiGraphs + # array of nan' to start with, any leftover nans will be converted to 0 + # nans are used so we can use sum, min, max for multigraphs + M = np.zeros((nlen,nlen), dtype=dtype, order=order)+np.nan + # use numpy nan-aware operations + operator={sum:np.nansum, min:np.nanmin, max:np.nanmax} + try: + op=operator[multigraph_weight] + except: + raise ValueError('multigraph_weight must be sum, min, or max') + + for u,v,attrs in G.edges_iter(data=True): + if (u in nodeset) and (v in nodeset): + i,j = index[u],index[v] + e_weight = attrs.get(weight, 1) + M[i,j] = op([e_weight,M[i,j]]) + if undirected: + M[j,i] = M[i,j] + # convert any nans to zeros + M = np.asmatrix(np.nan_to_num(M)) + else: + # Graph or DiGraph, this is much faster than above + M = np.zeros((nlen,nlen), dtype=dtype, order=order) + for u,nbrdict in G.adjacency_iter(): + for v,d in nbrdict.items(): + try: + M[index[u],index[v]]=d.get(weight,1) + except KeyError: + pass + M = np.asmatrix(M) + return M + + +def from_numpy_matrix(A,create_using=None): + """Return a graph from numpy matrix. + + The numpy matrix is interpreted as an adjacency matrix for the graph. + + Parameters + ---------- + A : numpy matrix + An adjacency matrix representation of a graph + + create_using : NetworkX graph + Use specified graph for result. The default is Graph() + + Notes + ----- + If the numpy matrix has a single data type for each matrix entry it + will be converted to an appropriate Python data type. + + If the numpy matrix has a user-specified compound data type the names + of the data fields will be used as attribute keys in the resulting + NetworkX graph. + + See Also + -------- + to_numpy_matrix, to_numpy_recarray + + Examples + -------- + Simple integer weights on edges: + + >>> import numpy + >>> A=numpy.matrix([[1,1],[2,1]]) + >>> G=nx.from_numpy_matrix(A) + + User defined compound data type on edges: + + >>> import numpy + >>> dt=[('weight',float),('cost',int)] + >>> A=numpy.matrix([[(1.0,2)]],dtype=dt) + >>> G=nx.from_numpy_matrix(A) + >>> G.edges(data=True) + [(0, 0, {'cost': 2, 'weight': 1.0})] + """ + kind_to_python_type={'f':float, + 'i':int, + 'u':int, + 'b':bool, + 'c':complex, + 'S':str, + 'V':'void'} + + try: # Python 3.x + blurb = chr(1245) # just to trigger the exception + kind_to_python_type['U']=str + except ValueError: # Python 2.6+ + kind_to_python_type['U']=unicode + + # This should never fail if you have created a numpy matrix with numpy... + try: + import numpy as np + except ImportError: + raise ImportError(\ + "from_numpy_matrix() requires numpy: http://scipy.org/ ") + + G=_prep_create_using(create_using) + n,m=A.shape + if n!=m: + raise nx.NetworkXError("Adjacency matrix is not square.", + "nx,ny=%s"%(A.shape,)) + dt=A.dtype + try: + python_type=kind_to_python_type[dt.kind] + except: + raise TypeError("Unknown numpy data type: %s"%dt) + + # make sure we get isolated nodes + G.add_nodes_from(range(n)) + # get a list of edges + x,y=np.asarray(A).nonzero() + + # handle numpy constructed data type + if python_type is 'void': + fields=sorted([(offset,dtype,name) for name,(dtype,offset) in + A.dtype.fields.items()]) + for (u,v) in zip(x,y): + attr={} + for (offset,dtype,name),val in zip(fields,A[u,v]): + attr[name]=kind_to_python_type[dtype.kind](val) + G.add_edge(u,v,attr) + else: # basic data type + G.add_edges_from( ((u,v,{'weight':python_type(A[u,v])}) + for (u,v) in zip(x,y)) ) + return G + + +def to_numpy_recarray(G,nodelist=None, + dtype=[('weight',float)], + order=None): + """Return the graph adjacency matrix as a NumPy recarray. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy matrix. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by G.nodes(). + + dtype : NumPy data-type, optional + A valid NumPy named dtype used to initialize the NumPy recarray. + The data type names are assumed to be keys in the graph edge attribute + dictionary. + + order : {'C', 'F'}, optional + Whether to store multidimensional data in C- or Fortran-contiguous + (row- or column-wise) order in memory. If None, then the NumPy default + is used. + + Returns + ------- + M : NumPy recarray + The graph with specified edge data as a Numpy recarray + + Notes + ----- + When `nodelist` does not contain every node in `G`, the matrix is built + from the subgraph of `G` that is induced by the nodes in `nodelist`. + + Examples + -------- + >>> G = nx.Graph() + >>> G.add_edge(1,2,weight=7.0,cost=5) + >>> A=nx.to_numpy_recarray(G,dtype=[('weight',float),('cost',int)]) + >>> print(A.weight) + [[ 0. 7.] + [ 7. 0.]] + >>> print(A.cost) + [[0 5] + [5 0]] + """ + try: + import numpy as np + except ImportError: + raise ImportError(\ + "to_numpy_matrix() requires numpy: http://scipy.org/ ") + + if G.is_multigraph(): + raise nx.NetworkXError("Not implemented for multigraphs.") + + if nodelist is None: + nodelist = G.nodes() + + nodeset = set(nodelist) + if len(nodelist) != len(nodeset): + msg = "Ambiguous ordering: `nodelist` contained duplicates." + raise nx.NetworkXError(msg) + + nlen=len(nodelist) + undirected = not G.is_directed() + index=dict(zip(nodelist,range(nlen))) + M = np.zeros((nlen,nlen), dtype=dtype, order=order) + + names=M.dtype.names + for u,v,attrs in G.edges_iter(data=True): + if (u in nodeset) and (v in nodeset): + i,j = index[u],index[v] + values=tuple([attrs[n] for n in names]) + M[i,j] = values + if undirected: + M[j,i] = M[i,j] + + return M.view(np.recarray) + + +def to_scipy_sparse_matrix(G, nodelist=None, dtype=None, + weight='weight', format='csr'): + """Return the graph adjacency matrix as a SciPy sparse matrix. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy matrix. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by G.nodes(). + + dtype : NumPy data-type, optional + A valid NumPy dtype used to initialize the array. If None, then the + NumPy default is used. + + weight : string or None optional (default='weight') + The edge attribute that holds the numerical value used for + the edge weight. If None then all edge weights are 1. + + format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'} + The type of the matrix to be returned (default 'csr'). For + some algorithms different implementations of sparse matrices + can perform better. See [1]_ for details. + + Returns + ------- + M : SciPy sparse matrix + Graph adjacency matrix. + + Notes + ----- + The matrix entries are populated using the edge attribute held in + parameter weight. When an edge does not have that attribute, the + value of the entry is 1. + + For multiple edges the matrix values are the sums of the edge weights. + + When `nodelist` does not contain every node in `G`, the matrix is built + from the subgraph of `G` that is induced by the nodes in `nodelist`. + + Uses coo_matrix format. To convert to other formats specify the + format= keyword. + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_edge(0,1,weight=2) + >>> G.add_edge(1,0) + >>> G.add_edge(2,2,weight=3) + >>> G.add_edge(2,2) + >>> S = nx.to_scipy_sparse_matrix(G, nodelist=[0,1,2]) + >>> print(S.todense()) + [[0 2 0] + [1 0 0] + [0 0 4]] + + References + ---------- + .. [1] Scipy Dev. References, "Sparse Matrices", + http://docs.scipy.org/doc/scipy/reference/sparse.html + """ + try: + from scipy import sparse + except ImportError: + raise ImportError(\ + "to_scipy_sparse_matrix() requires scipy: http://scipy.org/ ") + + if nodelist is None: + nodelist = G + nlen = len(nodelist) + if nlen == 0: + raise nx.NetworkXError("Graph has no nodes or edges") + + if len(nodelist) != len(set(nodelist)): + msg = "Ambiguous ordering: `nodelist` contained duplicates." + raise nx.NetworkXError(msg) + + index = dict(zip(nodelist,range(nlen))) + if G.number_of_edges() == 0: + row,col,data=[],[],[] + else: + row,col,data=zip(*((index[u],index[v],d.get(weight,1)) + for u,v,d in G.edges_iter(nodelist, data=True) + if u in index and v in index)) + if G.is_directed(): + M = sparse.coo_matrix((data,(row,col)),shape=(nlen,nlen), dtype=dtype) + else: + # symmetrize matrix + M = sparse.coo_matrix((data+data,(row+col,col+row)),shape=(nlen,nlen), + dtype=dtype) + try: + return M.asformat(format) + except AttributeError: + raise nx.NetworkXError("Unknown sparse matrix format: %s"%format) + +def from_scipy_sparse_matrix(A,create_using=None): + """Return a graph from scipy sparse matrix adjacency list. + + Parameters + ---------- + A : scipy sparse matrix + An adjacency matrix representation of a graph + + create_using : NetworkX graph + Use specified graph for result. The default is Graph() + + Examples + -------- + >>> import scipy.sparse + >>> A=scipy.sparse.eye(2,2,1) + >>> G=nx.from_scipy_sparse_matrix(A) + + """ + G=_prep_create_using(create_using) + + # convert all formats to lil - not the most efficient way + AA=A.tolil() + n,m=AA.shape + + if n!=m: + raise nx.NetworkXError(\ + "Adjacency matrix is not square. nx,ny=%s"%(A.shape,)) + G.add_nodes_from(range(n)) # make sure we get isolated nodes + + for i,row in enumerate(AA.rows): + for pos,j in enumerate(row): + G.add_edge(i,j,**{'weight':AA.data[i][pos]}) + return G + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/__init__.py new file mode 100644 index 0000000..211457e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/__init__.py @@ -0,0 +1,20 @@ +# graph drawing and interface to graphviz +import sys +from networkx.drawing.layout import * +from networkx.drawing.nx_pylab import * + +# graphviz interface +# prefer pygraphviz/agraph (it's faster) +from networkx.drawing.nx_agraph import * +try: + import pydot + import networkx.drawing.nx_pydot + from networkx.drawing.nx_pydot import * +except ImportError: + pass +try: + import pygraphviz + from networkx.drawing.nx_agraph import * +except ImportError: + pass + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/layout.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/layout.py new file mode 100644 index 0000000..671d3b6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/layout.py @@ -0,0 +1,540 @@ +""" +****** +Layout +****** + +Node positioning algorithms for graph drawing. +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nDan Schult(dschult@colgate.edu)""" +__all__ = ['circular_layout', + 'random_layout', + 'shell_layout', + 'spring_layout', + 'spectral_layout', + 'fruchterman_reingold_layout'] + +def random_layout(G,dim=2): + """Position nodes uniformly at random in the unit square. + + For every node, a position is generated by choosing each of dim + coordinates uniformly at random on the interval [0.0, 1.0). + + NumPy (http://scipy.org) is required for this function. + + Parameters + ---------- + G : NetworkX graph + A position will be assigned to every node in G. + + dim : int + Dimension of layout. + + Returns + ------- + dict : + A dictionary of positions keyed by node + + Examples + -------- + >>> G = nx.lollipop_graph(4, 3) + >>> pos = nx.random_layout(G) + + """ + try: + import numpy as np + except ImportError: + raise ImportError("random_layout() requires numpy: http://scipy.org/ ") + n=len(G) + pos=np.asarray(np.random.random((n,dim)),dtype=np.float32) + return dict(zip(G,pos)) + + +def circular_layout(G, dim=2, scale=1): + # dim=2 only + """Position nodes on a circle. + + Parameters + ---------- + G : NetworkX graph + + dim : int + Dimension of layout, currently only dim=2 is supported + + scale : float + Scale factor for positions + + Returns + ------- + dict : + A dictionary of positions keyed by node + + Examples + -------- + >>> G=nx.path_graph(4) + >>> pos=nx.circular_layout(G) + + Notes + ------ + This algorithm currently only works in two dimensions and does not + try to minimize edge crossings. + + """ + try: + import numpy as np + except ImportError: + raise ImportError("circular_layout() requires numpy: http://scipy.org/ ") + if len(G)==0: + return {} + if len(G)==1: + return {G.nodes()[0]:(1,)*dim} + t=np.arange(0,2.0*np.pi,2.0*np.pi/len(G),dtype=np.float32) + pos=np.transpose(np.array([np.cos(t),np.sin(t)])) + pos=_rescale_layout(pos,scale=scale) + return dict(zip(G,pos)) + +def shell_layout(G,nlist=None,dim=2,scale=1): + """Position nodes in concentric circles. + + Parameters + ---------- + G : NetworkX graph + + nlist : list of lists + List of node lists for each shell. + + dim : int + Dimension of layout, currently only dim=2 is supported + + scale : float + Scale factor for positions + + Returns + ------- + dict : + A dictionary of positions keyed by node + + Examples + -------- + >>> G=nx.path_graph(4) + >>> shells=[[0],[1,2,3]] + >>> pos=nx.shell_layout(G,shells) + + Notes + ------ + This algorithm currently only works in two dimensions and does not + try to minimize edge crossings. + + """ + try: + import numpy as np + except ImportError: + raise ImportError("shell_layout() requires numpy: http://scipy.org/ ") + if len(G)==0: + return {} + if len(G)==1: + return {G.nodes()[0]:(1,)*dim} + if nlist==None: + nlist=[G.nodes()] # draw the whole graph in one shell + + if len(nlist[0])==1: + radius=0.0 # single node at center + else: + radius=1.0 # else start at r=1 + + npos={} + for nodes in nlist: + t=np.arange(0,2.0*np.pi,2.0*np.pi/len(nodes),dtype=np.float32) + pos=np.transpose(np.array([radius*np.cos(t),radius*np.sin(t)])) + npos.update(zip(nodes,pos)) + radius+=1.0 + + # FIXME: rescale + return npos + + +def fruchterman_reingold_layout(G,dim=2,k=None, + pos=None, + fixed=None, + iterations=50, + weight='weight', + scale=1.0): + """Position nodes using Fruchterman-Reingold force-directed algorithm. + + Parameters + ---------- + G : NetworkX graph + + dim : int + Dimension of layout + + k : float (default=None) + Optimal distance between nodes. If None the distance is set to + 1/sqrt(n) where n is the number of nodes. Increase this value + to move nodes farther apart. + + + pos : dict or None optional (default=None) + Initial positions for nodes as a dictionary with node as keys + and values as a list or tuple. If None, then nuse random initial + positions. + + fixed : list or None optional (default=None) + Nodes to keep fixed at initial position. + + iterations : int optional (default=50) + Number of iterations of spring-force relaxation + + weight : string or None optional (default='weight') + The edge attribute that holds the numerical value used for + the edge weight. If None, then all edge weights are 1. + + scale : float (default=1.0) + Scale factor for positions. The nodes are positioned + in a box of size [0,scale] x [0,scale]. + + + Returns + ------- + dict : + A dictionary of positions keyed by node + + Examples + -------- + >>> G=nx.path_graph(4) + >>> pos=nx.spring_layout(G) + + # The same using longer function name + >>> pos=nx.fruchterman_reingold_layout(G) + """ + try: + import numpy as np + except ImportError: + raise ImportError("fruchterman_reingold_layout() requires numpy: http://scipy.org/ ") + if fixed is not None: + nfixed=dict(zip(G,range(len(G)))) + fixed=np.asarray([nfixed[v] for v in fixed]) + + if pos is not None: + pos_arr=np.asarray(np.random.random((len(G),dim))) + for i,n in enumerate(G): + if n in pos: + pos_arr[i]=np.asarray(pos[n]) + else: + pos_arr=None + + if len(G)==0: + return {} + if len(G)==1: + return {G.nodes()[0]:(1,)*dim} + + try: + # Sparse matrix + if len(G) < 500: # sparse solver for large graphs + raise ValueError + A=nx.to_scipy_sparse_matrix(G,weight=weight,dtype='f') + pos=_sparse_fruchterman_reingold(A,dim,k,pos_arr,fixed,iterations) + except: + A=nx.to_numpy_matrix(G,weight=weight) + pos=_fruchterman_reingold(A,dim,k,pos_arr,fixed,iterations) + if fixed is None: + pos=_rescale_layout(pos,scale=scale) + return dict(zip(G,pos)) + +spring_layout=fruchterman_reingold_layout + +def _fruchterman_reingold(A, dim=2, k=None, pos=None, fixed=None, + iterations=50): + # Position nodes in adjacency matrix A using Fruchterman-Reingold + # Entry point for NetworkX graph is fruchterman_reingold_layout() + try: + import numpy as np + except ImportError: + raise ImportError("_fruchterman_reingold() requires numpy: http://scipy.org/ ") + + try: + nnodes,_=A.shape + except AttributeError: + raise nx.NetworkXError( + "fruchterman_reingold() takes an adjacency matrix as input") + + A=np.asarray(A) # make sure we have an array instead of a matrix + + if pos==None: + # random initial positions + pos=np.asarray(np.random.random((nnodes,dim)),dtype=A.dtype) + else: + # make sure positions are of same type as matrix + pos=pos.astype(A.dtype) + + # optimal distance between nodes + if k is None: + k=np.sqrt(1.0/nnodes) + # the initial "temperature" is about .1 of domain area (=1x1) + # this is the largest step allowed in the dynamics. + t=0.1 + # simple cooling scheme. + # linearly step down by dt on each iteration so last iteration is size dt. + dt=t/float(iterations+1) + delta = np.zeros((pos.shape[0],pos.shape[0],pos.shape[1]),dtype=A.dtype) + # the inscrutable (but fast) version + # this is still O(V^2) + # could use multilevel methods to speed this up significantly + for iteration in range(iterations): + # matrix of difference between points + for i in range(pos.shape[1]): + delta[:,:,i]= pos[:,i,None]-pos[:,i] + # distance between points + distance=np.sqrt((delta**2).sum(axis=-1)) + # enforce minimum distance of 0.01 + distance=np.where(distance<0.01,0.01,distance) + # displacement "force" + displacement=np.transpose(np.transpose(delta)*\ + (k*k/distance**2-A*distance/k))\ + .sum(axis=1) + # update positions + length=np.sqrt((displacement**2).sum(axis=1)) + length=np.where(length<0.01,0.1,length) + delta_pos=np.transpose(np.transpose(displacement)*t/length) + if fixed is not None: + # don't change positions of fixed nodes + delta_pos[fixed]=0.0 + pos+=delta_pos + # cool temperature + t-=dt + pos=_rescale_layout(pos) + return pos + + +def _sparse_fruchterman_reingold(A, dim=2, k=None, pos=None, fixed=None, + iterations=50): + # Position nodes in adjacency matrix A using Fruchterman-Reingold + # Entry point for NetworkX graph is fruchterman_reingold_layout() + # Sparse version + try: + import numpy as np + except ImportError: + raise ImportError("_sparse_fruchterman_reingold() requires numpy: http://scipy.org/ ") + try: + nnodes,_=A.shape + except AttributeError: + raise nx.NetworkXError( + "fruchterman_reingold() takes an adjacency matrix as input") + try: + from scipy.sparse import spdiags,coo_matrix + except ImportError: + raise ImportError("_sparse_fruchterman_reingold() scipy numpy: http://scipy.org/ ") + # make sure we have a LIst of Lists representation + try: + A=A.tolil() + except: + A=(coo_matrix(A)).tolil() + + if pos==None: + # random initial positions + pos=np.asarray(np.random.random((nnodes,dim)),dtype=A.dtype) + else: + # make sure positions are of same type as matrix + pos=pos.astype(A.dtype) + + # no fixed nodes + if fixed==None: + fixed=[] + + # optimal distance between nodes + if k is None: + k=np.sqrt(1.0/nnodes) + # the initial "temperature" is about .1 of domain area (=1x1) + # this is the largest step allowed in the dynamics. + t=0.1 + # simple cooling scheme. + # linearly step down by dt on each iteration so last iteration is size dt. + dt=t/float(iterations+1) + + displacement=np.zeros((dim,nnodes)) + for iteration in range(iterations): + displacement*=0 + # loop over rows + for i in range(A.shape[0]): + if i in fixed: + continue + # difference between this row's node position and all others + delta=(pos[i]-pos).T + # distance between points + distance=np.sqrt((delta**2).sum(axis=0)) + # enforce minimum distance of 0.01 + distance=np.where(distance<0.01,0.01,distance) + # the adjacency matrix row + Ai=np.asarray(A.getrowview(i).toarray()) + # displacement "force" + displacement[:,i]+=\ + (delta*(k*k/distance**2-Ai*distance/k)).sum(axis=1) + # update positions + length=np.sqrt((displacement**2).sum(axis=0)) + length=np.where(length<0.01,0.1,length) + pos+=(displacement*t/length).T + # cool temperature + t-=dt + pos=_rescale_layout(pos) + return pos + + +def spectral_layout(G, dim=2, weight='weight', scale=1): + """Position nodes using the eigenvectors of the graph Laplacian. + + Parameters + ---------- + G : NetworkX graph + + dim : int + Dimension of layout + + weight : string or None optional (default='weight') + The edge attribute that holds the numerical value used for + the edge weight. If None, then all edge weights are 1. + + scale : float + Scale factor for positions + + Returns + ------- + dict : + A dictionary of positions keyed by node + + Examples + -------- + >>> G=nx.path_graph(4) + >>> pos=nx.spectral_layout(G) + + Notes + ----- + Directed graphs will be considered as unidrected graphs when + positioning the nodes. + + For larger graphs (>500 nodes) this will use the SciPy sparse + eigenvalue solver (ARPACK). + """ + # handle some special cases that break the eigensolvers + try: + import numpy as np + except ImportError: + raise ImportError("spectral_layout() requires numpy: http://scipy.org/ ") + if len(G)<=2: + if len(G)==0: + pos=np.array([]) + elif len(G)==1: + pos=np.array([[1,1]]) + else: + pos=np.array([[0,0.5],[1,0.5]]) + return dict(zip(G,pos)) + try: + # Sparse matrix + if len(G)< 500: # dense solver is faster for small graphs + raise ValueError + A=nx.to_scipy_sparse_matrix(G, weight=weight,dtype='f') + # Symmetrize directed graphs + if G.is_directed(): + A=A+np.transpose(A) + pos=_sparse_spectral(A,dim) + except (ImportError,ValueError): + # Dense matrix + A=nx.to_numpy_matrix(G, weight=weight) + # Symmetrize directed graphs + if G.is_directed(): + A=A+np.transpose(A) + pos=_spectral(A,dim) + + pos=_rescale_layout(pos,scale) + return dict(zip(G,pos)) + + +def _spectral(A, dim=2): + # Input adjacency matrix A + # Uses dense eigenvalue solver from numpy + try: + import numpy as np + except ImportError: + raise ImportError("spectral_layout() requires numpy: http://scipy.org/ ") + try: + nnodes,_=A.shape + except AttributeError: + raise nx.NetworkXError(\ + "spectral() takes an adjacency matrix as input") + + # form Laplacian matrix + # make sure we have an array instead of a matrix + A=np.asarray(A) + I=np.identity(nnodes,dtype=A.dtype) + D=I*np.sum(A,axis=1) # diagonal of degrees + L=D-A + + eigenvalues,eigenvectors=np.linalg.eig(L) + # sort and keep smallest nonzero + index=np.argsort(eigenvalues)[1:dim+1] # 0 index is zero eigenvalue + return np.real(eigenvectors[:,index]) + +def _sparse_spectral(A,dim=2): + # Input adjacency matrix A + # Uses sparse eigenvalue solver from scipy + # Could use multilevel methods here, see Koren "On spectral graph drawing" + try: + import numpy as np + from scipy.sparse import spdiags + except ImportError: + raise ImportError("_sparse_spectral() requires scipy & numpy: http://scipy.org/ ") + try: + from scipy.sparse.linalg.eigen import eigsh + except ImportError: + # scipy <0.9.0 names eigsh differently + from scipy.sparse.linalg import eigen_symmetric as eigsh + try: + nnodes,_=A.shape + except AttributeError: + raise nx.NetworkXError(\ + "sparse_spectral() takes an adjacency matrix as input") + + # form Laplacian matrix + data=np.asarray(A.sum(axis=1).T) + D=spdiags(data,0,nnodes,nnodes) + L=D-A + + k=dim+1 + # number of Lanczos vectors for ARPACK solver.What is the right scaling? + ncv=max(2*k+1,int(np.sqrt(nnodes))) + # return smallest k eigenvalues and eigenvectors + eigenvalues,eigenvectors=eigsh(L,k,which='SM',ncv=ncv) + index=np.argsort(eigenvalues)[1:k] # 0 index is zero eigenvalue + return np.real(eigenvectors[:,index]) + + +def _rescale_layout(pos,scale=1): + # rescale to (0,pscale) in all axes + + # shift origin to (0,0) + lim=0 # max coordinate for all axes + for i in range(pos.shape[1]): + pos[:,i]-=pos[:,i].min() + lim=max(pos[:,i].max(),lim) + # rescale to (0,scale) in all directions, preserves aspect + for i in range(pos.shape[1]): + pos[:,i]*=scale/lim + return pos + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_agraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_agraph.py new file mode 100644 index 0000000..ff61253 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_agraph.py @@ -0,0 +1,447 @@ +""" +*************** +Graphviz AGraph +*************** + +Interface to pygraphviz AGraph class. + +Examples +-------- +>>> G=nx.complete_graph(5) +>>> A=nx.to_agraph(G) +>>> H=nx.from_agraph(A) + +See Also +-------- +Pygraphviz: http://networkx.lanl.gov/pygraphviz +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import os +import sys +import tempfile +import networkx as nx +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +__all__ = ['from_agraph', 'to_agraph', + 'write_dot', 'read_dot', + 'graphviz_layout', + 'pygraphviz_layout', + 'view_pygraphviz'] + +def from_agraph(A,create_using=None): + """Return a NetworkX Graph or DiGraph from a PyGraphviz graph. + + Parameters + ---------- + A : PyGraphviz AGraph + A graph created with PyGraphviz + + create_using : NetworkX graph class instance + The output is created using the given graph class instance + + Examples + -------- + >>> K5=nx.complete_graph(5) + >>> A=nx.to_agraph(K5) + >>> G=nx.from_agraph(A) + >>> G=nx.from_agraph(A) + + + Notes + ----- + The Graph G will have a dictionary G.graph_attr containing + the default graphviz attributes for graphs, nodes and edges. + + Default node attributes will be in the dictionary G.node_attr + which is keyed by node. + + Edge attributes will be returned as edge data in G. With + edge_attr=False the edge data will be the Graphviz edge weight + attribute or the value 1 if no edge weight attribute is found. + + """ + if create_using is None: + if A.is_directed(): + if A.is_strict(): + create_using=nx.DiGraph() + else: + create_using=nx.MultiDiGraph() + else: + if A.is_strict(): + create_using=nx.Graph() + else: + create_using=nx.MultiGraph() + + # assign defaults + N=nx.empty_graph(0,create_using) + N.name='' + if A.name is not None: + N.name=A.name + + # add nodes, attributes to N.node_attr + for n in A.nodes(): + str_attr=dict((str(k),v) for k,v in n.attr.items()) + N.add_node(str(n),**str_attr) + + # add edges, assign edge data as dictionary of attributes + for e in A.edges(): + u,v=str(e[0]),str(e[1]) + attr=dict(e.attr) + str_attr=dict((str(k),v) for k,v in attr.items()) + if not N.is_multigraph(): + if e.name is not None: + str_attr['key']=e.name + N.add_edge(u,v,**str_attr) + else: + N.add_edge(u,v,key=e.name,**str_attr) + + # add default attributes for graph, nodes, and edges + # hang them on N.graph_attr + N.graph['graph']=dict(A.graph_attr) + N.graph['node']=dict(A.node_attr) + N.graph['edge']=dict(A.edge_attr) + return N + +def to_agraph(N): + """Return a pygraphviz graph from a NetworkX graph N. + + Parameters + ---------- + N : NetworkX graph + A graph created with NetworkX + + Examples + -------- + >>> K5=nx.complete_graph(5) + >>> A=nx.to_agraph(K5) + + Notes + ----- + If N has an dict N.graph_attr an attempt will be made first + to copy properties attached to the graph (see from_agraph) + and then updated with the calling arguments if any. + + """ + try: + import pygraphviz + except ImportError: + raise ImportError('requires pygraphviz ', + 'http://networkx.lanl.gov/pygraphviz ', + '(not available for Python3)') + directed=N.is_directed() + strict=N.number_of_selfloops()==0 and not N.is_multigraph() + A=pygraphviz.AGraph(name=N.name,strict=strict,directed=directed) + + # default graph attributes + A.graph_attr.update(N.graph.get('graph',{})) + A.node_attr.update(N.graph.get('node',{})) + A.edge_attr.update(N.graph.get('edge',{})) + + # add nodes + for n,nodedata in N.nodes(data=True): + A.add_node(n,**nodedata) + + # loop over edges + + if N.is_multigraph(): + for u,v,key,edgedata in N.edges_iter(data=True,keys=True): + str_edgedata=dict((k,str(v)) for k,v in edgedata.items()) + A.add_edge(u,v,key=str(key),**str_edgedata) + else: + for u,v,edgedata in N.edges_iter(data=True): + str_edgedata=dict((k,str(v)) for k,v in edgedata.items()) + A.add_edge(u,v,**str_edgedata) + + + return A + +def write_dot(G,path): + """Write NetworkX graph G to Graphviz dot format on path. + + Parameters + ---------- + G : graph + A networkx graph + path : filename + Filename or file handle to write + """ + try: + import pygraphviz + except ImportError: + raise ImportError('requires pygraphviz ', + 'http://networkx.lanl.gov/pygraphviz ', + '(not available for Python3)') + A=to_agraph(G) + A.write(path) + A.clear() + return + +def read_dot(path): + """Return a NetworkX graph from a dot file on path. + + Parameters + ---------- + path : file or string + File name or file handle to read. + """ + try: + import pygraphviz + except ImportError: + raise ImportError('read_dot() requires pygraphviz ', + 'http://networkx.lanl.gov/pygraphviz ', + '(not available for Python3)') + A=pygraphviz.AGraph(file=path) + return from_agraph(A) + + +def graphviz_layout(G,prog='neato',root=None, args=''): + """Create node positions for G using Graphviz. + + Parameters + ---------- + G : NetworkX graph + A graph created with NetworkX + prog : string + Name of Graphviz layout program + root : string, optional + Root node for twopi layout + args : string, optional + Extra arguments to Graphviz layout program + + Returns : dictionary + Dictionary of x,y, positions keyed by node. + + Examples + -------- + >>> G=nx.petersen_graph() + >>> pos=nx.graphviz_layout(G) + >>> pos=nx.graphviz_layout(G,prog='dot') + + Notes + ----- + This is a wrapper for pygraphviz_layout. + + """ + return pygraphviz_layout(G,prog=prog,root=root,args=args) + +def pygraphviz_layout(G,prog='neato',root=None, args=''): + """Create node positions for G using Graphviz. + + Parameters + ---------- + G : NetworkX graph + A graph created with NetworkX + prog : string + Name of Graphviz layout program + root : string, optional + Root node for twopi layout + args : string, optional + Extra arguments to Graphviz layout program + + Returns : dictionary + Dictionary of x,y, positions keyed by node. + + Examples + -------- + >>> G=nx.petersen_graph() + >>> pos=nx.graphviz_layout(G) + >>> pos=nx.graphviz_layout(G,prog='dot') + + """ + try: + import pygraphviz + except ImportError: + raise ImportError('requires pygraphviz ', + 'http://networkx.lanl.gov/pygraphviz ', + '(not available for Python3)') + if root is not None: + args+="-Groot=%s"%root + A=to_agraph(G) + A.layout(prog=prog,args=args) + node_pos={} + for n in G: + node=pygraphviz.Node(A,n) + try: + xx,yy=node.attr["pos"].split(',') + node_pos[n]=(float(xx),float(yy)) + except: + print("no position for node",n) + node_pos[n]=(0.0,0.0) + return node_pos + +@nx.utils.open_file(5, 'w') +def view_pygraphviz(G, edgelabel=None, prog='dot', args='', + suffix='', path=None): + """Views the graph G using the specified layout algorithm. + + Parameters + ---------- + G : NetworkX graph + The machine to draw. + edgelabel : str, callable, None + If a string, then it specifes the edge attribute to be displayed + on the edge labels. If a callable, then it is called for each + edge and it should return the string to be displayed on the edges. + The function signature of `edgelabel` should be edgelabel(data), + where `data` is the edge attribute dictionary. + prog : string + Name of Graphviz layout program. + args : str + Additional arguments to pass to the Graphviz layout program. + suffix : str + If `filename` is None, we save to a temporary file. The value of + `suffix` will appear at the tail end of the temporary filename. + path : str, None + The filename used to save the image. If None, save to a temporary + file. File formats are the same as those from pygraphviz.agraph.draw. + + Returns + ------- + path : str + The filename of the generated image. + A : PyGraphviz graph + The PyGraphviz graph instance used to generate the image. + + Notes + ----- + If this function is called in succession too quickly, sometimes the + image is not displayed. So you might consider time.sleep(.5) between + calls if you experience problems. + + """ + if not len(G): + raise nx.NetworkXException("An empty graph cannot be drawn.") + + import pygraphviz + + # If we are providing default values for graphviz, these must be set + # before any nodes or edges are added to the PyGraphviz graph object. + # The reason for this is that default values only affect incoming objects. + # If you change the default values after the objects have been added, + # then they inherit no value and are set only if explicitly set. + + # to_agraph() uses these values. + attrs = ['edge', 'node', 'graph'] + for attr in attrs: + if attr not in G.graph: + G.graph[attr] = {} + + # These are the default values. + edge_attrs = {'fontsize': '10'} + node_attrs = {'style': 'filled', + 'fillcolor': '#0000FF40', + 'height': '0.75', + 'width': '0.75', + 'shape': 'circle'} + graph_attrs = {} + + def update_attrs(which, attrs): + # Update graph attributes. Return list of those which were added. + added = [] + for k,v in attrs.items(): + if k not in G.graph[which]: + G.graph[which][k] = v + added.append(k) + + def clean_attrs(which, added): + # Remove added attributes + for attr in added: + del G.graph[which][attr] + if not G.graph[which]: + del G.graph[which] + + # Update all default values + update_attrs('edge', edge_attrs) + update_attrs('node', node_attrs) + update_attrs('graph', graph_attrs) + + # Convert to agraph, so we inherit default values + A = to_agraph(G) + + # Remove the default values we added to the original graph. + clean_attrs('edge', edge_attrs) + clean_attrs('node', node_attrs) + clean_attrs('graph', graph_attrs) + + # If the user passed in an edgelabel, we update the labels for all edges. + if edgelabel is not None: + if not hasattr(edgelabel, '__call__'): + def func(data): + return ''.join([" ", str(data[edgelabel]), " "]) + else: + func = edgelabel + + # update all the edge labels + if G.is_multigraph(): + for u,v,key,data in G.edges_iter(keys=True, data=True): + # PyGraphviz doesn't convert the key to a string. See #339 + edge = A.get_edge(u,v,str(key)) + edge.attr['label'] = str(func(data)) + else: + for u,v,data in G.edges_iter(data=True): + edge = A.get_edge(u,v) + edge.attr['label'] = str(func(data)) + + if path is None: + ext = 'png' + if suffix: + suffix = '_%s.%s' % (suffix, ext) + else: + suffix = '.%s' % (ext,) + path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) + else: + # Assume the decorator worked and it is a file-object. + pass + + display_pygraphviz(A, path=path, prog=prog, args=args) + + return path.name, A + +def display_pygraphviz(graph, path, format=None, prog=None, args=''): + """Internal function to display a graph in OS dependent manner. + + Parameters + ---------- + graph : PyGraphviz graph + A PyGraphviz AGraph instance. + path : file object + An already opened file object that will be closed. + format : str, None + An attempt is made to guess the output format based on the extension + of the filename. If that fails, the value of `format` is used. + prog : string + Name of Graphviz layout program. + args : str + Additional arguments to pass to the Graphviz layout program. + + Notes + ----- + If this function is called in succession too quickly, sometimes the + image is not displayed. So you might consider time.sleep(.5) between + calls if you experience problems. + + """ + if format is None: + filename = path.name + format = os.path.splitext(filename)[1].lower()[1:] + if not format: + # Let the draw() function use its default + format = None + + # Save to a file and display in the default viewer. + # We must close the file before viewing it. + graph.draw(path, format, prog, args) + path.close() + nx.utils.default_opener(filename) + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import pygraphviz + except: + raise SkipTest("pygraphviz not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pydot.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pydot.py new file mode 100644 index 0000000..183f6ab --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pydot.py @@ -0,0 +1,287 @@ +""" +***** +Pydot +***** + +Import and export NetworkX graphs in Graphviz dot format using pydot. + +Either this module or nx_pygraphviz can be used to interface with graphviz. + +See Also +-------- +Pydot: http://code.google.com/p/pydot/ +Graphviz: http://www.research.att.com/sw/tools/graphviz/ +DOT Language: http://www.graphviz.org/doc/info/lang.html +""" +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from networkx.utils import open_file, make_str +import networkx as nx +__author__ = """Aric Hagberg (aric.hagberg@gmail.com)""" +__all__ = ['write_dot', 'read_dot', 'graphviz_layout', 'pydot_layout', + 'to_pydot', 'from_pydot'] + +@open_file(1,mode='w') +def write_dot(G,path): + """Write NetworkX graph G to Graphviz dot format on path. + + Path can be a string or a file handle. + """ + try: + import pydot + except ImportError: + raise ImportError("write_dot() requires pydot", + "http://code.google.com/p/pydot/") + P=to_pydot(G) + path.write(P.to_string()) + return + +@open_file(0,mode='r') +def read_dot(path): + """Return a NetworkX MultiGraph or MultiDiGraph from a dot file on path. + + Parameters + ---------- + path : filename or file handle + + Returns + ------- + G : NetworkX multigraph + A MultiGraph or MultiDiGraph. + + Notes + ----- + Use G=nx.Graph(nx.read_dot(path)) to return a Graph instead of a MultiGraph. + """ + try: + import pydot + except ImportError: + raise ImportError("read_dot() requires pydot", + "http://code.google.com/p/pydot/") + + data=path.read() + P=pydot.graph_from_dot_data(data) + return from_pydot(P) + +def from_pydot(P): + """Return a NetworkX graph from a Pydot graph. + + Parameters + ---------- + P : Pydot graph + A graph created with Pydot + + Returns + ------- + G : NetworkX multigraph + A MultiGraph or MultiDiGraph. + + Examples + -------- + >>> K5=nx.complete_graph(5) + >>> A=nx.to_pydot(K5) + >>> G=nx.from_pydot(A) # return MultiGraph + >>> G=nx.Graph(nx.from_pydot(A)) # make a Graph instead of MultiGraph + + """ + if P.get_strict(None): # pydot bug: get_strict() shouldn't take argument + multiedges=False + else: + multiedges=True + + if P.get_type()=='graph': # undirected + if multiedges: + create_using=nx.MultiGraph() + else: + create_using=nx.Graph() + else: + if multiedges: + create_using=nx.MultiDiGraph() + else: + create_using=nx.DiGraph() + + # assign defaults + N=nx.empty_graph(0,create_using) + N.name=P.get_name() + + # add nodes, attributes to N.node_attr + for p in P.get_node_list(): + n=p.get_name().strip('"') + if n in ('node','graph','edge'): + continue + N.add_node(n,**p.get_attributes()) + + # add edges + for e in P.get_edge_list(): + u=e.get_source().strip('"') + v=e.get_destination().strip('"') + attr=e.get_attributes() + N.add_edge(u,v,**attr) + + # add default attributes for graph, nodes, edges + N.graph['graph']=P.get_attributes() + try: + N.graph['node']=P.get_node_defaults()[0] + except:# IndexError,TypeError: + N.graph['node']={} + try: + N.graph['edge']=P.get_edge_defaults()[0] + except:# IndexError,TypeError: + N.graph['edge']={} + return N + +def to_pydot(N, strict=True): + """Return a pydot graph from a NetworkX graph N. + + Parameters + ---------- + N : NetworkX graph + A graph created with NetworkX + + Examples + -------- + >>> K5=nx.complete_graph(5) + >>> P=nx.to_pydot(K5) + + Notes + ----- + + """ + try: + import pydot + except ImportError: + raise ImportError('to_pydot() requires pydot: ' + 'http://code.google.com/p/pydot/') + + # set Graphviz graph type + if N.is_directed(): + graph_type='digraph' + else: + graph_type='graph' + strict=N.number_of_selfloops()==0 and not N.is_multigraph() + + name = N.graph.get('name') + graph_defaults=N.graph.get('graph',{}) + if name is None: + P = pydot.Dot(graph_type=graph_type,strict=strict,**graph_defaults) + else: + P = pydot.Dot('"%s"'%name,graph_type=graph_type,strict=strict, + **graph_defaults) + try: + P.set_node_defaults(**N.graph['node']) + except KeyError: + pass + try: + P.set_edge_defaults(**N.graph['edge']) + except KeyError: + pass + + for n,nodedata in N.nodes_iter(data=True): + str_nodedata=dict((k,make_str(v)) for k,v in nodedata.items()) + p=pydot.Node(make_str(n),**str_nodedata) + P.add_node(p) + + if N.is_multigraph(): + for u,v,key,edgedata in N.edges_iter(data=True,keys=True): + str_edgedata=dict((k,make_str(v)) for k,v in edgedata.items()) + edge=pydot.Edge(make_str(u),make_str(v),key=make_str(key),**str_edgedata) + P.add_edge(edge) + + else: + for u,v,edgedata in N.edges_iter(data=True): + str_edgedata=dict((k,make_str(v)) for k,v in edgedata.items()) + edge=pydot.Edge(make_str(u),make_str(v),**str_edgedata) + P.add_edge(edge) + return P + + +def pydot_from_networkx(N): + """Create a Pydot graph from a NetworkX graph.""" + from warnings import warn + warn('pydot_from_networkx is replaced by to_pydot', DeprecationWarning) + return to_pydot(N) + +def networkx_from_pydot(D, create_using=None): + """Create a NetworkX graph from a Pydot graph.""" + from warnings import warn + warn('networkx_from_pydot is replaced by from_pydot', + DeprecationWarning) + return from_pydot(D) + +def graphviz_layout(G,prog='neato',root=None, **kwds): + """Create node positions using Pydot and Graphviz. + + Returns a dictionary of positions keyed by node. + + Examples + -------- + >>> G=nx.complete_graph(4) + >>> pos=nx.graphviz_layout(G) + >>> pos=nx.graphviz_layout(G,prog='dot') + + Notes + ----- + This is a wrapper for pydot_layout. + """ + return pydot_layout(G=G,prog=prog,root=root,**kwds) + + +def pydot_layout(G,prog='neato',root=None, **kwds): + """Create node positions using Pydot and Graphviz. + + Returns a dictionary of positions keyed by node. + + Examples + -------- + >>> G=nx.complete_graph(4) + >>> pos=nx.pydot_layout(G) + >>> pos=nx.pydot_layout(G,prog='dot') + """ + try: + import pydot + except ImportError: + raise ImportError('pydot_layout() requires pydot ', + 'http://code.google.com/p/pydot/') + + P=to_pydot(G) + if root is not None : + P.set("root",make_str(root)) + + D=P.create_dot(prog=prog) + + if D=="": # no data returned + print("Graphviz layout with %s failed"%(prog)) + print() + print("To debug what happened try:") + print("P=pydot_from_networkx(G)") + print("P.write_dot(\"file.dot\")") + print("And then run %s on file.dot"%(prog)) + return + + Q=pydot.graph_from_dot_data(D) + + node_pos={} + for n in G.nodes(): + pydot_node = pydot.Node(make_str(n)).get_name().encode('utf-8') + node=Q.get_node(pydot_node) + + if isinstance(node,list): + node=node[0] + pos=node.get_pos()[1:-1] # strip leading and trailing double quotes + if pos != None: + xx,yy=pos.split(",") + node_pos[n]=(float(xx),float(yy)) + return node_pos + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import pydot + import dot_parser + except: + raise SkipTest("pydot not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pylab.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pylab.py new file mode 100644 index 0000000..c7d0cf6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/nx_pylab.py @@ -0,0 +1,896 @@ +""" +********** +Matplotlib +********** + +Draw networks with matplotlib. + +See Also +-------- + +matplotlib: http://matplotlib.sourceforge.net/ + +pygraphviz: http://networkx.lanl.gov/pygraphviz/ + +""" +# Copyright (C) 2004-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.drawing.layout import shell_layout,\ + circular_layout,spectral_layout,spring_layout,random_layout +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +__all__ = ['draw', + 'draw_networkx', + 'draw_networkx_nodes', + 'draw_networkx_edges', + 'draw_networkx_labels', + 'draw_networkx_edge_labels', + 'draw_circular', + 'draw_random', + 'draw_spectral', + 'draw_spring', + 'draw_shell', + 'draw_graphviz'] + +def draw(G, pos=None, ax=None, hold=None, **kwds): + """Draw the graph G with Matplotlib. + + Draw the graph as a simple representation with no node + labels or edge labels and using the full Matplotlib figure area + and no axis labels by default. See draw_networkx() for more + full-featured drawing that allows title, axis labels etc. + + Parameters + ---------- + G : graph + A networkx graph + + pos : dictionary, optional + A dictionary with nodes as keys and positions as values. + If not specified a spring layout positioning will be computed. + See networkx.layout for functions that compute node positions. + + ax : Matplotlib Axes object, optional + Draw the graph in specified Matplotlib axes. + + hold : bool, optional + Set the Matplotlib hold state. If True subsequent draw + commands will be added to the current axes. + + **kwds : optional keywords + See networkx.draw_networkx() for a description of optional keywords. + + Examples + -------- + >>> G=nx.dodecahedral_graph() + >>> nx.draw(G) + >>> nx.draw(G,pos=nx.spring_layout(G)) # use spring layout + + See Also + -------- + draw_networkx() + draw_networkx_nodes() + draw_networkx_edges() + draw_networkx_labels() + draw_networkx_edge_labels() + + Notes + ----- + This function has the same name as pylab.draw and pyplot.draw + so beware when using + + >>> from networkx import * + + since you might overwrite the pylab.draw function. + + With pyplot use + + >>> import matplotlib.pyplot as plt + >>> import networkx as nx + >>> G=nx.dodecahedral_graph() + >>> nx.draw(G) # networkx draw() + >>> plt.draw() # pyplot draw() + + Also see the NetworkX drawing examples at + http://networkx.lanl.gov/gallery.html + """ + try: + import matplotlib.pyplot as plt + except ImportError: + raise ImportError("Matplotlib required for draw()") + except RuntimeError: + print("Matplotlib unable to open display") + raise + + if ax is None: + cf = plt.gcf() + else: + cf = ax.get_figure() + cf.set_facecolor('w') + if ax is None: + if cf._axstack() is None: + ax=cf.add_axes((0,0,1,1)) + else: + ax=cf.gca() + + # allow callers to override the hold state by passing hold=True|False + b = plt.ishold() + h = kwds.pop('hold', None) + if h is not None: + plt.hold(h) + try: + draw_networkx(G,pos=pos,ax=ax,**kwds) + ax.set_axis_off() + plt.draw_if_interactive() + except: + plt.hold(b) + raise + plt.hold(b) + return + + +def draw_networkx(G, pos=None, with_labels=True, **kwds): + """Draw the graph G using Matplotlib. + + Draw the graph with Matplotlib with options for node positions, + labeling, titles, and many other drawing features. + See draw() for simple drawing without labels or axes. + + Parameters + ---------- + G : graph + A networkx graph + + pos : dictionary, optional + A dictionary with nodes as keys and positions as values. + If not specified a spring layout positioning will be computed. + See networkx.layout for functions that compute node positions. + + with_labels : bool, optional (default=True) + Set to True to draw labels on the nodes. + + ax : Matplotlib Axes object, optional + Draw the graph in the specified Matplotlib axes. + + nodelist : list, optional (default G.nodes()) + Draw only specified nodes + + edgelist : list, optional (default=G.edges()) + Draw only specified edges + + node_size : scalar or array, optional (default=300) + Size of nodes. If an array is specified it must be the + same length as nodelist. + + node_color : color string, or array of floats, (default='r') + Node color. Can be a single color format string, + or a sequence of colors with the same length as nodelist. + If numeric values are specified they will be mapped to + colors using the cmap and vmin,vmax parameters. See + matplotlib.scatter for more details. + + node_shape : string, optional (default='o') + The shape of the node. Specification is as matplotlib.scatter + marker, one of 'so^>v>> G=nx.dodecahedral_graph() + >>> nx.draw(G) + >>> nx.draw(G,pos=nx.spring_layout(G)) # use spring layout + + >>> import matplotlib.pyplot as plt + >>> limits=plt.axis('off') # turn of axis + + Also see the NetworkX drawing examples at + http://networkx.lanl.gov/gallery.html + + See Also + -------- + draw() + draw_networkx_nodes() + draw_networkx_edges() + draw_networkx_labels() + draw_networkx_edge_labels() + """ + try: + import matplotlib.pyplot as plt + except ImportError: + raise ImportError("Matplotlib required for draw()") + except RuntimeError: + print("Matplotlib unable to open display") + raise + + if pos is None: + pos=nx.drawing.spring_layout(G) # default to spring layout + + node_collection=draw_networkx_nodes(G, pos, **kwds) + edge_collection=draw_networkx_edges(G, pos, **kwds) + if with_labels: + draw_networkx_labels(G, pos, **kwds) + plt.draw_if_interactive() + +def draw_networkx_nodes(G, pos, + nodelist=None, + node_size=300, + node_color='r', + node_shape='o', + alpha=1.0, + cmap=None, + vmin=None, + vmax=None, + ax=None, + linewidths=None, + label = None, + **kwds): + """Draw the nodes of the graph G. + + This draws only the nodes of the graph G. + + Parameters + ---------- + G : graph + A networkx graph + + pos : dictionary + A dictionary with nodes as keys and positions as values. + If not specified a spring layout positioning will be computed. + See networkx.layout for functions that compute node positions. + + ax : Matplotlib Axes object, optional + Draw the graph in the specified Matplotlib axes. + + nodelist : list, optional + Draw only specified nodes (default G.nodes()) + + node_size : scalar or array + Size of nodes (default=300). If an array is specified it must be the + same length as nodelist. + + node_color : color string, or array of floats + Node color. Can be a single color format string (default='r'), + or a sequence of colors with the same length as nodelist. + If numeric values are specified they will be mapped to + colors using the cmap and vmin,vmax parameters. See + matplotlib.scatter for more details. + + node_shape : string + The shape of the node. Specification is as matplotlib.scatter + marker, one of 'so^>v>> G=nx.dodecahedral_graph() + >>> nodes=nx.draw_networkx_nodes(G,pos=nx.spring_layout(G)) + + Also see the NetworkX drawing examples at + http://networkx.lanl.gov/gallery.html + + See Also + -------- + draw() + draw_networkx() + draw_networkx_edges() + draw_networkx_labels() + draw_networkx_edge_labels() + """ + try: + import matplotlib.pyplot as plt + import numpy + except ImportError: + raise ImportError("Matplotlib required for draw()") + except RuntimeError: + print("Matplotlib unable to open display") + raise + + + if ax is None: + ax=plt.gca() + + if nodelist is None: + nodelist=G.nodes() + + if not nodelist or len(nodelist)==0: # empty nodelist, no drawing + return None + + try: + xy=numpy.asarray([pos[v] for v in nodelist]) + except KeyError as e: + raise nx.NetworkXError('Node %s has no position.'%e) + except ValueError: + raise nx.NetworkXError('Bad value in node positions.') + + + + node_collection=ax.scatter(xy[:,0], xy[:,1], + s=node_size, + c=node_color, + marker=node_shape, + cmap=cmap, + vmin=vmin, + vmax=vmax, + alpha=alpha, + linewidths=linewidths, + label=label) + + node_collection.set_zorder(2) + return node_collection + + +def draw_networkx_edges(G, pos, + edgelist=None, + width=1.0, + edge_color='k', + style='solid', + alpha=None, + edge_cmap=None, + edge_vmin=None, + edge_vmax=None, + ax=None, + arrows=True, + label=None, + **kwds): + """Draw the edges of the graph G. + + This draws only the edges of the graph G. + + Parameters + ---------- + G : graph + A networkx graph + + pos : dictionary + A dictionary with nodes as keys and positions as values. + If not specified a spring layout positioning will be computed. + See networkx.layout for functions that compute node positions. + + edgelist : collection of edge tuples + Draw only specified edges(default=G.edges()) + + width : float + Line width of edges (default =1.0) + + edge_color : color string, or array of floats + Edge color. Can be a single color format string (default='r'), + or a sequence of colors with the same length as edgelist. + If numeric values are specified they will be mapped to + colors using the edge_cmap and edge_vmin,edge_vmax parameters. + + style : string + Edge line style (default='solid') (solid|dashed|dotted,dashdot) + + alpha : float + The edge transparency (default=1.0) + + edge_ cmap : Matplotlib colormap + Colormap for mapping intensities of edges (default=None) + + edge_vmin,edge_vmax : floats + Minimum and maximum for edge colormap scaling (default=None) + + ax : Matplotlib Axes object, optional + Draw the graph in the specified Matplotlib axes. + + arrows : bool, optional (default=True) + For directed graphs, if True draw arrowheads. + + label : [None| string] + Label for legend + + Notes + ----- + For directed graphs, "arrows" (actually just thicker stubs) are drawn + at the head end. Arrows can be turned off with keyword arrows=False. + Yes, it is ugly but drawing proper arrows with Matplotlib this + way is tricky. + + Examples + -------- + >>> G=nx.dodecahedral_graph() + >>> edges=nx.draw_networkx_edges(G,pos=nx.spring_layout(G)) + + Also see the NetworkX drawing examples at + http://networkx.lanl.gov/gallery.html + + See Also + -------- + draw() + draw_networkx() + draw_networkx_nodes() + draw_networkx_labels() + draw_networkx_edge_labels() + """ + try: + import matplotlib + import matplotlib.pyplot as plt + import matplotlib.cbook as cb + from matplotlib.colors import colorConverter,Colormap + from matplotlib.collections import LineCollection + import numpy + except ImportError: + raise ImportError("Matplotlib required for draw()") + except RuntimeError: + print("Matplotlib unable to open display") + raise + + if ax is None: + ax=plt.gca() + + if edgelist is None: + edgelist=G.edges() + + if not edgelist or len(edgelist)==0: # no edges! + return None + + # set edge positions + edge_pos=numpy.asarray([(pos[e[0]],pos[e[1]]) for e in edgelist]) + + if not cb.iterable(width): + lw = (width,) + else: + lw = width + + if not cb.is_string_like(edge_color) \ + and cb.iterable(edge_color) \ + and len(edge_color)==len(edge_pos): + if numpy.alltrue([cb.is_string_like(c) + for c in edge_color]): + # (should check ALL elements) + # list of color letters such as ['k','r','k',...] + edge_colors = tuple([colorConverter.to_rgba(c,alpha) + for c in edge_color]) + elif numpy.alltrue([not cb.is_string_like(c) + for c in edge_color]): + # If color specs are given as (rgb) or (rgba) tuples, we're OK + if numpy.alltrue([cb.iterable(c) and len(c) in (3,4) + for c in edge_color]): + edge_colors = tuple(edge_color) + else: + # numbers (which are going to be mapped with a colormap) + edge_colors = None + else: + raise ValueError('edge_color must consist of either color names or numbers') + else: + if cb.is_string_like(edge_color) or len(edge_color)==1: + edge_colors = ( colorConverter.to_rgba(edge_color, alpha), ) + else: + raise ValueError('edge_color must be a single color or list of exactly m colors where m is the number or edges') + + edge_collection = LineCollection(edge_pos, + colors = edge_colors, + linewidths = lw, + antialiaseds = (1,), + linestyle = style, + transOffset = ax.transData, + ) + + + edge_collection.set_zorder(1) # edges go behind nodes + edge_collection.set_label(label) + ax.add_collection(edge_collection) + + # Note: there was a bug in mpl regarding the handling of alpha values for + # each line in a LineCollection. It was fixed in matplotlib in r7184 and + # r7189 (June 6 2009). We should then not set the alpha value globally, + # since the user can instead provide per-edge alphas now. Only set it + # globally if provided as a scalar. + if cb.is_numlike(alpha): + edge_collection.set_alpha(alpha) + + if edge_colors is None: + if edge_cmap is not None: + assert(isinstance(edge_cmap, Colormap)) + edge_collection.set_array(numpy.asarray(edge_color)) + edge_collection.set_cmap(edge_cmap) + if edge_vmin is not None or edge_vmax is not None: + edge_collection.set_clim(edge_vmin, edge_vmax) + else: + edge_collection.autoscale() + + arrow_collection=None + + if G.is_directed() and arrows: + + # a directed graph hack + # draw thick line segments at head end of edge + # waiting for someone else to implement arrows that will work + arrow_colors = edge_colors + a_pos=[] + p=1.0-0.25 # make head segment 25 percent of edge length + for src,dst in edge_pos: + x1,y1=src + x2,y2=dst + dx=x2-x1 # x offset + dy=y2-y1 # y offset + d=numpy.sqrt(float(dx**2+dy**2)) # length of edge + if d==0: # source and target at same position + continue + if dx==0: # vertical edge + xa=x2 + ya=dy*p+y1 + if dy==0: # horizontal edge + ya=y2 + xa=dx*p+x1 + else: + theta=numpy.arctan2(dy,dx) + xa=p*d*numpy.cos(theta)+x1 + ya=p*d*numpy.sin(theta)+y1 + + a_pos.append(((xa,ya),(x2,y2))) + + arrow_collection = LineCollection(a_pos, + colors = arrow_colors, + linewidths = [4*ww for ww in lw], + antialiaseds = (1,), + transOffset = ax.transData, + ) + + arrow_collection.set_zorder(1) # edges go behind nodes + arrow_collection.set_label(label) + ax.add_collection(arrow_collection) + + + # update view + minx = numpy.amin(numpy.ravel(edge_pos[:,:,0])) + maxx = numpy.amax(numpy.ravel(edge_pos[:,:,0])) + miny = numpy.amin(numpy.ravel(edge_pos[:,:,1])) + maxy = numpy.amax(numpy.ravel(edge_pos[:,:,1])) + + w = maxx-minx + h = maxy-miny + padx, pady = 0.05*w, 0.05*h + corners = (minx-padx, miny-pady), (maxx+padx, maxy+pady) + ax.update_datalim( corners) + ax.autoscale_view() + +# if arrow_collection: + + return edge_collection + + +def draw_networkx_labels(G, pos, + labels=None, + font_size=12, + font_color='k', + font_family='sans-serif', + font_weight='normal', + alpha=1.0, + ax=None, + **kwds): + """Draw node labels on the graph G. + + Parameters + ---------- + G : graph + A networkx graph + + pos : dictionary, optional + A dictionary with nodes as keys and positions as values. + If not specified a spring layout positioning will be computed. + See networkx.layout for functions that compute node positions. + + labels : dictionary, optional (default=None) + Node labels in a dictionary keyed by node of text labels + + font_size : int + Font size for text labels (default=12) + + font_color : string + Font color string (default='k' black) + + font_family : string + Font family (default='sans-serif') + + font_weight : string + Font weight (default='normal') + + alpha : float + The text transparency (default=1.0) + + ax : Matplotlib Axes object, optional + Draw the graph in the specified Matplotlib axes. + + + Examples + -------- + >>> G=nx.dodecahedral_graph() + >>> labels=nx.draw_networkx_labels(G,pos=nx.spring_layout(G)) + + Also see the NetworkX drawing examples at + http://networkx.lanl.gov/gallery.html + + + See Also + -------- + draw() + draw_networkx() + draw_networkx_nodes() + draw_networkx_edges() + draw_networkx_edge_labels() + """ + try: + import matplotlib.pyplot as plt + import matplotlib.cbook as cb + except ImportError: + raise ImportError("Matplotlib required for draw()") + except RuntimeError: + print("Matplotlib unable to open display") + raise + + if ax is None: + ax=plt.gca() + + if labels is None: + labels=dict( (n,n) for n in G.nodes()) + + # set optional alignment + horizontalalignment=kwds.get('horizontalalignment','center') + verticalalignment=kwds.get('verticalalignment','center') + + text_items={} # there is no text collection so we'll fake one + for n, label in labels.items(): + (x,y)=pos[n] + if not cb.is_string_like(label): + label=str(label) # this will cause "1" and 1 to be labeled the same + t=ax.text(x, y, + label, + size=font_size, + color=font_color, + family=font_family, + weight=font_weight, + horizontalalignment=horizontalalignment, + verticalalignment=verticalalignment, + transform = ax.transData, + clip_on=True, + ) + text_items[n]=t + + return text_items + +def draw_networkx_edge_labels(G, pos, + edge_labels=None, + label_pos=0.5, + font_size=10, + font_color='k', + font_family='sans-serif', + font_weight='normal', + alpha=1.0, + bbox=None, + ax=None, + rotate=True, + **kwds): + """Draw edge labels. + + Parameters + ---------- + G : graph + A networkx graph + + pos : dictionary, optional + A dictionary with nodes as keys and positions as values. + If not specified a spring layout positioning will be computed. + See networkx.layout for functions that compute node positions. + + ax : Matplotlib Axes object, optional + Draw the graph in the specified Matplotlib axes. + + alpha : float + The text transparency (default=1.0) + + edge_labels : dictionary + Edge labels in a dictionary keyed by edge two-tuple of text + labels (default=None). Only labels for the keys in the dictionary + are drawn. + + label_pos : float + Position of edge label along edge (0=head, 0.5=center, 1=tail) + + font_size : int + Font size for text labels (default=12) + + font_color : string + Font color string (default='k' black) + + font_weight : string + Font weight (default='normal') + + font_family : string + Font family (default='sans-serif') + + bbox : Matplotlib bbox + Specify text box shape and colors. + + clip_on : bool + Turn on clipping at axis boundaries (default=True) + + Examples + -------- + >>> G=nx.dodecahedral_graph() + >>> edge_labels=nx.draw_networkx_edge_labels(G,pos=nx.spring_layout(G)) + + Also see the NetworkX drawing examples at + http://networkx.lanl.gov/gallery.html + + See Also + -------- + draw() + draw_networkx() + draw_networkx_nodes() + draw_networkx_edges() + draw_networkx_labels() + """ + try: + import matplotlib.pyplot as plt + import matplotlib.cbook as cb + import numpy + except ImportError: + raise ImportError("Matplotlib required for draw()") + except RuntimeError: + print("Matplotlib unable to open display") + raise + + if ax is None: + ax=plt.gca() + if edge_labels is None: + labels=dict( ((u,v), d) for u,v,d in G.edges(data=True) ) + else: + labels = edge_labels + text_items={} + for (n1,n2), label in labels.items(): + (x1,y1)=pos[n1] + (x2,y2)=pos[n2] + (x,y) = (x1 * label_pos + x2 * (1.0 - label_pos), + y1 * label_pos + y2 * (1.0 - label_pos)) + + if rotate: + angle=numpy.arctan2(y2-y1,x2-x1)/(2.0*numpy.pi)*360 # degrees + # make label orientation "right-side-up" + if angle > 90: + angle-=180 + if angle < - 90: + angle+=180 + # transform data coordinate angle to screen coordinate angle + xy=numpy.array((x,y)) + trans_angle=ax.transData.transform_angles(numpy.array((angle,)), + xy.reshape((1,2)))[0] + else: + trans_angle=0.0 + # use default box of white with white border + if bbox is None: + bbox = dict(boxstyle='round', + ec=(1.0, 1.0, 1.0), + fc=(1.0, 1.0, 1.0), + ) + if not cb.is_string_like(label): + label=str(label) # this will cause "1" and 1 to be labeled the same + + # set optional alignment + horizontalalignment=kwds.get('horizontalalignment','center') + verticalalignment=kwds.get('verticalalignment','center') + + t=ax.text(x, y, + label, + size=font_size, + color=font_color, + family=font_family, + weight=font_weight, + horizontalalignment=horizontalalignment, + verticalalignment=verticalalignment, + rotation=trans_angle, + transform = ax.transData, + bbox = bbox, + zorder = 1, + clip_on=True, + ) + text_items[(n1,n2)]=t + + return text_items + +def draw_circular(G, **kwargs): + """Draw the graph G with a circular layout.""" + draw(G,circular_layout(G),**kwargs) + +def draw_random(G, **kwargs): + """Draw the graph G with a random layout.""" + draw(G,random_layout(G),**kwargs) + +def draw_spectral(G, **kwargs): + """Draw the graph G with a spectral layout.""" + draw(G,spectral_layout(G),**kwargs) + +def draw_spring(G, **kwargs): + """Draw the graph G with a spring layout.""" + draw(G,spring_layout(G),**kwargs) + +def draw_shell(G, **kwargs): + """Draw networkx graph with shell layout.""" + nlist = kwargs.get('nlist', None) + if nlist != None: + del(kwargs['nlist']) + draw(G,shell_layout(G,nlist=nlist),**kwargs) + +def draw_graphviz(G, prog="neato", **kwargs): + """Draw networkx graph with graphviz layout.""" + pos=nx.drawing.graphviz_layout(G,prog) + draw(G,pos,**kwargs) + +def draw_nx(G,pos,**kwds): + """For backward compatibility; use draw or draw_networkx.""" + draw(G,pos,**kwds) + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import matplotlib as mpl + mpl.use('PS',warn=False) + import matplotlib.pyplot as plt + except: + raise SkipTest("matplotlib not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_agraph.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_agraph.py new file mode 100644 index 0000000..b2f28a3 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_agraph.py @@ -0,0 +1,75 @@ +"""Unit tests for PyGraphviz intefaace. +""" +import os +import tempfile + +from nose import SkipTest +from nose.tools import assert_true,assert_equal + +import networkx as nx + +class TestAGraph(object): + @classmethod + def setupClass(cls): + global pygraphviz + try: + import pygraphviz + except ImportError: + raise SkipTest('PyGraphviz not available.') + + def build_graph(self, G): + G.add_edge('A','B') + G.add_edge('A','C') + G.add_edge('A','C') + G.add_edge('B','C') + G.add_edge('A','D') + G.add_node('E') + return G + + def assert_equal(self, G1, G2): + assert_true( sorted(G1.nodes())==sorted(G2.nodes()) ) + assert_true( sorted(G1.edges())==sorted(G2.edges()) ) + + + def agraph_checks(self, G): + G = self.build_graph(G) + A=nx.to_agraph(G) + H=nx.from_agraph(A) + self.assert_equal(G, H) + + fname=tempfile.mktemp() + nx.drawing.nx_agraph.write_dot(H,fname) + Hin=nx.drawing.nx_agraph.read_dot(fname) + os.unlink(fname) + self.assert_equal(H,Hin) + + + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + nx.drawing.nx_agraph.write_dot(H,fh) + fh.close() + + fh=open(fname,'r') + Hin=nx.drawing.nx_agraph.read_dot(fh) + fh.close() + os.unlink(fname) + self.assert_equal(H,Hin) + + def test_from_agraph_name(self): + G=nx.Graph(name='test') + A=nx.to_agraph(G) + H=nx.from_agraph(A) + assert_equal(G.name,'test') + + + def testUndirected(self): + self.agraph_checks(nx.Graph()) + + def testDirected(self): + self.agraph_checks(nx.DiGraph()) + + def testMultiUndirected(self): + self.agraph_checks(nx.MultiGraph()) + + def testMultiDirected(self): + self.agraph_checks(nx.MultiDiGraph()) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_layout.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_layout.py new file mode 100644 index 0000000..0327782 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_layout.py @@ -0,0 +1,61 @@ +"""Unit tests for layout functions.""" +import sys +from nose import SkipTest +from nose.tools import assert_equal +import networkx as nx + +class TestLayout(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global numpy + try: + import numpy + except ImportError: + raise SkipTest('numpy not available.') + + + def setUp(self): + self.Gi=nx.grid_2d_graph(5,5) + self.Gs=nx.Graph() + self.Gs.add_path('abcdef') + self.bigG=nx.grid_2d_graph(25,25) #bigger than 500 nodes for sparse + + def test_smoke_int(self): + G=self.Gi + vpos=nx.random_layout(G) + vpos=nx.circular_layout(G) + vpos=nx.spring_layout(G) + vpos=nx.fruchterman_reingold_layout(G) + vpos=nx.spectral_layout(G) + vpos=nx.spectral_layout(self.bigG) + vpos=nx.shell_layout(G) + + def test_smoke_string(self): + G=self.Gs + vpos=nx.random_layout(G) + vpos=nx.circular_layout(G) + vpos=nx.spring_layout(G) + vpos=nx.fruchterman_reingold_layout(G) + vpos=nx.spectral_layout(G) + vpos=nx.shell_layout(G) + + + def test_adjacency_interface_numpy(self): + A=nx.to_numpy_matrix(self.Gs) + pos=nx.drawing.layout._fruchterman_reingold(A) + pos=nx.drawing.layout._fruchterman_reingold(A,dim=3) + assert_equal(pos.shape,(6,3)) + + def test_adjacency_interface_scipy(self): + try: + import scipy + except ImportError: + raise SkipTest('scipy not available.') + + A=nx.to_scipy_sparse_matrix(self.Gs,dtype='f') + pos=nx.drawing.layout._sparse_fruchterman_reingold(A) + pos=nx.drawing.layout._sparse_spectral(A) + + pos=nx.drawing.layout._sparse_fruchterman_reingold(A,dim=3) + assert_equal(pos.shape,(6,3)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pydot.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pydot.py new file mode 100644 index 0000000..9cdffee --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pydot.py @@ -0,0 +1,62 @@ +""" + Unit tests for pydot drawing functions. +""" +import os +import tempfile + +from nose import SkipTest +from nose.tools import assert_true + +import networkx as nx + +class TestPydot(object): + @classmethod + def setupClass(cls): + global pydot + try: + import pydot + import dot_parser + except ImportError: + raise SkipTest('pydot not available.') + + def build_graph(self, G): + G.add_edge('A','B') + G.add_edge('A','C') + G.add_edge('B','C') + G.add_edge('A','D') + G.add_node('E') + return G, nx.to_pydot(G) + + def assert_equal(self, G1, G2): + assert_true( sorted(G1.nodes())==sorted(G2.nodes()) ) + assert_true( sorted(G1.edges())==sorted(G2.edges()) ) + + def pydot_checks(self, G): + H, P = self.build_graph(G) + G2 = H.__class__(nx.from_pydot(P)) + self.assert_equal(H, G2) + + fname = tempfile.mktemp() + assert_true( P.write_raw(fname) ) + + Pin = pydot.graph_from_dot_file(fname) + + n1 = sorted([p.get_name() for p in P.get_node_list()]) + n2 = sorted([p.get_name() for p in Pin.get_node_list()]) + assert_true( n1 == n2 ) + + e1=[(e.get_source(),e.get_destination()) for e in P.get_edge_list()] + e2=[(e.get_source(),e.get_destination()) for e in Pin.get_edge_list()] + assert_true( sorted(e1)==sorted(e2) ) + + Hin = nx.drawing.nx_pydot.read_dot(fname) + Hin = H.__class__(Hin) + self.assert_equal(H, Hin) +# os.unlink(fname) + + + def testUndirected(self): + self.pydot_checks(nx.Graph()) + + def testDirected(self): + self.pydot_checks(nx.DiGraph()) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pylab.py b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pylab.py new file mode 100644 index 0000000..9c55590 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/drawing/tests/test_pylab.py @@ -0,0 +1,40 @@ +""" + Unit tests for matplotlib drawing functions. +""" + +import os + +from nose import SkipTest + +import networkx as nx + +class TestPylab(object): + @classmethod + def setupClass(cls): + global plt + try: + import matplotlib as mpl + mpl.use('PS',warn=False) + import matplotlib.pyplot as plt + except ImportError: + raise SkipTest('matplotlib not available.') + except RuntimeError: + raise SkipTest('matplotlib not available.') + + def setUp(self): + self.G=nx.barbell_graph(5,10) + + + def test_draw(self): + N=self.G + nx.draw_spring(N) + plt.savefig("test.ps") + nx.draw_random(N) + plt.savefig("test.ps") + nx.draw_circular(N) + plt.savefig("test.ps") + nx.draw_spectral(N) + plt.savefig("test.ps") + nx.draw_spring(N.to_directed()) + plt.savefig("test.ps") + os.unlink('test.ps') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/exception.py b/lib/python2.7/site-packages/setoolsgui/networkx/exception.py new file mode 100644 index 0000000..0267038 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/exception.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" +********** +Exceptions +********** + +Base exceptions and errors for NetworkX. + +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)\nDan Schult(dschult@colgate.edu)\nLoïc Séguin-C. """ +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# + +# Exception handling + +# the root of all Exceptions +class NetworkXException(Exception): + """Base class for exceptions in NetworkX.""" + +class NetworkXError(NetworkXException): + """Exception for a serious error in NetworkX""" + +class NetworkXPointlessConcept(NetworkXException): + """Harary, F. and Read, R. "Is the Null Graph a Pointless Concept?" +In Graphs and Combinatorics Conference, George Washington University. +New York: Springer-Verlag, 1973. +""" + +class NetworkXAlgorithmError(NetworkXException): + """Exception for unexpected termination of algorithms.""" + +class NetworkXUnfeasible(NetworkXAlgorithmError): + """Exception raised by algorithms trying to solve a problem + instance that has no feasible solution.""" + +class NetworkXNoPath(NetworkXUnfeasible): + """Exception for algorithms that should return a path when running + on graphs where such a path does not exist.""" + +class NetworkXUnbounded(NetworkXAlgorithmError): + """Exception raised by algorithms trying to solve a maximization + or a minimization problem instance that is unbounded.""" + +class NetworkXNotImplemented(NetworkXException): + """Exception raised by algorithms not implemented for a type of graph.""" diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/external/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/external/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/__init__.py new file mode 100644 index 0000000..154bfd6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/__init__.py @@ -0,0 +1,8 @@ +""" + Hack for including decorator-3.3.1 in NetworkX. +""" +import sys +if sys.version >= '3': + from .decorator3._decorator3 import * +else: + from .decorator2._decorator2 import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/__init__.py @@ -0,0 +1 @@ +# diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/_decorator2.py b/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/_decorator2.py new file mode 100644 index 0000000..2e8c123 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/external/decorator/decorator2/_decorator2.py @@ -0,0 +1,210 @@ +########################## LICENCE ############################### +## +## Copyright (c) 2005-2011, Michele Simionato +## All rights reserved. +## +## Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## Redistributions in bytecode 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. + +## 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 +## HOLDERS 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. + +""" +Decorator module, see http://pypi.python.org/pypi/decorator +for the documentation. +""" + +__version__ = '3.3.2' + +__all__ = ["decorator", "FunctionMaker", "partial"] + +import sys, re, inspect + +try: + from functools import partial +except ImportError: # for Python version < 2.5 + class partial(object): + "A simple replacement of functools.partial" + def __init__(self, func, *args, **kw): + self.func = func + self.args = args + self.keywords = kw + def __call__(self, *otherargs, **otherkw): + kw = self.keywords.copy() + kw.update(otherkw) + return self.func(*(self.args + otherargs), **kw) + +if sys.version >= '3': + from inspect import getfullargspec +else: + class getfullargspec(object): + "A quick and dirty replacement for getfullargspec for Python 2.X" + def __init__(self, f): + self.args, self.varargs, self.varkw, self.defaults = \ + inspect.getargspec(f) + self.kwonlyargs = [] + self.kwonlydefaults = None + self.annotations = getattr(f, '__annotations__', {}) + def __iter__(self): + yield self.args + yield self.varargs + yield self.varkw + yield self.defaults + +DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') + +# basic functionality +class FunctionMaker(object): + """ + An object with the ability to create functions with a given signature. + It has attributes name, doc, module, signature, defaults, dict and + methods update and make. + """ + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + self.module = func.__module__ + if inspect.isfunction(func): + argspec = getfullargspec(func) + for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', + 'kwonlydefaults', 'annotations'): + setattr(self, a, getattr(argspec, a)) + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + self.signature = inspect.formatargspec( + formatvalue=lambda val: "", *argspec)[1:-1] + allargs = list(self.args) + if self.varargs: + allargs.append('*' + self.varargs) + if self.varkw: + allargs.append('**' + self.varkw) + try: + self.shortsignature = ', '.join(allargs) + except TypeError: # exotic signature, valid only in Python 2.X + self.shortsignature = self.signature + self.dict = func.__dict__.copy() + # func=None happens when decorating a caller + if name: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.func_defaults = getattr(self, 'defaults', ()) + func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) + callermodule = sys._getframe(3).f_globals.get('__name__', '?') + func.__module__ = getattr(self, 'module', callermodule) + func.__dict__.update(kw) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.match(src) + if mo is None: + raise SyntaxError('not a valid function template\n%s' % src) + name = mo.group(1) # extract the function name + names = set([name] + [arg.strip(' *') for arg in + self.shortsignature.split(',')]) + for n in names: + if n in ('_func_', '_call_'): + raise NameError('%s is overridden in\n%s' % (n, src)) + if not src.endswith('\n'): # add a newline just for safety + src += '\n' # this is needed in old versions of Python + try: + code = compile(src, '', 'single') + # print >> sys.stderr, 'Compiling %s' % src + exec code in evaldict + except: + print >> sys.stderr, 'Error in generated code:' + print >> sys.stderr, src + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + doc=None, module=None, addsource=True, **attrs): + """ + Create a function from the strings name, signature and body. + evaldict is the evaluation dictionary. If addsource is true an attribute + __source__ is added to the result. The attributes attrs are added, + if any. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] #strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + self = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + return self.make('def %(name)s(%(signature)s):\n' + ibody, + evaldict, addsource, **attrs) + +def decorator(caller, func=None): + """ + decorator(caller) converts a caller function into a decorator; + decorator(caller, func) decorates a function using a caller. + """ + if func is not None: # returns a decorated function + evaldict = func.func_globals.copy() + evaldict['_call_'] = caller + evaldict['_func_'] = func + return FunctionMaker.create( + func, "return _call_(_func_, %(shortsignature)s)", + evaldict, undecorated=func, __wrapped__=func) + else: # returns a decorator + if isinstance(caller, partial): + return partial(decorator, caller) + # otherwise assume caller is a function + first = inspect.getargspec(caller)[0][0] # first arg + evaldict = caller.func_globals.copy() + evaldict['_call_'] = caller + evaldict['decorator'] = decorator + return FunctionMaker.create( + '%s(%s)' % (caller.__name__, first), + 'return decorator(_call_, %s)' % first, + evaldict, undecorated=caller, __wrapped__=caller, + doc=caller.__doc__, module=caller.__module__) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/__init__.py new file mode 100644 index 0000000..92edd41 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/__init__.py @@ -0,0 +1,21 @@ +""" +A package for generating various graphs in networkx. + +""" +from networkx.generators.atlas import * +from networkx.generators.bipartite import * +from networkx.generators.classic import * +from networkx.generators.degree_seq import * +from networkx.generators.directed import * +from networkx.generators.ego import * +from networkx.generators.geometric import * +from networkx.generators.hybrid import * +from networkx.generators.line import * +from networkx.generators.random_graphs import * +from networkx.generators.small import * +from networkx.generators.stochastic import * +from networkx.generators.social import * +from networkx.generators.threshold import * +from networkx.generators.intersection import * +from networkx.generators.random_clustered import * + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/atlas.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/atlas.py new file mode 100644 index 0000000..f3d9c57 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/atlas.py @@ -0,0 +1,12336 @@ +""" +Generators for the small graph atlas. + + +See +"An Atlas of Graphs" by Ronald C. Read and Robin J. Wilson, +Oxford University Press, 1998. + +Because of its size, this module is not imported by default. + +""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """Pieter Swart (swart@lanl.gov)""" + +__all__ = ['graph_atlas_g'] + +from networkx.generators.small import make_small_graph + +def graph_atlas_g(): + """ + Return the list [G0,G1,...,G1252] of graphs as named in the Graph Atlas. + G0,G1,...,G1252 are all graphs with up to 7 nodes. + + The graphs are listed: + 1. in increasing order of number of nodes; + 2. for a fixed number of nodes, + in increasing order of the number of edges; + 3. for fixed numbers of nodes and edges, + in increasing order of the degree sequence, + for example 111223 < 112222; + 4. for fixed degree sequence, in increasing number of automorphisms. + + Note that indexing is set up so that for + GAG=graph_atlas_g(), then + G123=GAG[123] and G[0]=empty_graph(0) + + """ + + descr_list=[ + ['edgelist', 'G0', 0, []], + ['edgelist', 'G1', 1, []], + ['edgelist', 'G2', 2, []], + ['edgelist', 'G3', 2, [[1, 2]]], + ['edgelist', 'G4', 3, []], + ['edgelist', 'G5', 3, [[2, 3]]], + ['edgelist', 'G6', 3, [[1, 2], [1, 3]]], + ['edgelist', 'G7', 3, [[1, 2], [1, 3], [2, 3]]], + ['edgelist', 'G8', 4, []], + ['edgelist', 'G9', 4, [[4, 3]]], + ['edgelist', 'G10', 4, [[4, 3], [4, 2]]], + ['edgelist', 'G11', 4, [[1, 2], [4, 3]]], + ['edgelist', 'G12', 4, [[4, 3], [2, 3], [4, 2]]], + ['edgelist', 'G13', 4, [[4, 1], [4, 2], [4, 3]]], + ['edgelist', 'G14', 4, [[1, 2], [2, 3], [1, 4]]], + ['edgelist', 'G15', 4, [[4, 3], [2, 3], [4, 2], [4, 1]]], + ['edgelist', 'G16', 4, [[1, 2], [2, 3], [3, 4], [1, 4]]], + ['edgelist', 'G17', 4, [[1, 2], [1, 3], [1, 4], [2, 3], [3, 4]]], + ['edgelist', 'G18', 4, [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3]]], + ['edgelist', 'G19', 5, []], + ['edgelist', 'G20', 5, [[5, 4]]], + ['edgelist', 'G21', 5, [[2, 3], [1, 2]]], + ['edgelist', 'G22', 5, [[1, 3], [5, 4]]], + ['edgelist', 'G23', 5, [[2, 3], [1, 2], [3, 1]]], + ['edgelist', 'G24', 5, [[5, 4], [4, 3], [4, 2]]], + ['edgelist', 'G25', 5, [[4, 3], [5, 4], [1, 5]]], + ['edgelist', 'G26', 5, [[2, 3], [1, 2], [5, 4]]], + ['edgelist', 'G27', 5, [[5, 4], [2, 3], [4, 2], [4, 3]]], + ['edgelist', 'G28', 5, [[1, 4], [2, 1], [3, 2], [4, 3]]], + ['edgelist', 'G29', 5, [[5, 4], [5, 1], [5, 2], [5, 3]]], + ['edgelist', 'G30', 5, [[5, 1], [4, 2], [5, 4], [4, 3]]], + ['edgelist', 'G31', 5, [[3, 4], [2, 3], [1, 2], [5, 1]]], + ['edgelist', 'G32', 5, [[2, 3], [1, 2], [3, 1], [5, 4]]], + ['edgelist', 'G33', 5, [[1, 4], [3, 1], [4, 3], [2, 1], [3, 2]]], + ['edgelist', 'G34', 5, [[5, 3], [5, 4], [3, 4], [5, 2], [5, 1]]], + ['edgelist', 'G35', 5, [[1, 2], [2, 3], [3, 4], [1, 5], [1, 3]]], + ['edgelist', 'G36', 5, [[5, 1], [2, 3], [5, 4], [4, 3], [4, 2]]], + ['edgelist', 'G37', 5, [[2, 1], [5, 2], [3, 5], [4, 3], [2, 4]]], + ['edgelist', 'G38', 5, [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5]]], + ['edgelist', 'G39', 5, [[2, 1], [5, 2], [5, 1], [1, 4], [2, 4], [4, 5]]], + ['edgelist', 'G40', 5, [[2, 1], [5, 2], [3, 5], [4, 3], [2, 4], [3, 2]]], + ['edgelist', 'G41', 5, [[2, 1], [5, 2], [3, 5], [4, 3], [2, 4], [4, 5]]], + ['edgelist', 'G42', 5, [[1, 2], [5, 4], [3, 4], [5, 3], [5, 1], [5, 2]]], + ['edgelist', 'G43', 5, [[1, 5], [4, 1], [5, 4], [3, 4], [2, 3], [1, 2]]], + ['edgelist', 'G44', 5, [[3, 2], [1, 3], [4, 1], [2, 4], [5, 2], [1, 5]]], + ['edgelist', + 'G45', + 5, + [[5, 1], [2, 3], [5, 4], [4, 3], [4, 2], [5, 2], [3, 5]]], + ['edgelist', + 'G46', + 5, + [[5, 2], [3, 5], [4, 3], [2, 4], [4, 5], [1, 4], [5, 1]]], + ['edgelist', + 'G47', + 5, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2]]], + ['edgelist', + 'G48', + 5, + [[3, 2], [1, 3], [4, 1], [2, 4], [5, 2], [1, 5], [3, 5]]], + ['edgelist', + 'G49', + 5, + [[2, 1], [5, 2], [3, 5], [4, 3], [2, 4], [5, 1], [4, 5], [1, 4]]], + ['edgelist', + 'G50', + 5, + [[1, 2], [2, 3], [3, 4], [1, 4], [5, 1], [5, 2], [5, 3], [5, 4]]], + ['edgelist', + 'G51', + 5, + [[1, 2], [4, 5], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5]]], + ['edgelist', + 'G52', + 5, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5]]], + ['edgelist', 'G53', 6, []], + ['edgelist', 'G54', 6, [[6, 5]]], + ['edgelist', 'G55', 6, [[1, 4], [6, 5]]], + ['edgelist', 'G56', 6, [[2, 4], [2, 3]]], + ['edgelist', 'G57', 6, [[2, 4], [3, 2], [4, 3]]], + ['edgelist', 'G58', 6, [[1, 4], [6, 1], [5, 1]]], + ['edgelist', 'G59', 6, [[5, 4], [6, 5], [1, 6]]], + ['edgelist', 'G60', 6, [[5, 4], [6, 2], [6, 3]]], + ['edgelist', 'G61', 6, [[2, 3], [4, 1], [6, 5]]], + ['edgelist', 'G62', 6, [[1, 4], [5, 1], [6, 5], [1, 6]]], + ['edgelist', 'G63', 6, [[4, 1], [6, 4], [5, 6], [1, 5]]], + ['edgelist', 'G64', 6, [[6, 2], [6, 4], [6, 3], [1, 6]]], + ['edgelist', 'G65', 6, [[5, 4], [4, 2], [5, 1], [4, 3]]], + ['edgelist', 'G66', 6, [[1, 3], [2, 4], [3, 2], [6, 4]]], + ['edgelist', 'G67', 6, [[2, 4], [3, 2], [4, 3], [1, 6]]], + ['edgelist', 'G68', 6, [[2, 3], [1, 4], [6, 1], [5, 1]]], + ['edgelist', 'G69', 6, [[5, 6], [2, 3], [1, 6], [4, 5]]], + ['edgelist', 'G70', 6, [[1, 3], [5, 1], [4, 2], [6, 4]]], + ['edgelist', 'G71', 6, [[4, 1], [6, 4], [5, 6], [1, 5], [6, 1]]], + ['edgelist', 'G72', 6, [[6, 4], [4, 2], [4, 3], [5, 4], [5, 6]]], + ['edgelist', 'G73', 6, [[6, 4], [6, 5], [3, 4], [4, 5], [1, 5]]], + ['edgelist', 'G74', 6, [[5, 4], [2, 3], [5, 1], [4, 3], [4, 2]]], + ['edgelist', 'G75', 6, [[2, 5], [4, 5], [5, 1], [3, 2], [4, 3]]], + ['edgelist', 'G76', 6, [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5]]], + ['edgelist', 'G77', 6, [[6, 4], [6, 5], [6, 1], [6, 2], [6, 3]]], + ['edgelist', 'G78', 6, [[2, 5], [6, 2], [2, 1], [3, 2], [3, 4]]], + ['edgelist', 'G79', 6, [[1, 2], [4, 5], [1, 3], [4, 1], [6, 4]]], + ['edgelist', 'G80', 6, [[2, 1], [3, 2], [3, 5], [2, 4], [6, 4]]], + ['edgelist', 'G81', 6, [[5, 4], [1, 6], [5, 1], [4, 3], [4, 2]]], + ['edgelist', 'G82', 6, [[2, 3], [1, 2], [5, 6], [2, 4], [3, 4]]], + ['edgelist', 'G83', 6, [[1, 2], [1, 6], [3, 4], [4, 5], [5, 6]]], + ['edgelist', 'G84', 6, [[5, 4], [6, 2], [6, 3], [1, 4], [5, 1]]], + ['edgelist', 'G85', 6, [[2, 3], [4, 1], [6, 4], [5, 6], [1, 5]]], + ['edgelist', 'G86', 6, [[1, 4], [6, 1], [5, 6], [4, 5], [6, 4], [5, 1]]], + ['edgelist', 'G87', 6, [[2, 5], [3, 5], [5, 1], [3, 4], [4, 2], [4, 5]]], + ['edgelist', 'G88', 6, [[2, 5], [3, 5], [5, 1], [3, 2], [4, 2], [3, 4]]], + ['edgelist', 'G89', 6, [[3, 1], [6, 5], [5, 4], [6, 4], [5, 1], [3, 5]]], + ['edgelist', 'G90', 6, [[4, 3], [5, 4], [1, 5], [2, 1], [3, 2], [1, 4]]], + ['edgelist', 'G91', 6, [[5, 2], [4, 2], [5, 3], [4, 3], [3, 1], [2, 1]]], + ['edgelist', 'G92', 6, [[6, 3], [6, 4], [6, 5], [4, 5], [6, 2], [6, 1]]], + ['edgelist', 'G93', 6, [[5, 4], [5, 3], [5, 1], [2, 5], [4, 1], [6, 4]]], + ['edgelist', 'G94', 6, [[5, 4], [4, 6], [6, 5], [6, 2], [4, 3], [5, 1]]], + ['edgelist', 'G95', 6, [[5, 3], [2, 3], [5, 4], [5, 2], [5, 1], [1, 6]]], + ['edgelist', 'G96', 6, [[2, 3], [4, 2], [1, 4], [3, 1], [5, 1], [6, 1]]], + ['edgelist', 'G97', 6, [[3, 1], [5, 3], [2, 5], [3, 2], [4, 2], [6, 4]]], + ['edgelist', 'G98', 6, [[2, 3], [4, 2], [1, 4], [3, 1], [5, 1], [6, 4]]], + ['edgelist', 'G99', 6, [[6, 4], [3, 6], [3, 1], [5, 3], [5, 4], [4, 2]]], + ['edgelist', 'G100', 6, [[1, 3], [4, 5], [2, 1], [6, 4], [5, 6], [4, 1]]], + ['edgelist', 'G101', 6, [[2, 3], [4, 1], [6, 4], [5, 6], [1, 5], [6, 1]]], + ['edgelist', 'G102', 6, [[5, 4], [2, 3], [5, 1], [4, 3], [4, 2], [6, 1]]], + ['edgelist', 'G103', 6, [[2, 5], [3, 5], [5, 1], [1, 6], [4, 2], [3, 4]]], + ['edgelist', 'G104', 6, [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 6]]], + ['edgelist', 'G105', 6, [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6]]], + ['edgelist', 'G106', 6, [[2, 4], [3, 2], [4, 3], [1, 5], [6, 1], [5, 6]]], + ['edgelist', + 'G107', + 6, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [1, 6]]], + ['edgelist', + 'G108', + 6, + [[2, 5], [3, 5], [3, 2], [4, 2], [3, 4], [3, 1], [1, 2]]], + ['edgelist', + 'G109', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2]]], + ['edgelist', + 'G110', + 6, + [[1, 2], [4, 3], [1, 3], [4, 1], [4, 2], [6, 2], [6, 3]]], + ['edgelist', + 'G111', + 6, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [4, 5]]], + ['edgelist', + 'G112', + 6, + [[2, 1], [5, 2], [3, 5], [4, 3], [6, 2], [3, 6], [2, 3]]], + ['edgelist', + 'G113', + 6, + [[1, 5], [3, 1], [2, 3], [4, 2], [6, 4], [4, 1], [3, 4]]], + ['edgelist', + 'G114', + 6, + [[2, 5], [3, 5], [3, 4], [3, 2], [4, 2], [5, 6], [1, 5]]], + ['edgelist', + 'G115', + 6, + [[2, 1], [5, 2], [3, 5], [4, 3], [6, 2], [3, 6], [5, 6]]], + ['edgelist', + 'G116', + 6, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 5]]], + ['edgelist', + 'G117', + 6, + [[1, 6], [5, 1], [6, 5], [1, 3], [4, 1], [4, 3], [1, 2]]], + ['edgelist', + 'G118', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 6], [5, 2]]], + ['edgelist', + 'G119', + 6, + [[1, 2], [5, 1], [2, 5], [1, 3], [4, 1], [4, 3], [4, 6]]], + ['edgelist', + 'G120', + 6, + [[2, 5], [3, 5], [5, 1], [1, 6], [4, 2], [3, 4], [4, 5]]], + ['edgelist', + 'G121', + 6, + [[3, 1], [4, 3], [5, 4], [6, 5], [3, 6], [2, 3], [5, 2]]], + ['edgelist', + 'G122', + 6, + [[2, 6], [1, 2], [5, 1], [4, 5], [3, 4], [2, 3], [1, 4]]], + ['edgelist', + 'G123', + 6, + [[2, 5], [3, 5], [5, 1], [1, 6], [4, 2], [3, 4], [3, 2]]], + ['edgelist', + 'G124', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [1, 3], [6, 2]]], + ['edgelist', + 'G125', + 6, + [[3, 1], [5, 2], [2, 3], [6, 5], [3, 6], [4, 2], [6, 4]]], + ['edgelist', + 'G126', + 6, + [[6, 1], [4, 6], [3, 4], [1, 3], [2, 4], [5, 2], [4, 5]]], + ['edgelist', + 'G127', + 6, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [3, 4]]], + ['edgelist', + 'G128', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 4]]], + ['edgelist', + 'G129', + 6, + [[5, 4], [1, 5], [2, 1], [3, 2], [4, 3], [1, 6], [6, 4]]], + ['edgelist', + 'G130', + 6, + [[2, 3], [1, 2], [3, 1], [4, 1], [5, 4], [6, 5], [4, 6]]], + ['edgelist', + 'G131', + 6, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2]]], + ['edgelist', + 'G132', + 6, + [[1, 2], [2, 3], [3, 4], [1, 4], [5, 1], [5, 2], [5, 3], [5, 4]]], + ['edgelist', + 'G133', + 6, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 1], [1, 5]]], + ['edgelist', + 'G134', + 6, + [[2, 3], [4, 2], [1, 4], [2, 1], [3, 1], [4, 3], [6, 4], [5, 1]]], + ['edgelist', + 'G135', + 6, + [[1, 2], [3, 5], [1, 3], [6, 3], [4, 2], [4, 3], [3, 2], [5, 2]]], + ['edgelist', + 'G136', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [2, 6]]], + ['edgelist', + 'G137', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 5]]], + ['edgelist', + 'G138', + 6, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [3, 2], [6, 2]]], + ['edgelist', + 'G139', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 1]]], + ['edgelist', + 'G140', + 6, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [4, 1], [6, 2]]], + ['edgelist', + 'G141', + 6, + [[3, 1], [4, 3], [5, 4], [6, 5], [3, 6], [2, 3], [5, 2], [6, 4]]], + ['edgelist', + 'G142', + 6, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [1, 6], [6, 5]]], + ['edgelist', + 'G143', + 6, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [6, 2], [6, 4]]], + ['edgelist', + 'G144', + 6, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [4, 5]]], + ['edgelist', + 'G145', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 1], [6, 3], [1, 3]]], + ['edgelist', + 'G146', + 6, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4]]], + ['edgelist', + 'G147', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3]]], + ['edgelist', + 'G148', + 6, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [2, 5], [1, 2]]], + ['edgelist', + 'G149', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1]]], + ['edgelist', + 'G150', + 6, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [3, 2]]], + ['edgelist', + 'G151', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [5, 6], [6, 4], [2, 6]]], + ['edgelist', + 'G152', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 2]]], + ['edgelist', + 'G153', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 6], [6, 3], [6, 1]]], + ['edgelist', + 'G154', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [5, 2], [6, 3]]], + ['edgelist', + 'G155', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [3, 5], [1, 4]]], + ['edgelist', + 'G156', + 6, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [5, 3]]], + ['edgelist', + 'G157', + 6, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [1, 5]]], + ['edgelist', + 'G158', + 6, + [[1, 2], [2, 3], [3, 4], [1, 4], [5, 1], [5, 2], [5, 3], [5, 4], [5, 6]]], + ['edgelist', + 'G159', + 6, + [[3, 1], [5, 2], [2, 3], [6, 5], [3, 6], [4, 2], [6, 4], [4, 3], [5, 4]]], + ['edgelist', + 'G160', + 6, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [5, 6]]], + ['edgelist', + 'G161', + 6, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4], [5, 6]]], + ['edgelist', + 'G162', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [3, 6], [1, 6], [3, 1], [4, 1]]], + ['edgelist', + 'G163', + 6, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [1, 5], [2, 1], [5, 2]]], + ['edgelist', + 'G164', + 6, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [5, 2], [2, 1], [6, 2]]], + ['edgelist', + 'G165', + 6, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 5], [5, 1], [6, 1]]], + ['edgelist', + 'G166', + 6, + [[5, 4], [1, 5], [2, 1], [3, 2], [4, 3], [1, 6], [6, 4], [1, 4], [2, 6]]], + ['edgelist', + 'G167', + 6, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [4, 3], [1, 4], [5, 1]]], + ['edgelist', + 'G168', + 6, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [4, 3], [1, 4], [3, 5]]], + ['edgelist', + 'G169', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [3, 6], [1, 6], [3, 1], [6, 2]]], + ['edgelist', + 'G170', + 6, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4], [3, 1]]], + ['edgelist', + 'G171', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 1], [6, 5], [6, 3], [6, 4]]], + ['edgelist', + 'G172', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [6, 2]]], + ['edgelist', + 'G173', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 4], [5, 3], [6, 3]]], + ['edgelist', + 'G174', + 6, + [[3, 4], [1, 3], [4, 1], [5, 4], [2, 5], [6, 2], [5, 6], [2, 1], [6, 3]]], + ['edgelist', + 'G175', + 6, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 4], [6, 3], [5, 2]]], + ['edgelist', + 'G176', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3]]], + ['edgelist', + 'G177', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [5, 6]]], + ['edgelist', + 'G178', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 6]]], + ['edgelist', + 'G179', + 6, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [2, 1]]], + ['edgelist', + 'G180', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [6, 5], + [4, 6], + [2, 6]]], + ['edgelist', + 'G181', + 6, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [3, 5]]], + ['edgelist', + 'G182', + 6, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3]]], + ['edgelist', + 'G183', + 6, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3]]], + ['edgelist', + 'G184', + 6, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [6, 3]]], + ['edgelist', + 'G185', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2]]], + ['edgelist', + 'G186', + 6, + [[1, 2], + [3, 5], + [1, 3], + [5, 6], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4]]], + ['edgelist', + 'G187', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5]]], + ['edgelist', + 'G188', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [1, 3], + [2, 4], + [6, 2]]], + ['edgelist', + 'G189', + 6, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [4, 3], + [1, 4]]], + ['edgelist', + 'G190', + 6, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [6, 4], + [3, 6], + [2, 1]]], + ['edgelist', + 'G191', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3], + [2, 6]]], + ['edgelist', + 'G192', + 6, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4]]], + ['edgelist', + 'G193', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6]]], + ['edgelist', + 'G194', + 6, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [1, 3]]], + ['edgelist', + 'G195', + 6, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [6, 4], + [3, 6], + [2, 1], + [6, 2]]], + ['edgelist', + 'G196', + 6, + [[2, 4], + [5, 2], + [4, 5], + [3, 4], + [1, 3], + [5, 1], + [6, 5], + [3, 6], + [5, 3], + [1, 6], + [2, 6]]], + ['edgelist', + 'G197', + 6, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2]]], + ['edgelist', + 'G198', + 6, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3]]], + ['edgelist', + 'G199', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [1, 4]]], + ['edgelist', + 'G200', + 6, + [[1, 2], + [2, 3], + [1, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [5, 4]]], + ['edgelist', + 'G201', + 6, + [[4, 3], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [3, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [1, 5]]], + ['edgelist', + 'G202', + 6, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3], + [5, 6]]], + ['edgelist', + 'G203', + 6, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [3, 4]]], + ['edgelist', + 'G204', + 6, + [[1, 2], + [2, 3], + [1, 3], + [4, 3], + [4, 2], + [5, 1], + [3, 5], + [6, 2], + [1, 6], + [5, 6], + [4, 5], + [6, 4]]], + ['edgelist', + 'G205', + 6, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [3, 4], + [1, 5]]], + ['edgelist', + 'G206', + 6, + [[1, 2], + [2, 3], + [1, 3], + [4, 3], + [4, 2], + [5, 1], + [3, 5], + [6, 2], + [1, 6], + [5, 6], + [4, 5], + [6, 4], + [4, 1]]], + ['edgelist', + 'G207', + 6, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [2, 4], + [3, 1], + [5, 1], + [6, 4]]], + ['edgelist', + 'G208', + 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]]], + ['edgelist', 'G209', 7, []], + ['edgelist', 'G210', 7, [[7, 6]]], + ['edgelist', 'G211', 7, [[3, 4], [2, 3]]], + ['edgelist', 'G212', 7, [[6, 5], [7, 1]]], + ['edgelist', 'G213', 7, [[1, 5], [5, 3], [3, 1]]], + ['edgelist', 'G214', 7, [[1, 2], [1, 7], [1, 6]]], + ['edgelist', 'G215', 7, [[6, 5], [7, 1], [6, 7]]], + ['edgelist', 'G216', 7, [[4, 3], [2, 3], [6, 7]]], + ['edgelist', 'G217', 7, [[4, 2], [6, 7], [1, 5]]], + ['edgelist', 'G218', 7, [[3, 6], [7, 3], [6, 7], [2, 3]]], + ['edgelist', 'G219', 7, [[2, 3], [5, 2], [6, 5], [3, 6]]], + ['edgelist', 'G220', 7, [[2, 1], [6, 2], [2, 3], [5, 2]]], + ['edgelist', 'G221', 7, [[2, 1], [3, 2], [6, 3], [7, 3]]], + ['edgelist', 'G222', 7, [[4, 5], [3, 4], [2, 3], [1, 2]]], + ['edgelist', 'G223', 7, [[5, 3], [1, 5], [3, 1], [6, 7]]], + ['edgelist', 'G224', 7, [[1, 2], [7, 1], [1, 6], [5, 3]]], + ['edgelist', 'G225', 7, [[4, 2], [6, 5], [7, 6], [1, 7]]], + ['edgelist', 'G226', 7, [[1, 5], [4, 1], [3, 6], [7, 3]]], + ['edgelist', 'G227', 7, [[3, 4], [2, 3], [7, 1], [6, 5]]], + ['edgelist', 'G228', 7, [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1]]], + ['edgelist', 'G229', 7, [[3, 6], [7, 3], [6, 7], [5, 3], [4, 3]]], + ['edgelist', 'G230', 7, [[5, 3], [5, 1], [3, 1], [6, 5], [7, 1]]], + ['edgelist', 'G231', 7, [[3, 6], [7, 3], [6, 7], [2, 3], [1, 2]]], + ['edgelist', 'G232', 7, [[5, 2], [1, 5], [4, 1], [2, 4], [3, 2]]], + ['edgelist', 'G233', 7, [[2, 3], [1, 2], [5, 1], [4, 5], [3, 4]]], + ['edgelist', 'G234', 7, [[6, 2], [6, 1], [3, 6], [4, 6], [5, 6]]], + ['edgelist', 'G235', 7, [[2, 6], [7, 2], [2, 1], [3, 2], [4, 3]]], + ['edgelist', 'G236', 7, [[2, 6], [5, 2], [3, 4], [7, 3], [3, 2]]], + ['edgelist', 'G237', 7, [[2, 6], [7, 2], [2, 3], [3, 4], [5, 4]]], + ['edgelist', 'G238', 7, [[3, 2], [4, 3], [5, 4], [6, 5], [4, 7]]], + ['edgelist', 'G239', 7, [[7, 6], [3, 7], [2, 3], [6, 3], [4, 5]]], + ['edgelist', 'G240', 7, [[5, 4], [6, 5], [7, 6], [1, 7], [2, 1]]], + ['edgelist', 'G241', 7, [[1, 5], [4, 1], [3, 6], [7, 3], [6, 7]]], + ['edgelist', 'G242', 7, [[5, 2], [6, 3], [7, 6], [4, 7], [3, 4]]], + ['edgelist', 'G243', 7, [[2, 5], [4, 2], [2, 1], [3, 2], [7, 6]]], + ['edgelist', 'G244', 7, [[1, 5], [4, 1], [2, 1], [3, 2], [7, 6]]], + ['edgelist', 'G245', 7, [[1, 5], [4, 1], [3, 2], [6, 3], [7, 3]]], + ['edgelist', 'G246', 7, [[7, 6], [4, 5], [3, 4], [2, 3], [1, 2]]], + ['edgelist', 'G247', 7, [[3, 4], [2, 3], [7, 1], [6, 7], [6, 5]]], + ['edgelist', 'G248', 7, [[1, 2], [5, 7], [6, 5], [4, 3], [7, 6]]], + ['edgelist', 'G249', 7, [[2, 6], [7, 2], [6, 7], [3, 6], [2, 3], [7, 3]]], + ['edgelist', 'G250', 7, [[2, 5], [4, 2], [3, 4], [5, 3], [2, 1], [3, 2]]], + ['edgelist', 'G251', 7, [[1, 5], [4, 1], [2, 4], [3, 2], [2, 5], [4, 5]]], + ['edgelist', 'G252', 7, [[6, 3], [5, 6], [3, 5], [4, 3], [7, 4], [3, 7]]], + ['edgelist', 'G253', 7, [[2, 3], [5, 2], [6, 5], [3, 6], [1, 2], [5, 1]]], + ['edgelist', 'G254', 7, [[2, 3], [6, 2], [5, 6], [3, 5], [1, 3], [6, 1]]], + ['edgelist', 'G255', 7, [[3, 6], [7, 3], [6, 7], [3, 5], [2, 3], [4, 3]]], + ['edgelist', 'G256', 7, [[2, 5], [4, 2], [3, 4], [2, 3], [3, 6], [7, 3]]], + ['edgelist', 'G257', 7, [[6, 5], [7, 6], [2, 7], [6, 2], [4, 7], [1, 2]]], + ['edgelist', 'G258', 7, [[7, 6], [2, 7], [6, 2], [4, 2], [1, 4], [2, 5]]], + ['edgelist', 'G259', 7, [[1, 5], [4, 1], [3, 4], [5, 3], [3, 6], [7, 3]]], + ['edgelist', 'G260', 7, [[2, 5], [4, 2], [3, 4], [2, 3], [3, 6], [7, 6]]], + ['edgelist', 'G261', 7, [[3, 4], [2, 3], [4, 7], [6, 5], [7, 6], [6, 3]]], + ['edgelist', 'G262', 7, [[3, 6], [7, 3], [6, 7], [2, 5], [4, 2], [3, 2]]], + ['edgelist', 'G263', 7, [[5, 6], [1, 5], [4, 1], [3, 4], [5, 3], [7, 4]]], + ['edgelist', 'G264', 7, [[1, 5], [4, 1], [2, 4], [7, 6], [2, 5], [2, 1]]], + ['edgelist', 'G265', 7, [[2, 5], [4, 2], [3, 4], [6, 3], [7, 6], [3, 7]]], + ['edgelist', 'G266', 7, [[7, 4], [6, 7], [5, 6], [2, 5], [3, 2], [6, 3]]], + ['edgelist', 'G267', 7, [[2, 1], [4, 2], [7, 4], [6, 7], [5, 6], [2, 5]]], + ['edgelist', 'G268', 7, [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6]]], + ['edgelist', 'G269', 7, [[1, 5], [4, 1], [5, 4], [3, 6], [7, 3], [6, 7]]], + ['edgelist', 'G270', 7, [[7, 4], [1, 7], [7, 3], [6, 7], [7, 2], [5, 7]]], + ['edgelist', 'G271', 7, [[3, 5], [6, 3], [3, 4], [7, 3], [2, 3], [2, 1]]], + ['edgelist', 'G272', 7, [[2, 1], [3, 2], [6, 3], [2, 5], [4, 2], [7, 3]]], + ['edgelist', 'G273', 7, [[2, 1], [3, 2], [4, 7], [2, 4], [5, 2], [6, 5]]], + ['edgelist', 'G274', 7, [[2, 1], [3, 2], [6, 3], [7, 6], [2, 5], [4, 2]]], + ['edgelist', 'G275', 7, [[2, 1], [3, 5], [6, 3], [7, 6], [3, 7], [4, 3]]], + ['edgelist', 'G276', 7, [[5, 1], [2, 5], [4, 2], [3, 2], [6, 3], [7, 3]]], + ['edgelist', 'G277', 7, [[7, 6], [2, 3], [1, 2], [3, 1], [4, 3], [1, 5]]], + ['edgelist', 'G278', 7, [[1, 5], [4, 1], [2, 1], [3, 2], [6, 3], [7, 3]]], + ['edgelist', 'G279', 7, [[2, 1], [4, 2], [7, 4], [3, 7], [5, 2], [6, 5]]], + ['edgelist', 'G280', 7, [[3, 6], [7, 3], [5, 3], [2, 5], [4, 2], [1, 4]]], + ['edgelist', 'G281', 7, [[1, 5], [4, 1], [3, 4], [5, 3], [2, 3], [7, 6]]], + ['edgelist', 'G282', 7, [[1, 5], [4, 1], [3, 2], [6, 3], [7, 6], [3, 7]]], + ['edgelist', 'G283', 7, [[4, 5], [2, 1], [3, 2], [6, 3], [7, 6], [3, 7]]], + ['edgelist', 'G284', 7, [[5, 6], [1, 5], [4, 1], [7, 4], [2, 1], [3, 2]]], + ['edgelist', 'G285', 7, [[3, 6], [7, 3], [6, 7], [2, 5], [4, 2], [2, 1]]], + ['edgelist', 'G286', 7, [[5, 6], [4, 5], [3, 4], [2, 3], [1, 2], [7, 1]]], + ['edgelist', 'G287', 7, [[7, 5], [6, 7], [5, 6], [3, 4], [2, 3], [1, 2]]], + ['edgelist', 'G288', 7, [[1, 2], [5, 1], [3, 4], [6, 3], [7, 6], [4, 7]]], + ['edgelist', 'G289', 7, [[2, 3], [1, 2], [5, 1], [4, 5], [3, 4], [7, 6]]], + ['edgelist', + 'G290', + 7, + [[2, 5], [4, 2], [3, 4], [5, 3], [2, 1], [3, 2], [4, 5]]], + ['edgelist', + 'G291', + 7, + [[2, 3], [6, 2], [5, 6], [3, 5], [1, 3], [6, 1], [6, 3]]], + ['edgelist', + 'G292', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2]]], + ['edgelist', + 'G293', + 7, + [[2, 3], [6, 2], [5, 6], [3, 5], [1, 3], [6, 1], [2, 1]]], + ['edgelist', + 'G294', + 7, + [[1, 5], [4, 1], [3, 4], [5, 3], [3, 6], [7, 3], [3, 1]]], + ['edgelist', + 'G295', + 7, + [[2, 5], [4, 2], [3, 4], [5, 3], [2, 1], [3, 2], [3, 7]]], + ['edgelist', + 'G296', + 7, + [[2, 5], [4, 2], [3, 4], [5, 3], [2, 1], [4, 5], [7, 4]]], + ['edgelist', + 'G297', + 7, + [[1, 5], [4, 1], [3, 4], [5, 3], [3, 6], [7, 3], [4, 5]]], + ['edgelist', + 'G298', + 7, + [[1, 5], [4, 1], [2, 4], [4, 7], [2, 5], [2, 1], [6, 5]]], + ['edgelist', + 'G299', + 7, + [[1, 5], [4, 1], [2, 4], [7, 6], [2, 5], [2, 1], [4, 5]]], + ['edgelist', + 'G300', + 7, + [[6, 3], [5, 6], [3, 5], [4, 3], [7, 4], [3, 7], [3, 2]]], + ['edgelist', + 'G301', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [1, 3], [3, 6]]], + ['edgelist', + 'G302', + 7, + [[6, 3], [5, 6], [3, 5], [4, 3], [7, 4], [3, 7], [4, 2]]], + ['edgelist', + 'G303', + 7, + [[2, 5], [4, 2], [3, 4], [5, 3], [3, 1], [3, 2], [7, 1]]], + ['edgelist', + 'G304', + 7, + [[2, 3], [6, 2], [5, 6], [3, 5], [1, 3], [6, 1], [4, 6]]], + ['edgelist', + 'G305', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [1, 3], [4, 6]]], + ['edgelist', + 'G306', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [1, 3], [2, 6]]], + ['edgelist', + 'G307', + 7, + [[4, 3], [5, 4], [4, 6], [3, 5], [6, 3], [7, 2], [7, 5]]], + ['edgelist', + 'G308', + 7, + [[2, 3], [6, 2], [5, 6], [3, 5], [1, 3], [6, 1], [1, 4]]], + ['edgelist', + 'G309', + 7, + [[4, 5], [2, 4], [3, 2], [7, 3], [6, 7], [2, 6], [5, 2]]], + ['edgelist', + 'G310', + 7, + [[1, 2], [5, 1], [2, 5], [3, 2], [4, 3], [6, 4], [5, 6]]], + ['edgelist', + 'G311', + 7, + [[7, 4], [6, 7], [2, 6], [3, 2], [4, 3], [5, 3], [6, 5]]], + ['edgelist', + 'G312', + 7, + [[2, 3], [5, 2], [6, 5], [7, 6], [4, 7], [3, 4], [6, 3]]], + ['edgelist', + 'G313', + 7, + [[5, 2], [4, 5], [2, 4], [3, 2], [7, 3], [6, 7], [3, 6]]], + ['edgelist', + 'G314', + 7, + [[4, 1], [7, 4], [1, 7], [2, 1], [1, 3], [6, 1], [1, 5]]], + ['edgelist', + 'G315', + 7, + [[2, 6], [7, 2], [2, 3], [4, 2], [5, 4], [2, 5], [5, 1]]], + ['edgelist', + 'G316', + 7, + [[6, 1], [7, 6], [1, 7], [6, 3], [2, 6], [7, 4], [5, 7]]], + ['edgelist', + 'G317', + 7, + [[5, 2], [1, 5], [2, 1], [3, 2], [1, 4], [7, 1], [5, 6]]], + ['edgelist', + 'G318', + 7, + [[6, 3], [7, 6], [3, 7], [3, 5], [4, 3], [2, 1], [3, 2]]], + ['edgelist', + 'G319', + 7, + [[5, 2], [1, 5], [4, 1], [2, 4], [3, 2], [2, 6], [7, 2]]], + ['edgelist', + 'G320', + 7, + [[2, 1], [5, 2], [1, 5], [6, 5], [3, 2], [4, 3], [7, 2]]], + ['edgelist', + 'G321', + 7, + [[1, 2], [5, 1], [2, 5], [3, 2], [4, 3], [6, 5], [7, 5]]], + ['edgelist', + 'G322', + 7, + [[3, 4], [6, 3], [7, 6], [4, 7], [2, 3], [5, 6], [1, 6]]], + ['edgelist', + 'G323', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [3, 2], [7, 6]]], + ['edgelist', + 'G324', + 7, + [[3, 6], [7, 3], [6, 7], [5, 3], [2, 3], [1, 2], [4, 2]]], + ['edgelist', + 'G325', + 7, + [[3, 6], [7, 3], [5, 3], [2, 5], [4, 2], [3, 4], [1, 2]]], + ['edgelist', + 'G326', + 7, + [[7, 3], [6, 7], [3, 6], [2, 3], [1, 2], [5, 2], [4, 2]]], + ['edgelist', + 'G327', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [6, 5], [3, 2], [7, 4]]], + ['edgelist', + 'G328', + 7, + [[3, 6], [7, 3], [6, 7], [5, 6], [4, 7], [2, 3], [1, 2]]], + ['edgelist', + 'G329', + 7, + [[3, 6], [7, 3], [2, 5], [2, 3], [1, 2], [5, 1], [1, 4]]], + ['edgelist', + 'G330', + 7, + [[7, 6], [2, 3], [5, 2], [1, 5], [4, 1], [2, 4], [4, 5]]], + ['edgelist', + 'G331', + 7, + [[5, 2], [1, 5], [2, 1], [4, 7], [3, 4], [1, 3], [6, 1]]], + ['edgelist', + 'G332', + 7, + [[5, 2], [1, 5], [4, 1], [2, 4], [3, 2], [6, 3], [7, 2]]], + ['edgelist', + 'G333', + 7, + [[5, 2], [1, 5], [2, 1], [3, 4], [1, 3], [6, 1], [7, 6]]], + ['edgelist', + 'G334', + 7, + [[1, 2], [6, 1], [7, 6], [4, 7], [3, 4], [1, 3], [5, 1]]], + ['edgelist', + 'G335', + 7, + [[2, 1], [5, 2], [3, 5], [4, 3], [5, 4], [1, 5], [7, 6]]], + ['edgelist', + 'G336', + 7, + [[4, 7], [3, 4], [2, 3], [1, 2], [5, 1], [2, 5], [6, 5]]], + ['edgelist', + 'G337', + 7, + [[2, 1], [6, 2], [7, 6], [3, 7], [2, 3], [4, 3], [5, 4]]], + ['edgelist', + 'G338', + 7, + [[3, 4], [2, 3], [1, 2], [5, 1], [6, 5], [7, 6], [5, 2]]], + ['edgelist', + 'G339', + 7, + [[6, 3], [7, 6], [3, 7], [2, 3], [5, 2], [1, 5], [4, 2]]], + ['edgelist', + 'G340', + 7, + [[3, 4], [2, 3], [1, 2], [5, 1], [6, 5], [7, 6], [6, 3]]], + ['edgelist', + 'G341', + 7, + [[2, 5], [1, 2], [3, 1], [4, 3], [6, 4], [1, 6], [7, 4]]], + ['edgelist', + 'G342', + 7, + [[3, 2], [4, 3], [7, 4], [6, 7], [1, 6], [3, 1], [6, 5]]], + ['edgelist', + 'G343', + 7, + [[6, 3], [7, 6], [3, 7], [2, 3], [1, 2], [5, 1], [4, 1]]], + ['edgelist', + 'G344', + 7, + [[5, 2], [1, 5], [4, 1], [2, 4], [3, 2], [6, 3], [7, 3]]], + ['edgelist', + 'G345', + 7, + [[2, 1], [3, 2], [6, 3], [5, 6], [1, 5], [5, 2], [7, 4]]], + ['edgelist', + 'G346', + 7, + [[3, 6], [7, 3], [1, 5], [4, 1], [2, 4], [5, 2], [2, 1]]], + ['edgelist', + 'G347', + 7, + [[7, 6], [1, 5], [4, 1], [2, 4], [5, 2], [3, 5], [4, 3]]], + ['edgelist', + 'G348', + 7, + [[3, 2], [6, 3], [5, 6], [1, 5], [4, 1], [7, 4], [3, 7]]], + ['edgelist', + 'G349', + 7, + [[5, 1], [4, 5], [2, 4], [3, 2], [6, 3], [7, 6], [3, 7]]], + ['edgelist', + 'G350', + 7, + [[7, 6], [3, 7], [2, 3], [5, 2], [1, 5], [4, 1], [2, 4]]], + ['edgelist', + 'G351', + 7, + [[5, 2], [1, 5], [3, 1], [4, 3], [7, 4], [6, 7], [1, 6]]], + ['edgelist', + 'G352', + 7, + [[1, 5], [4, 1], [5, 4], [3, 2], [6, 3], [7, 6], [3, 7]]], + ['edgelist', + 'G353', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [1, 7]]], + ['edgelist', + 'G354', + 7, + [[2, 1], [5, 2], [1, 5], [6, 3], [7, 6], [4, 7], [3, 4]]], + ['edgelist', + 'G355', + 7, + [[1, 2], [5, 1], [6, 5], [3, 6], [2, 3], [6, 2], [5, 2], [3, 5]]], + ['edgelist', + 'G356', + 7, + [[5, 2], [6, 5], [3, 6], [2, 3], [1, 2], [6, 1], [1, 5], [3, 1]]], + ['edgelist', + 'G357', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [4, 5], [6, 2], [7, 2]]], + ['edgelist', + 'G358', + 7, + [[5, 2], [6, 5], [3, 6], [2, 3], [6, 2], [7, 6], [3, 5], [4, 3]]], + ['edgelist', + 'G359', + 7, + [[2, 4], [1, 2], [5, 1], [3, 5], [2, 3], [5, 2], [6, 5], [2, 6]]], + ['edgelist', + 'G360', + 7, + [[3, 1], [4, 3], [7, 4], [6, 7], [1, 6], [4, 1], [1, 7], [5, 1]]], + ['edgelist', + 'G361', + 7, + [[2, 1], [3, 2], [6, 3], [5, 6], [1, 5], [3, 1], [6, 1], [7, 6]]], + ['edgelist', + 'G362', + 7, + [[2, 1], [3, 2], [4, 3], [2, 4], [5, 4], [3, 5], [6, 3], [4, 6]]], + ['edgelist', + 'G363', + 7, + [[3, 1], [4, 3], [7, 4], [6, 7], [1, 6], [4, 1], [7, 1], [5, 6]]], + ['edgelist', + 'G364', + 7, + [[2, 1], [3, 2], [5, 4], [2, 6], [5, 2], [3, 5], [6, 3], [4, 6]]], + ['edgelist', + 'G365', + 7, + [[4, 6], [3, 2], [5, 4], [2, 6], [5, 2], [3, 5], [6, 3], [5, 7]]], + ['edgelist', + 'G366', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [4, 5], [3, 2], [6, 3]]], + ['edgelist', + 'G367', + 7, + [[4, 6], [3, 2], [5, 4], [2, 6], [5, 2], [3, 5], [6, 3], [1, 4]]], + ['edgelist', + 'G368', + 7, + [[5, 1], [3, 5], [1, 3], [4, 1], [3, 4], [6, 3], [7, 6], [3, 7]]], + ['edgelist', + 'G369', + 7, + [[4, 3], [7, 4], [6, 7], [3, 6], [1, 3], [6, 1], [5, 6], [3, 5]]], + ['edgelist', + 'G370', + 7, + [[1, 6], [5, 1], [3, 5], [6, 3], [2, 6], [5, 2], [4, 5], [6, 4]]], + ['edgelist', + 'G371', + 7, + [[3, 4], [2, 3], [5, 2], [6, 5], [2, 6], [6, 3], [7, 6], [4, 7]]], + ['edgelist', + 'G372', + 7, + [[6, 3], [5, 6], [1, 5], [4, 1], [7, 4], [3, 7], [5, 3], [4, 3]]], + ['edgelist', + 'G373', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [3, 5], [4, 3], [6, 5], [3, 6]]], + ['edgelist', + 'G374', + 7, + [[6, 7], [3, 6], [7, 3], [4, 3], [5, 4], [1, 5], [4, 1], [3, 5]]], + ['edgelist', + 'G375', + 7, + [[2, 1], [6, 1], [4, 3], [2, 4], [6, 3], [7, 2], [7, 3], [7, 6]]], + ['edgelist', + 'G376', + 7, + [[6, 5], [7, 6], [4, 7], [1, 4], [5, 1], [3, 5], [4, 3], [1, 3]]], + ['edgelist', + 'G377', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [5, 3], [2, 6]]], + ['edgelist', + 'G378', + 7, + [[6, 1], [7, 3], [1, 7], [2, 1], [3, 2], [6, 3], [5, 6], [5, 7]]], + ['edgelist', + 'G379', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [3, 2], [2, 6], [7, 2]]], + ['edgelist', + 'G380', + 7, + [[1, 3], [5, 1], [2, 5], [1, 2], [4, 1], [2, 4], [6, 2], [7, 2]]], + ['edgelist', + 'G381', + 7, + [[5, 3], [1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [2, 6], [7, 2]]], + ['edgelist', + 'G382', + 7, + [[1, 5], [4, 1], [5, 4], [2, 5], [4, 2], [2, 6], [3, 2], [7, 2]]], + ['edgelist', + 'G383', + 7, + [[3, 2], [1, 3], [4, 1], [6, 4], [3, 6], [4, 3], [5, 4], [7, 6]]], + ['edgelist', + 'G384', + 7, + [[5, 3], [1, 5], [4, 1], [2, 4], [5, 2], [4, 5], [2, 6], [7, 2]]], + ['edgelist', + 'G385', + 7, + [[3, 2], [1, 3], [4, 1], [6, 4], [3, 6], [7, 6], [5, 4], [6, 1]]], + ['edgelist', + 'G386', + 7, + [[2, 1], [3, 2], [4, 3], [2, 4], [5, 3], [4, 5], [5, 6], [7, 5]]], + ['edgelist', + 'G387', + 7, + [[7, 6], [2, 3], [5, 2], [1, 5], [4, 1], [2, 4], [1, 2], [4, 5]]], + ['edgelist', + 'G388', + 7, + [[1, 2], [7, 6], [3, 4], [7, 5], [7, 4], [7, 3], [7, 1], [7, 2]]], + ['edgelist', + 'G389', + 7, + [[7, 5], [2, 3], [3, 4], [7, 6], [5, 6], [7, 3], [7, 1], [7, 2]]], + ['edgelist', + 'G390', + 7, + [[1, 2], [2, 3], [3, 4], [7, 6], [7, 5], [7, 4], [7, 1], [7, 3]]], + ['edgelist', + 'G391', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [7, 2], [6, 2], [3, 6]]], + ['edgelist', + 'G392', + 7, + [[4, 1], [3, 4], [5, 3], [1, 5], [2, 1], [3, 2], [6, 3], [7, 3]]], + ['edgelist', + 'G393', + 7, + [[3, 2], [4, 3], [7, 4], [6, 7], [1, 6], [3, 1], [6, 3], [5, 6]]], + ['edgelist', + 'G394', + 7, + [[2, 1], [3, 2], [4, 3], [5, 4], [6, 3], [2, 6], [7, 2], [3, 7]]], + ['edgelist', + 'G395', + 7, + [[3, 6], [5, 3], [2, 5], [4, 2], [1, 4], [2, 1], [3, 2], [7, 3]]], + ['edgelist', + 'G396', + 7, + [[5, 6], [1, 5], [4, 1], [3, 4], [5, 3], [2, 5], [4, 2], [7, 4]]], + ['edgelist', + 'G397', + 7, + [[1, 2], [5, 1], [2, 5], [3, 2], [5, 3], [6, 5], [2, 6], [7, 4]]], + ['edgelist', + 'G398', + 7, + [[1, 2], [3, 1], [4, 3], [5, 4], [2, 5], [3, 2], [2, 7], [6, 1]]], + ['edgelist', + 'G399', + 7, + [[5, 6], [1, 5], [2, 1], [5, 2], [4, 1], [2, 4], [7, 2], [3, 7]]], + ['edgelist', + 'G400', + 7, + [[3, 6], [5, 3], [1, 5], [2, 1], [5, 2], [4, 1], [2, 4], [7, 2]]], + ['edgelist', + 'G401', + 7, + [[2, 7], [3, 2], [1, 3], [2, 1], [5, 2], [4, 5], [3, 4], [5, 6]]], + ['edgelist', + 'G402', + 7, + [[1, 2], [3, 1], [4, 3], [5, 4], [2, 5], [3, 2], [2, 7], [6, 4]]], + ['edgelist', + 'G403', + 7, + [[1, 5], [4, 1], [5, 4], [2, 5], [4, 2], [6, 2], [7, 3], [2, 7]]], + ['edgelist', + 'G404', + 7, + [[3, 4], [2, 3], [1, 2], [6, 1], [5, 6], [1, 5], [3, 1], [7, 6]]], + ['edgelist', + 'G405', + 7, + [[5, 6], [1, 5], [4, 1], [2, 4], [5, 2], [3, 5], [4, 3], [7, 3]]], + ['edgelist', + 'G406', + 7, + [[3, 4], [2, 3], [1, 2], [5, 1], [6, 5], [5, 2], [3, 7], [6, 3]]], + ['edgelist', + 'G407', + 7, + [[1, 2], [2, 3], [3, 4], [7, 4], [5, 6], [7, 3], [7, 1], [7, 2]]], + ['edgelist', + 'G408', + 7, + [[5, 2], [1, 5], [4, 1], [2, 4], [1, 2], [3, 2], [6, 3], [7, 3]]], + ['edgelist', + 'G409', + 7, + [[1, 2], [2, 3], [3, 4], [7, 6], [5, 6], [7, 3], [7, 5], [7, 2]]], + ['edgelist', + 'G410', + 7, + [[1, 2], [5, 1], [1, 3], [6, 1], [7, 6], [4, 7], [3, 4], [6, 3]]], + ['edgelist', + 'G411', + 7, + [[1, 5], [4, 1], [3, 4], [5, 3], [2, 5], [4, 2], [3, 6], [7, 3]]], + ['edgelist', + 'G412', + 7, + [[5, 6], [4, 5], [2, 4], [3, 2], [7, 3], [5, 7], [4, 3], [1, 2]]], + ['edgelist', + 'G413', + 7, + [[2, 1], [3, 7], [4, 3], [5, 4], [6, 3], [2, 6], [7, 2], [7, 6]]], + ['edgelist', + 'G414', + 7, + [[3, 4], [2, 3], [1, 2], [5, 1], [6, 5], [7, 6], [6, 3], [5, 2]]], + ['edgelist', + 'G415', + 7, + [[5, 2], [1, 5], [4, 1], [2, 4], [4, 5], [3, 2], [3, 6], [7, 3]]], + ['edgelist', + 'G416', + 7, + [[1, 7], [5, 1], [2, 5], [4, 2], [1, 4], [3, 5], [4, 3], [6, 3]]], + ['edgelist', + 'G417', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [3, 5], [4, 3], [2, 1], [7, 6]]], + ['edgelist', + 'G418', + 7, + [[1, 2], [5, 1], [4, 3], [7, 4], [6, 7], [3, 6], [7, 3], [4, 6]]], + ['edgelist', + 'G419', + 7, + [[6, 3], [7, 6], [3, 7], [5, 3], [1, 5], [4, 1], [3, 4], [2, 3]]], + ['edgelist', + 'G420', + 7, + [[3, 1], [2, 3], [1, 2], [6, 1], [5, 6], [1, 5], [7, 1], [4, 7]]], + ['edgelist', + 'G421', + 7, + [[1, 2], [3, 1], [4, 3], [3, 2], [2, 5], [6, 5], [6, 4], [2, 7]]], + ['edgelist', + 'G422', + 7, + [[2, 7], [3, 2], [1, 3], [2, 1], [5, 2], [4, 5], [3, 4], [6, 7]]], + ['edgelist', + 'G423', + 7, + [[7, 2], [1, 7], [2, 1], [6, 2], [1, 6], [3, 2], [4, 3], [5, 4]]], + ['edgelist', + 'G424', + 7, + [[7, 6], [3, 7], [2, 3], [5, 2], [4, 5], [1, 4], [5, 1], [3, 5]]], + ['edgelist', + 'G425', + 7, + [[2, 7], [1, 2], [6, 1], [2, 6], [4, 1], [5, 4], [3, 5], [1, 3]]], + ['edgelist', + 'G426', + 7, + [[3, 7], [5, 3], [1, 5], [2, 1], [5, 2], [4, 5], [6, 4], [3, 6]]], + ['edgelist', + 'G427', + 7, + [[2, 1], [3, 2], [7, 3], [6, 7], [2, 6], [5, 2], [4, 5], [3, 4]]], + ['edgelist', + 'G428', + 7, + [[7, 2], [5, 4], [2, 1], [6, 2], [4, 3], [3, 2], [5, 7], [6, 5]]], + ['edgelist', + 'G429', + 7, + [[5, 3], [1, 5], [2, 1], [5, 2], [4, 5], [7, 4], [6, 7], [4, 6]]], + ['edgelist', + 'G430', + 7, + [[5, 2], [3, 5], [1, 3], [7, 1], [4, 7], [1, 4], [6, 1], [5, 6]]], + ['edgelist', + 'G431', + 7, + [[6, 7], [5, 6], [1, 5], [4, 1], [3, 4], [5, 3], [2, 5], [4, 2]]], + ['edgelist', + 'G432', + 7, + [[7, 4], [6, 7], [5, 6], [1, 5], [2, 1], [3, 2], [6, 3], [5, 2]]], + ['edgelist', + 'G433', + 7, + [[1, 2], [3, 1], [4, 3], [3, 2], [2, 5], [6, 5], [6, 4], [5, 7]]], + ['edgelist', + 'G434', + 7, + [[5, 1], [4, 5], [3, 4], [7, 3], [6, 7], [2, 6], [5, 2], [3, 2]]], + ['edgelist', + 'G435', + 7, + [[7, 2], [1, 7], [5, 4], [6, 2], [1, 6], [3, 2], [4, 3], [6, 7]]], + ['edgelist', + 'G436', + 7, + [[7, 3], [6, 7], [4, 6], [7, 4], [5, 4], [1, 5], [2, 1], [5, 2]]], + ['edgelist', + 'G437', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 7], [6, 2]]], + ['edgelist', + 'G438', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 7], [5, 3]]], + ['edgelist', + 'G439', + 7, + [[1, 2], [3, 1], [4, 3], [5, 4], [2, 5], [3, 2], [6, 7], [1, 6]]], + ['edgelist', + 'G440', + 7, + [[5, 1], [3, 5], [4, 3], [7, 4], [6, 7], [5, 6], [2, 3], [6, 2]]], + ['edgelist', + 'G441', + 7, + [[6, 2], [3, 5], [4, 3], [1, 4], [6, 1], [5, 6], [2, 3], [1, 7]]], + ['edgelist', + 'G442', + 7, + [[6, 7], [3, 6], [5, 3], [1, 5], [4, 1], [3, 4], [2, 5], [4, 2]]], + ['edgelist', + 'G443', + 7, + [[1, 5], [2, 1], [5, 2], [4, 5], [6, 4], [7, 6], [3, 7], [5, 3]]], + ['edgelist', + 'G444', + 7, + [[1, 2], [7, 6], [3, 4], [4, 5], [7, 5], [1, 6], [7, 3], [7, 2]]], + ['edgelist', + 'G445', + 7, + [[2, 3], [1, 2], [5, 1], [6, 5], [3, 6], [4, 3], [7, 4], [6, 7]]], + ['edgelist', + 'G446', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 7], [2, 7]]], + ['edgelist', + 'G447', + 7, + [[7, 3], [6, 7], [3, 6], [2, 3], [5, 2], [1, 5], [4, 1], [2, 4]]], + ['edgelist', + 'G448', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [7, 6], [7, 2]]], + ['edgelist', + 'G449', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [7, 1], [7, 4]]], + ['edgelist', + 'G450', + 7, + [[1, 5], [2, 1], [4, 3], [2, 5], [3, 6], [6, 4], [7, 5], [7, 4]]], + ['edgelist', + 'G451', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [2, 1], [7, 3], [6, 7], [3, 6]]], + ['edgelist', + 'G452', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [3, 5], [1, 4]]], + ['edgelist', + 'G453', + 7, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [5, 3]]], + ['edgelist', + 'G454', + 7, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [1, 5]]], + ['edgelist', + 'G455', + 7, + [[1, 2], [2, 3], [3, 4], [1, 4], [5, 1], [5, 2], [5, 3], [5, 4], [5, 6]]], + ['edgelist', + 'G456', + 7, + [[3, 1], [5, 2], [2, 3], [6, 5], [3, 6], [4, 2], [6, 4], [4, 3], [5, 4]]], + ['edgelist', + 'G457', + 7, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [5, 6]]], + ['edgelist', + 'G458', + 7, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4], [5, 6]]], + ['edgelist', + 'G459', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [3, 6], [1, 6], [3, 1], [4, 1]]], + ['edgelist', + 'G460', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [1, 5], [2, 1], [5, 2]]], + ['edgelist', + 'G461', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [5, 2], [2, 1], [6, 2]]], + ['edgelist', + 'G462', + 7, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 5], [5, 1], [6, 1]]], + ['edgelist', + 'G463', + 7, + [[5, 4], [1, 5], [2, 1], [3, 2], [4, 3], [1, 6], [6, 4], [1, 4], [2, 6]]], + ['edgelist', + 'G464', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [4, 3], [1, 4], [5, 1]]], + ['edgelist', + 'G465', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [5, 6], [4, 5], [4, 3], [1, 4], [3, 5]]], + ['edgelist', + 'G466', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [3, 6], [1, 6], [3, 1], [6, 2]]], + ['edgelist', + 'G467', + 7, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4], [3, 1]]], + ['edgelist', + 'G468', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 1], [6, 5], [6, 3], [6, 4]]], + ['edgelist', + 'G469', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [6, 2]]], + ['edgelist', + 'G470', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 4], [5, 3], [6, 3]]], + ['edgelist', + 'G471', + 7, + [[3, 4], [1, 3], [4, 1], [5, 4], [2, 5], [6, 2], [5, 6], [2, 1], [6, 3]]], + ['edgelist', + 'G472', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [1, 4], [6, 3], [5, 2]]], + ['edgelist', + 'G473', + 7, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 1], [1, 5], [1, 7]]], + ['edgelist', + 'G474', + 7, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 1], [1, 5], [3, 7]]], + ['edgelist', + 'G475', + 7, + [[2, 3], [4, 2], [1, 4], [2, 1], [3, 1], [4, 3], [6, 4], [5, 1], [2, 7]]], + ['edgelist', + 'G476', + 7, + [[1, 2], [3, 5], [1, 3], [4, 2], [4, 3], [3, 2], [5, 2], [6, 3], [3, 7]]], + ['edgelist', + 'G477', + 7, + [[1, 2], [3, 5], [1, 3], [6, 3], [4, 2], [4, 3], [3, 2], [5, 2], [2, 7]]], + ['edgelist', + 'G478', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [2, 6], [2, 7]]], + ['edgelist', + 'G479', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [2, 6], [5, 7]]], + ['edgelist', + 'G480', + 7, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [3, 2], [6, 2], [2, 7]]], + ['edgelist', + 'G481', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 5], [5, 7]]], + ['edgelist', + 'G482', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 5], [4, 7]]], + ['edgelist', + 'G483', + 7, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [3, 2], [6, 2], [1, 7]]], + ['edgelist', + 'G484', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 1], [2, 7]]], + ['edgelist', + 'G485', + 7, + [[3, 1], [4, 3], [5, 4], [6, 5], [3, 6], [2, 3], [5, 2], [6, 4], [3, 7]]], + ['edgelist', + 'G486', + 7, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [4, 1], [6, 2], [1, 7]]], + ['edgelist', + 'G487', + 7, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [6, 1], [1, 5], [6, 7]]], + ['edgelist', + 'G488', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 1], [5, 7]]], + ['edgelist', + 'G489', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 5], [3, 7]]], + ['edgelist', + 'G490', + 7, + [[3, 1], [4, 3], [5, 4], [6, 5], [3, 6], [2, 3], [5, 2], [6, 4], [6, 7]]], + ['edgelist', + 'G491', + 7, + [[2, 3], [4, 2], [1, 4], [2, 1], [3, 1], [4, 3], [5, 1], [7, 6], [7, 4]]], + ['edgelist', + 'G492', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 1], [1, 7]]], + ['edgelist', + 'G493', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 7], [6, 5], [1, 4], [3, 5]]], + ['edgelist', + 'G494', + 7, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [3, 2], [6, 2], [6, 7]]], + ['edgelist', + 'G495', + 7, + [[3, 1], [4, 3], [5, 4], [6, 5], [3, 6], [2, 3], [5, 2], [6, 4], [5, 7]]], + ['edgelist', + 'G496', + 7, + [[1, 2], [3, 6], [1, 3], [4, 1], [4, 2], [4, 3], [3, 2], [6, 2], [5, 7]]], + ['edgelist', + 'G497', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 1], [3, 7]]], + ['edgelist', + 'G498', + 7, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [4, 1], [6, 2], [6, 7]]], + ['edgelist', + 'G499', + 7, + [[1, 2], [3, 6], [1, 3], [6, 5], [4, 2], [4, 3], [4, 1], [6, 2], [3, 7]]], + ['edgelist', + 'G500', + 7, + [[1, 2], [3, 6], [1, 3], [5, 1], [4, 2], [4, 3], [6, 2], [6, 4], [1, 7]]], + ['edgelist', + 'G501', + 7, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [1, 6], [6, 5], [6, 7]]], + ['edgelist', + 'G502', + 7, + [[1, 2], [2, 3], [3, 4], [1, 4], [5, 1], [5, 2], [5, 3], [5, 4], [6, 7]]], + ['edgelist', + 'G503', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [4, 5], [5, 7]]], + ['edgelist', + 'G504', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 1], [6, 3], [1, 3], [1, 7]]], + ['edgelist', + 'G505', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [4, 5], [4, 7]]], + ['edgelist', + 'G506', + 7, + [[1, 2], [3, 5], [1, 3], [6, 3], [4, 2], [4, 3], [3, 2], [5, 2], [6, 7]]], + ['edgelist', + 'G507', + 7, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4], [5, 7]]], + ['edgelist', + 'G508', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3], [3, 7]]], + ['edgelist', + 'G509', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [7, 6], [7, 2]]], + ['edgelist', + 'G510', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [4, 5], [3, 7]]], + ['edgelist', + 'G511', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [4, 5], [1, 7]]], + ['edgelist', + 'G512', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [7, 6], [4, 7], [2, 7], [1, 2], [2, 5]]], + ['edgelist', + 'G513', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [1, 7]]], + ['edgelist', + 'G514', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [3, 2], [5, 7]]], + ['edgelist', + 'G515', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 5], [6, 7]]], + ['edgelist', + 'G516', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3], [5, 7]]], + ['edgelist', + 'G517', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3], [6, 7]]], + ['edgelist', + 'G518', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [7, 6], [4, 7], [2, 7], [1, 2], [1, 5]]], + ['edgelist', + 'G519', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 1], [6, 3], [1, 3], [2, 7]]], + ['edgelist', + 'G520', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 1], [6, 3], [1, 3], [5, 7]]], + ['edgelist', + 'G521', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [3, 2], [3, 7]]], + ['edgelist', + 'G522', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [4, 7]]], + ['edgelist', + 'G523', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [3, 7]]], + ['edgelist', + 'G524', + 7, + [[1, 2], [3, 6], [1, 3], [6, 2], [4, 2], [4, 3], [3, 2], [7, 1], [7, 5]]], + ['edgelist', + 'G525', + 7, + [[2, 6], [5, 2], [1, 5], [6, 1], [3, 6], [5, 3], [4, 5], [6, 4], [2, 7]]], + ['edgelist', + 'G526', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 4], [5, 2], [6, 1], [6, 7]]], + ['edgelist', + 'G527', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3], [2, 7]]], + ['edgelist', + 'G528', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3], [1, 7]]], + ['edgelist', + 'G529', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 3], [4, 7]]], + ['edgelist', + 'G530', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [7, 6], [4, 7], [2, 7], [1, 2], [3, 5]]], + ['edgelist', + 'G531', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [5, 6], [6, 4], [2, 6], [4, 7]]], + ['edgelist', + 'G532', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [2, 7]]], + ['edgelist', + 'G533', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 2], [5, 7]]], + ['edgelist', + 'G534', + 7, + [[1, 2], [3, 6], [1, 3], [6, 2], [4, 2], [4, 3], [4, 1], [7, 5], [7, 1]]], + ['edgelist', + 'G535', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 6], [6, 3], [6, 1], [2, 7]]], + ['edgelist', + 'G536', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [3, 2], [7, 1]]], + ['edgelist', + 'G537', + 7, + [[6, 4], [4, 3], [5, 4], [6, 5], [3, 6], [2, 3], [5, 2], [7, 1], [7, 3]]], + ['edgelist', + 'G538', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 6], [6, 3], [6, 1], [1, 7]]], + ['edgelist', + 'G539', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [5, 6], [6, 4], [2, 6], [6, 7]]], + ['edgelist', + 'G540', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [4, 1], [6, 3], [6, 1], [5, 7]]], + ['edgelist', + 'G541', + 7, + [[2, 4], [3, 2], [1, 3], [6, 1], [7, 6], [4, 7], [2, 7], [1, 2], [6, 5]]], + ['edgelist', + 'G542', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [5, 2], [6, 3], [6, 7]]], + ['edgelist', + 'G543', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [5, 6], [6, 4], [2, 6], [2, 7]]], + ['edgelist', + 'G544', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [3, 2], [4, 7]]], + ['edgelist', + 'G545', + 7, + [[1, 2], [2, 3], [1, 3], [4, 1], [4, 2], [4, 3], [1, 6], [6, 5], [5, 7]]], + ['edgelist', + 'G546', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [5, 6], [6, 4], [2, 6], [1, 7]]], + ['edgelist', + 'G547', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [2, 6], [6, 3], [6, 1], [5, 7]]], + ['edgelist', + 'G548', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [3, 5], [6, 2], [1, 7]]], + ['edgelist', + 'G549', + 7, + [[1, 2], [3, 6], [1, 3], [6, 4], [4, 2], [4, 3], [6, 2], [7, 5], [7, 1]]], + ['edgelist', + 'G550', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [5, 2], [6, 3], [1, 7]]], + ['edgelist', + 'G551', + 7, + [[7, 4], [2, 3], [7, 6], [4, 5], [7, 5], [1, 6], [7, 1], [7, 2], [7, 3]]], + ['edgelist', + 'G552', + 7, + [[1, 2], [3, 1], [4, 3], [5, 4], [2, 5], [3, 2], [7, 3], [6, 7], [3, 6]]], + ['edgelist', + 'G553', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [4, 5], [7, 6], [7, 1]]], + ['edgelist', + 'G554', + 7, + [[2, 5], [3, 5], [3, 4], [1, 5], [4, 2], [5, 6], [1, 6], [7, 5], [7, 4]]], + ['edgelist', + 'G555', + 7, + [[5, 2], [6, 5], [7, 6], [4, 7], [3, 4], [2, 3], [6, 3], [1, 6], [3, 1]]], + ['edgelist', + 'G556', + 7, + [[5, 2], [4, 2], [3, 4], [5, 1], [6, 1], [6, 3], [6, 5], [7, 5], [6, 7]]], + ['edgelist', + 'G557', + 7, + [[2, 1], [3, 2], [7, 3], [4, 7], [6, 4], [5, 6], [4, 5], [3, 4], [1, 3]]], + ['edgelist', + 'G558', + 7, + [[1, 3], [6, 1], [2, 6], [3, 2], [5, 3], [6, 5], [7, 6], [4, 7], [3, 4]]], + ['edgelist', + 'G559', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [1, 7], [2, 4], [5, 2]]], + ['edgelist', + 'G560', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 5], [6, 2], [7, 2], [1, 7]]], + ['edgelist', + 'G561', + 7, + [[1, 5], [2, 1], [5, 2], [4, 5], [3, 4], [7, 3], [6, 7], [2, 6], [3, 2]]], + ['edgelist', + 'G562', + 7, + [[1, 2], [3, 1], [4, 3], [5, 4], [2, 5], [3, 2], [6, 4], [7, 6], [4, 7]]], + ['edgelist', + 'G563', + 7, + [[7, 6], [4, 7], [3, 4], [1, 5], [1, 6], [2, 1], [3, 1], [2, 3], [6, 5]]], + ['edgelist', + 'G564', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 4], [6, 2], [7, 2], [1, 7]]], + ['edgelist', + 'G565', + 7, + [[6, 3], [7, 6], [4, 7], [3, 4], [1, 3], [5, 1], [6, 5], [2, 6], [1, 2]]], + ['edgelist', + 'G566', + 7, + [[3, 5], [2, 3], [5, 2], [6, 5], [1, 6], [2, 1], [7, 5], [4, 7], [3, 4]]], + ['edgelist', + 'G567', + 7, + [[7, 3], [6, 7], [3, 6], [2, 3], [1, 2], [5, 1], [2, 5], [4, 2], [1, 4]]], + ['edgelist', + 'G568', + 7, + [[1, 6], [7, 1], [2, 7], [5, 2], [3, 5], [4, 3], [2, 4], [6, 2], [7, 6]]], + ['edgelist', + 'G569', + 7, + [[7, 6], [4, 7], [3, 4], [6, 3], [1, 6], [2, 1], [5, 2], [1, 5], [3, 1]]], + ['edgelist', + 'G570', + 7, + [[1, 5], [4, 1], [2, 4], [5, 2], [3, 5], [7, 3], [6, 7], [3, 6], [4, 3]]], + ['edgelist', + 'G571', + 7, + [[2, 1], [5, 2], [6, 5], [1, 6], [7, 1], [4, 7], [3, 4], [1, 3], [4, 5]]], + ['edgelist', + 'G572', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [7, 1], [7, 2], [7, 4]]], + ['edgelist', + 'G573', + 7, + [[1, 2], [2, 3], [3, 4], [1, 4], [5, 2], [6, 5], [6, 4], [7, 1], [7, 5]]], + ['edgelist', + 'G574', + 7, + [[1, 2], [5, 1], [2, 5], [3, 2], [6, 3], [5, 6], [7, 6], [4, 7], [3, 4]]], + ['edgelist', + 'G575', + 7, + [[2, 1], [7, 4], [1, 5], [6, 1], [4, 6], [6, 7], [2, 3], [2, 5], [7, 3]]], + ['edgelist', + 'G576', + 7, + [[7, 3], [6, 7], [3, 6], [2, 3], [1, 4], [5, 1], [2, 5], [4, 2], [4, 5]]], + ['edgelist', + 'G577', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [7, 2], [6, 7], [7, 1]]], + ['edgelist', + 'G578', + 7, + [[1, 5], [2, 1], [3, 2], [4, 3], [1, 4], [3, 5], [6, 5], [7, 6], [4, 7]]], + ['edgelist', + 'G579', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [5, 3], [7, 2], [6, 7]]], + ['edgelist', + 'G580', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [1, 5], [6, 4], [6, 5], [7, 2], [7, 6]]], + ['edgelist', + 'G581', + 7, + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [1, 6], [7, 1], [7, 5], [7, 3]]], + ['edgelist', + 'G582', + 7, + [[1, 5], [4, 1], [5, 4], [7, 2], [6, 7], [2, 6], [3, 2], [6, 3], [7, 3]]], + ['edgelist', + 'G583', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3]]], + ['edgelist', + 'G584', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 5]]], + ['edgelist', + 'G585', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1]]], + ['edgelist', + 'G586', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [2, 1]]], + ['edgelist', + 'G587', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [6, 5], + [4, 6], + [2, 6]]], + ['edgelist', + 'G588', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [3, 5]]], + ['edgelist', + 'G589', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3]]], + ['edgelist', + 'G590', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3]]], + ['edgelist', + 'G591', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2]]], + ['edgelist', + 'G592', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [6, 3]]], + ['edgelist', + 'G593', + 7, + [[1, 2], + [3, 5], + [1, 3], + [5, 6], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4]]], + ['edgelist', + 'G594', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5]]], + ['edgelist', + 'G595', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [1, 3], + [2, 4], + [6, 2]]], + ['edgelist', + 'G596', + 7, + [[1, 2], + [2, 3], + [4, 5], + [1, 3], + [4, 1], + [3, 5], + [6, 3], + [2, 6], + [5, 2], + [4, 6]]], + ['edgelist', + 'G597', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [6, 4], + [3, 6], + [2, 1]]], + ['edgelist', + 'G598', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [5, 3], + [3, 7]]], + ['edgelist', + 'G599', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [5, 3], + [2, 7]]], + ['edgelist', + 'G600', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [1, 5], + [2, 7]]], + ['edgelist', + 'G601', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [1, 5], + [1, 7]]], + ['edgelist', + 'G602', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [1, 5], + [4, 7]]], + ['edgelist', + 'G603', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [5, 6], + [5, 7]]], + ['edgelist', + 'G604', + 7, + [[3, 1], + [5, 2], + [2, 3], + [6, 5], + [3, 6], + [4, 2], + [6, 4], + [4, 3], + [5, 4], + [4, 7]]], + ['edgelist', + 'G605', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [5, 6], + [3, 7]]], + ['edgelist', + 'G606', + 7, + [[3, 1], + [5, 2], + [2, 3], + [6, 5], + [3, 6], + [4, 2], + [6, 4], + [4, 3], + [5, 4], + [3, 7]]], + ['edgelist', + 'G607', + 7, + [[3, 4], + [2, 3], + [5, 2], + [6, 5], + [3, 6], + [1, 3], + [5, 1], + [1, 2], + [6, 1], + [7, 6]]], + ['edgelist', + 'G608', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [1, 5], + [6, 7]]], + ['edgelist', + 'G609', + 7, + [[3, 1], + [5, 2], + [2, 3], + [6, 5], + [3, 6], + [4, 2], + [6, 4], + [4, 3], + [5, 4], + [5, 7]]], + ['edgelist', + 'G610', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [5, 6], + [7, 6]]], + ['edgelist', + 'G611', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 7]]], + ['edgelist', + 'G612', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [7, 6]]], + ['edgelist', + 'G613', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [4, 1], + [1, 7]]], + ['edgelist', + 'G614', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [4, 1], + [3, 7]]], + ['edgelist', + 'G615', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [2, 7]]], + ['edgelist', + 'G616', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [4, 1], + [4, 7]]], + ['edgelist', + 'G617', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [1, 5], + [2, 1], + [5, 2], + [1, 7]]], + ['edgelist', + 'G618', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [2, 1], + [6, 2], + [2, 7]]], + ['edgelist', + 'G619', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [6, 5], + [5, 1], + [6, 1], + [1, 7]]], + ['edgelist', + 'G620', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [4, 7]]], + ['edgelist', + 'G621', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [4, 1], + [6, 7]]], + ['edgelist', + 'G622', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [4, 7]]], + ['edgelist', + 'G623', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [2, 1], + [6, 2], + [1, 7]]], + ['edgelist', + 'G624', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [3, 1], + [6, 4], + [3, 4], + [7, 3]]], + ['edgelist', + 'G625', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [7, 5], + [7, 3]]], + ['edgelist', + 'G626', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [3, 7]]], + ['edgelist', + 'G627', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [2, 1], + [6, 2], + [6, 7]]], + ['edgelist', + 'G628', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [4, 1], + [5, 7]]], + ['edgelist', + 'G629', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [4, 7]]], + ['edgelist', + 'G630', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [6, 5], + [5, 1], + [6, 1], + [3, 7]]], + ['edgelist', + 'G631', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [3, 1], + [6, 7]]], + ['edgelist', + 'G632', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [3, 7]]], + ['edgelist', + 'G633', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [3, 1], + [6, 4], + [3, 4], + [1, 7]]], + ['edgelist', + 'G634', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [2, 7]]], + ['edgelist', + 'G635', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [5, 7]]], + ['edgelist', + 'G636', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [7, 5], + [7, 1]]], + ['edgelist', + 'G637', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [6, 7]]], + ['edgelist', + 'G638', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [1, 5], + [2, 1], + [5, 2], + [6, 7]]], + ['edgelist', + 'G639', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [3, 1], + [1, 7]]], + ['edgelist', + 'G640', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [2, 1], + [6, 2], + [3, 7]]], + ['edgelist', + 'G641', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 4], + [5, 3], + [6, 3], + [3, 7]]], + ['edgelist', + 'G642', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 5], + [6, 3], + [6, 4], + [6, 7]]], + ['edgelist', + 'G643', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [4, 1], + [6, 3], + [6, 1], + [6, 2], + [1, 7]]], + ['edgelist', + 'G644', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [6, 5], + [5, 1], + [6, 1], + [6, 7]]], + ['edgelist', + 'G645', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [7, 6], + [7, 5]]], + ['edgelist', + 'G646', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [2, 7]]], + ['edgelist', + 'G647', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [5, 7]]], + ['edgelist', + 'G648', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 5], + [6, 3], + [6, 4], + [5, 7]]], + ['edgelist', + 'G649', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [3, 7]]], + ['edgelist', + 'G650', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 5], + [6, 3], + [6, 4], + [1, 7]]], + ['edgelist', + 'G651', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 4], + [5, 3], + [6, 3], + [6, 7]]], + ['edgelist', + 'G652', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [4, 1], + [6, 3], + [6, 1], + [6, 2], + [2, 7]]], + ['edgelist', + 'G653', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [4, 7]]], + ['edgelist', + 'G654', + 7, + [[5, 4], + [5, 2], + [2, 3], + [6, 5], + [3, 6], + [4, 2], + [6, 4], + [4, 3], + [7, 1], + [7, 3]]], + ['edgelist', + 'G655', + 7, + [[2, 1], + [3, 2], + [4, 3], + [5, 4], + [6, 5], + [2, 6], + [7, 2], + [5, 7], + [3, 7], + [6, 3]]], + ['edgelist', + 'G656', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 4], + [5, 3], + [6, 3], + [1, 7]]], + ['edgelist', + 'G657', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [4, 1], + [6, 3], + [6, 1], + [6, 2], + [4, 7]]], + ['edgelist', + 'G658', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [4, 1], + [6, 3], + [6, 1], + [6, 2], + [3, 7]]], + ['edgelist', + 'G659', + 7, + [[1, 2], + [3, 6], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [3, 2], + [6, 2], + [7, 6], + [7, 5]]], + ['edgelist', + 'G660', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [2, 7]]], + ['edgelist', + 'G661', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 7]]], + ['edgelist', + 'G662', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [3, 1], + [2, 7]]], + ['edgelist', + 'G663', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 5], + [6, 3], + [6, 4], + [2, 7]]], + ['edgelist', + 'G664', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 4], + [5, 3], + [6, 3], + [2, 7]]], + ['edgelist', + 'G665', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [4, 1], + [6, 3], + [6, 1], + [6, 2], + [5, 7]]], + ['edgelist', + 'G666', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [2, 1], + [6, 3], + [5, 7]]], + ['edgelist', + 'G667', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 4], + [6, 3], + [5, 2], + [1, 7]]], + ['edgelist', + 'G668', + 7, + [[5, 1], + [2, 5], + [4, 2], + [3, 4], + [2, 3], + [7, 2], + [1, 7], + [6, 1], + [2, 6], + [1, 2]]], + ['edgelist', + 'G669', + 7, + [[4, 3], + [7, 4], + [6, 7], + [1, 6], + [3, 1], + [6, 3], + [2, 6], + [3, 2], + [5, 3], + [6, 5]]], + ['edgelist', + 'G670', + 7, + [[3, 1], + [2, 3], + [4, 2], + [1, 4], + [7, 1], + [2, 7], + [6, 2], + [1, 6], + [5, 1], + [2, 5]]], + ['edgelist', + 'G671', + 7, + [[7, 5], + [2, 3], + [7, 6], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [7, 4]]], + ['edgelist', + 'G672', + 7, + [[1, 2], + [7, 6], + [3, 4], + [4, 5], + [7, 5], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [7, 4]]], + ['edgelist', + 'G673', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [3, 2], + [1, 6], + [6, 3], + [7, 2], + [3, 7]]], + ['edgelist', + 'G674', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [3, 2], + [6, 1], + [6, 3], + [7, 3], + [1, 7]]], + ['edgelist', + 'G675', + 7, + [[7, 5], + [2, 3], + [7, 6], + [4, 5], + [5, 6], + [1, 6], + [7, 4], + [7, 2], + [7, 3], + [1, 5]]], + ['edgelist', + 'G676', + 7, + [[2, 1], + [3, 2], + [1, 3], + [4, 3], + [5, 4], + [3, 5], + [6, 3], + [5, 6], + [7, 5], + [2, 7]]], + ['edgelist', + 'G677', + 7, + [[1, 2], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 6], + [3, 7]]], + ['edgelist', + 'G678', + 7, + [[1, 3], + [6, 1], + [5, 6], + [3, 5], + [2, 3], + [6, 2], + [7, 6], + [4, 7], + [3, 4], + [3, 7]]], + ['edgelist', + 'G679', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [3, 2], + [6, 2], + [1, 6], + [7, 1], + [3, 7]]], + ['edgelist', + 'G680', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 3], + [7, 5], + [1, 3], + [5, 1]]], + ['edgelist', + 'G681', + 7, + [[1, 5], + [4, 1], + [3, 4], + [6, 3], + [7, 6], + [3, 7], + [5, 3], + [2, 5], + [4, 2], + [5, 4]]], + ['edgelist', + 'G682', + 7, + [[2, 7], + [3, 2], + [1, 3], + [2, 1], + [5, 2], + [4, 5], + [3, 4], + [6, 7], + [5, 6], + [4, 2]]], + ['edgelist', + 'G683', + 7, + [[7, 6], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [7, 5]]], + ['edgelist', + 'G684', + 7, + [[1, 2], + [7, 6], + [3, 4], + [4, 5], + [7, 5], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [6, 4]]], + ['edgelist', + 'G685', + 7, + [[1, 2], + [2, 3], + [3, 4], + [6, 5], + [1, 5], + [6, 1], + [6, 4], + [6, 3], + [7, 6], + [7, 2]]], + ['edgelist', + 'G686', + 7, + [[1, 4], + [3, 1], + [2, 3], + [4, 2], + [5, 4], + [3, 5], + [1, 5], + [7, 1], + [6, 7], + [1, 6]]], + ['edgelist', + 'G687', + 7, + [[1, 4], + [3, 1], + [2, 3], + [4, 2], + [5, 4], + [1, 6], + [1, 5], + [7, 1], + [6, 7], + [2, 5]]], + ['edgelist', + 'G688', + 7, + [[1, 2], + [7, 6], + [3, 4], + [4, 5], + [7, 5], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [5, 3]]], + ['edgelist', + 'G689', + 7, + [[2, 3], + [6, 2], + [7, 6], + [3, 7], + [2, 7], + [6, 3], + [5, 2], + [1, 5], + [4, 1], + [2, 4]]], + ['edgelist', + 'G690', + 7, + [[5, 3], + [7, 3], + [6, 4], + [5, 2], + [3, 1], + [7, 4], + [6, 3], + [1, 2], + [1, 5], + [7, 1]]], + ['edgelist', + 'G691', + 7, + [[5, 3], + [4, 7], + [6, 4], + [6, 2], + [3, 1], + [7, 1], + [6, 3], + [2, 5], + [1, 5], + [6, 5]]], + ['edgelist', + 'G692', + 7, + [[5, 1], + [6, 5], + [5, 2], + [3, 2], + [4, 3], + [1, 4], + [4, 5], + [6, 4], + [7, 2], + [7, 6]]], + ['edgelist', + 'G693', + 7, + [[1, 5], + [2, 1], + [3, 2], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [6, 3], + [7, 4], + [3, 7]]], + ['edgelist', + 'G694', + 7, + [[2, 7], + [3, 2], + [1, 3], + [2, 1], + [5, 2], + [4, 5], + [3, 4], + [6, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G695', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 4], + [7, 2], + [7, 6], + [6, 2]]], + ['edgelist', + 'G696', + 7, + [[2, 1], + [5, 2], + [1, 5], + [3, 1], + [4, 3], + [7, 4], + [6, 7], + [1, 6], + [6, 3], + [7, 3]]], + ['edgelist', + 'G697', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 4], + [6, 2], + [6, 5], + [7, 2], + [6, 7]]], + ['edgelist', + 'G698', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [5, 2], + [7, 2], + [7, 6]]], + ['edgelist', + 'G699', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [3, 2], + [6, 4], + [3, 6], + [7, 2], + [5, 7]]], + ['edgelist', + 'G700', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 3], + [6, 5], + [7, 6], + [7, 1], + [1, 3]]], + ['edgelist', + 'G701', + 7, + [[3, 1], + [6, 3], + [2, 6], + [1, 2], + [4, 1], + [6, 4], + [7, 6], + [5, 7], + [1, 5], + [5, 4]]], + ['edgelist', + 'G702', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [5, 3], + [2, 6], + [7, 3], + [7, 6]]], + ['edgelist', + 'G703', + 7, + [[6, 1], + [7, 6], + [3, 7], + [4, 3], + [1, 4], + [5, 1], + [3, 5], + [5, 4], + [2, 5], + [4, 2]]], + ['edgelist', + 'G704', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [7, 4], + [6, 7], + [4, 6], + [5, 6], + [5, 7]]], + ['edgelist', + 'G705', + 7, + [[6, 3], + [3, 2], + [4, 3], + [5, 4], + [2, 5], + [6, 1], + [7, 2], + [7, 1], + [2, 6], + [3, 7]]], + ['edgelist', + 'G706', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 3], + [7, 5], + [5, 3], + [6, 2]]], + ['edgelist', + 'G707', + 7, + [[5, 3], + [3, 4], + [5, 2], + [1, 2], + [4, 1], + [7, 5], + [1, 7], + [6, 1], + [5, 6], + [2, 6]]], + ['edgelist', + 'G708', + 7, + [[3, 2], + [6, 3], + [4, 6], + [1, 4], + [5, 1], + [7, 5], + [4, 7], + [2, 4], + [5, 2], + [6, 5]]], + ['edgelist', + 'G709', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [7, 6], + [7, 4]]], + ['edgelist', + 'G710', + 7, + [[1, 2], + [5, 1], + [2, 5], + [3, 2], + [6, 3], + [5, 6], + [7, 6], + [4, 7], + [3, 4], + [6, 4]]], + ['edgelist', + 'G711', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 6], + [7, 2], + [7, 3], + [5, 3]]], + ['edgelist', + 'G712', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 4], + [6, 3], + [7, 6], + [7, 5]]], + ['edgelist', + 'G713', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 4], + [7, 3], + [5, 1]]], + ['edgelist', + 'G714', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 5], + [7, 6], + [7, 4]]], + ['edgelist', + 'G715', + 7, + [[1, 6], + [7, 1], + [2, 7], + [1, 2], + [2, 6], + [3, 2], + [4, 3], + [5, 4], + [7, 5], + [5, 6]]], + ['edgelist', + 'G716', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 6], + [7, 5], + [3, 1]]], + ['edgelist', + 'G717', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 6], + [7, 4]]], + ['edgelist', + 'G718', + 7, + [[3, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [6, 2], + [6, 1], + [7, 1], + [2, 7], + [7, 6]]], + ['edgelist', + 'G719', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 4], + [6, 2], + [7, 2], + [7, 5], + [7, 6]]], + ['edgelist', + 'G720', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 3], + [5, 2], + [7, 1], + [6, 7]]], + ['edgelist', + 'G721', + 7, + [[4, 2], + [1, 4], + [6, 1], + [2, 6], + [3, 2], + [7, 3], + [1, 7], + [1, 5], + [5, 3], + [5, 7]]], + ['edgelist', + 'G722', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 5], + [7, 2], + [7, 3], + [7, 6]]], + ['edgelist', + 'G723', + 7, + [[1, 4], + [3, 1], + [2, 3], + [4, 2], + [5, 4], + [3, 5], + [6, 5], + [6, 1], + [7, 5], + [7, 2]]], + ['edgelist', + 'G724', + 7, + [[1, 2], + [7, 6], + [3, 4], + [4, 5], + [7, 5], + [1, 6], + [7, 3], + [7, 2], + [5, 3], + [6, 2]]], + ['edgelist', + 'G725', + 7, + [[6, 3], + [7, 6], + [3, 7], + [5, 3], + [1, 5], + [4, 1], + [3, 4], + [2, 1], + [2, 4], + [5, 2]]], + ['edgelist', + 'G726', + 7, + [[4, 5], + [2, 4], + [5, 2], + [1, 5], + [4, 1], + [2, 1], + [3, 2], + [6, 3], + [7, 6], + [3, 7]]], + ['edgelist', + 'G727', + 7, + [[6, 7], + [3, 6], + [7, 3], + [4, 7], + [1, 4], + [5, 1], + [6, 5], + [2, 5], + [4, 2], + [3, 2]]], + ['edgelist', + 'G728', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 6], + [5, 3]]], + ['edgelist', + 'G729', + 7, + [[2, 1], + [3, 2], + [4, 3], + [1, 4], + [6, 1], + [2, 6], + [5, 6], + [7, 5], + [4, 7], + [3, 7]]], + ['edgelist', + 'G730', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [5, 2], + [3, 6], + [7, 1], + [4, 7]]], + ['edgelist', + 'G731', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3], + [2, 6]]], + ['edgelist', + 'G732', + 7, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4]]], + ['edgelist', + 'G733', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6]]], + ['edgelist', + 'G734', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [1, 3]]], + ['edgelist', + 'G735', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [6, 2]]], + ['edgelist', + 'G736', + 7, + [[2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G737', + 7, + [[4, 7], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [7, 6], + [3, 7], + [6, 2], + [1, 4], + [2, 7], + [1, 2]]], + ['edgelist', + 'G738', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3]]], + ['edgelist', + 'G739', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [1, 4]]], + ['edgelist', + 'G740', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 5], + [7, 5]]], + ['edgelist', + 'G741', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [7, 5]]], + ['edgelist', + 'G742', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 3], + [5, 7]]], + ['edgelist', + 'G743', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [3, 6], + [7, 3]]], + ['edgelist', + 'G744', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 3], + [1, 7]]], + ['edgelist', + 'G745', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [7, 6]]], + ['edgelist', + 'G746', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [2, 1], + [5, 7]]], + ['edgelist', + 'G747', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [6, 5], + [4, 6], + [2, 6], + [2, 7]]], + ['edgelist', + 'G748', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [6, 5], + [4, 6], + [2, 6], + [7, 5]]], + ['edgelist', + 'G749', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [5, 6], + [2, 1], + [2, 7]]], + ['edgelist', + 'G750', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [3, 5], + [3, 7]]], + ['edgelist', + 'G751', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [6, 5], + [4, 6], + [2, 6], + [6, 7]]], + ['edgelist', + 'G752', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3], + [3, 7]]], + ['edgelist', + 'G753', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3], + [7, 2]]], + ['edgelist', + 'G754', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3], + [4, 7]]], + ['edgelist', + 'G755', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3], + [7, 5]]], + ['edgelist', + 'G756', + 7, + [[1, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G757', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [6, 3], + [1, 7]]], + ['edgelist', + 'G758', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3], + [1, 7]]], + ['edgelist', + 'G759', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2], + [2, 7]]], + ['edgelist', + 'G760', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3], + [6, 7]]], + ['edgelist', + 'G761', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [6, 5], + [4, 6], + [2, 6], + [1, 7]]], + ['edgelist', + 'G762', + 7, + [[1, 2], + [3, 5], + [1, 3], + [5, 6], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [3, 7]]], + ['edgelist', + 'G763', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3], + [4, 7]]], + ['edgelist', + 'G764', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2], + [3, 7]]], + ['edgelist', + 'G765', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3], + [6, 7]]], + ['edgelist', + 'G766', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [6, 3], + [6, 7]]], + ['edgelist', + 'G767', + 7, + [[1, 2], + [3, 5], + [1, 3], + [5, 6], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [6, 7]]], + ['edgelist', + 'G768', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 5], + [6, 7]]], + ['edgelist', + 'G769', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [6, 3], + [2, 7]]], + ['edgelist', + 'G770', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2], + [5, 7]]], + ['edgelist', + 'G771', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2], + [6, 7]]], + ['edgelist', + 'G772', + 7, + [[1, 2], + [3, 5], + [1, 3], + [5, 6], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [5, 7]]], + ['edgelist', + 'G773', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [5, 1], + [3, 5], + [2, 7]]], + ['edgelist', + 'G774', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [7, 6], + [7, 3]]], + ['edgelist', + 'G775', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5], + [6, 7]]], + ['edgelist', + 'G776', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [1, 3], + [2, 4], + [6, 2], + [2, 7]]], + ['edgelist', + 'G777', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5], + [2, 7]]], + ['edgelist', + 'G778', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [5, 6], + [4, 5], + [2, 4], + [6, 2], + [3, 4], + [2, 3], + [3, 7]]], + ['edgelist', + 'G779', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [4, 3], + [1, 4], + [3, 5], + [6, 3], + [2, 7]]], + ['edgelist', + 'G780', + 7, + [[1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [6, 7]]], + ['edgelist', + 'G781', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [6, 4], + [3, 6], + [2, 1], + [2, 7]]], + ['edgelist', + 'G782', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [1, 3], + [2, 4], + [6, 2], + [6, 7]]], + ['edgelist', + 'G783', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [1, 3], + [2, 4], + [6, 2], + [7, 4]]], + ['edgelist', + 'G784', + 7, + [[5, 4], + [1, 5], + [2, 1], + [3, 2], + [4, 3], + [1, 6], + [6, 4], + [1, 4], + [2, 6], + [6, 3], + [5, 7]]], + ['edgelist', + 'G785', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 6], + [1, 6], + [3, 1], + [6, 2], + [5, 2], + [7, 4]]], + ['edgelist', + 'G786', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [4, 3], + [1, 4], + [2, 7]]], + ['edgelist', + 'G787', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [6, 4], + [3, 6], + [2, 1], + [7, 3]]], + ['edgelist', + 'G788', + 7, + [[1, 2], + [3, 5], + [1, 3], + [5, 6], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 7]]], + ['edgelist', + 'G789', + 7, + [[1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6]]], + ['edgelist', + 'G790', + 7, + [[7, 6], + [1, 7], + [6, 1], + [2, 6], + [7, 2], + [3, 7], + [6, 3], + [4, 6], + [7, 4], + [5, 7], + [6, 5]]], + ['edgelist', + 'G791', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [3, 2], + [6, 2], + [3, 6], + [7, 3], + [2, 7], + [4, 2]]], + ['edgelist', + 'G792', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 2], + [4, 6], + [7, 2], + [5, 7], + [2, 5], + [4, 2]]], + ['edgelist', + 'G793', + 7, + [[2, 5], + [3, 4], + [5, 3], + [1, 7], + [5, 6], + [7, 6], + [4, 2], + [7, 5], + [4, 1], + [4, 7], + [5, 4]]], + ['edgelist', + 'G794', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [5, 3], + [7, 5], + [3, 7], + [6, 3], + [1, 3]]], + ['edgelist', + 'G795', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [5, 1], + [4, 1], + [3, 1], + [7, 1], + [4, 7]]], + ['edgelist', + 'G796', + 7, + [[1, 2], + [3, 1], + [6, 3], + [7, 6], + [3, 7], + [2, 3], + [5, 2], + [3, 5], + [4, 3], + [5, 4], + [4, 2]]], + ['edgelist', + 'G797', + 7, + [[5, 6], + [2, 5], + [3, 2], + [4, 3], + [7, 4], + [6, 7], + [3, 6], + [5, 3], + [4, 6], + [1, 6], + [3, 1]]], + ['edgelist', + 'G798', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [5, 3], + [6, 3], + [6, 1], + [7, 3], + [5, 7], + [6, 5]]], + ['edgelist', + 'G799', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [7, 2], + [3, 7], + [1, 3], + [7, 1], + [6, 3], + [1, 6]]], + ['edgelist', + 'G800', + 7, + [[1, 6], + [7, 1], + [2, 7], + [6, 2], + [3, 6], + [7, 3], + [5, 4], + [4, 3], + [5, 6], + [7, 5], + [7, 6]]], + ['edgelist', + 'G801', + 7, + [[1, 6], + [7, 1], + [2, 7], + [6, 2], + [3, 6], + [7, 3], + [4, 7], + [6, 4], + [5, 6], + [7, 5], + [5, 4]]], + ['edgelist', + 'G802', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 7], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G803', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [1, 3], + [3, 5], + [6, 3], + [5, 6], + [7, 6], + [7, 1]]], + ['edgelist', + 'G804', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [1, 7], + [5, 3], + [1, 5], + [3, 1], + [7, 5]]], + ['edgelist', + 'G805', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [6, 2], + [6, 3], + [7, 2], + [3, 7], + [5, 3], + [6, 5]]], + ['edgelist', + 'G806', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 2], + [3, 6], + [5, 3], + [7, 3], + [5, 7]]], + ['edgelist', + 'G807', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 5], + [7, 3], + [1, 3], + [5, 1]]], + ['edgelist', + 'G808', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [4, 2], + [6, 4], + [5, 6], + [2, 5], + [7, 6], + [7, 2]]], + ['edgelist', + 'G809', + 7, + [[1, 5], + [4, 1], + [5, 4], + [3, 5], + [4, 3], + [2, 4], + [3, 2], + [5, 2], + [6, 3], + [7, 6], + [3, 7]]], + ['edgelist', + 'G810', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 7], + [3, 4], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G811', + 7, + [[1, 2], + [5, 1], + [6, 5], + [7, 6], + [4, 7], + [3, 4], + [2, 3], + [5, 2], + [3, 5], + [6, 3], + [2, 6]]], + ['edgelist', + 'G812', + 7, + [[1, 5], + [4, 1], + [5, 4], + [3, 5], + [7, 3], + [2, 7], + [6, 2], + [3, 6], + [4, 3], + [2, 4], + [5, 2]]], + ['edgelist', + 'G813', + 7, + [[1, 2], + [7, 6], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [7, 4], + [7, 5]]], + ['edgelist', + 'G814', + 7, + [[5, 2], + [1, 5], + [2, 1], + [4, 2], + [1, 4], + [6, 2], + [7, 6], + [2, 7], + [3, 2], + [6, 3], + [7, 3]]], + ['edgelist', + 'G815', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 5], + [7, 6], + [5, 7]]], + ['edgelist', + 'G816', + 7, + [[2, 1], + [3, 2], + [4, 3], + [5, 4], + [1, 5], + [3, 1], + [6, 3], + [7, 6], + [4, 7], + [7, 1], + [1, 6]]], + ['edgelist', + 'G817', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 3], + [5, 1], + [7, 5], + [1, 7], + [4, 7]]], + ['edgelist', + 'G818', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [3, 1], + [6, 3], + [7, 6], + [5, 7], + [1, 6], + [7, 1]]], + ['edgelist', + 'G819', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [3, 7], + [4, 7], + [1, 4], + [5, 1]]], + ['edgelist', + 'G820', + 7, + [[5, 7], + [6, 5], + [7, 6], + [4, 7], + [6, 4], + [3, 6], + [4, 3], + [6, 1], + [7, 1], + [2, 1], + [3, 2]]], + ['edgelist', + 'G821', + 7, + [[3, 1], + [5, 3], + [6, 5], + [4, 6], + [2, 4], + [1, 2], + [3, 2], + [4, 3], + [7, 4], + [6, 7], + [5, 4]]], + ['edgelist', + 'G822', + 7, + [[5, 4], + [5, 3], + [2, 5], + [4, 2], + [1, 4], + [2, 1], + [4, 3], + [4, 6], + [3, 6], + [7, 1], + [5, 7]]], + ['edgelist', + 'G823', + 7, + [[1, 2], + [1, 3], + [3, 4], + [6, 2], + [2, 4], + [6, 3], + [7, 4], + [7, 1], + [6, 4], + [5, 6], + [4, 5]]], + ['edgelist', + 'G824', + 7, + [[5, 1], + [2, 5], + [7, 2], + [1, 7], + [4, 1], + [2, 4], + [6, 2], + [1, 6], + [7, 6], + [3, 4], + [1, 3]]], + ['edgelist', + 'G825', + 7, + [[1, 2], + [6, 1], + [5, 6], + [2, 5], + [3, 2], + [4, 3], + [5, 4], + [5, 3], + [7, 5], + [3, 7], + [4, 7]]], + ['edgelist', + 'G826', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 2], + [4, 6], + [7, 4], + [5, 7], + [6, 7]]], + ['edgelist', + 'G827', + 7, + [[7, 4], + [6, 7], + [3, 6], + [4, 3], + [6, 4], + [5, 6], + [3, 5], + [2, 3], + [6, 2], + [1, 2], + [5, 1]]], + ['edgelist', + 'G828', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 2], + [7, 6], + [5, 7]]], + ['edgelist', + 'G829', + 7, + [[1, 5], + [4, 1], + [3, 4], + [6, 3], + [7, 6], + [3, 7], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2]]], + ['edgelist', + 'G830', + 7, + [[6, 1], + [1, 2], + [4, 1], + [6, 4], + [3, 6], + [7, 3], + [5, 7], + [6, 5], + [2, 6], + [7, 2], + [4, 7]]], + ['edgelist', + 'G831', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [3, 2], + [6, 2], + [3, 6], + [7, 5], + [7, 3], + [4, 7]]], + ['edgelist', + 'G832', + 7, + [[4, 3], + [7, 4], + [6, 7], + [1, 6], + [3, 1], + [2, 3], + [1, 2], + [6, 2], + [3, 6], + [5, 3], + [7, 5]]], + ['edgelist', + 'G833', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [5, 2], + [6, 4], + [6, 2], + [7, 5], + [7, 6], + [4, 7]]], + ['edgelist', + 'G834', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 4], + [7, 1], + [7, 3], + [7, 4], + [6, 7]]], + ['edgelist', + 'G835', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 2], + [7, 6], + [5, 7], + [4, 7], + [2, 7]]], + ['edgelist', + 'G836', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 2], + [7, 6], + [2, 7], + [3, 7], + [5, 3]]], + ['edgelist', + 'G837', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 4], + [5, 3], + [7, 2], + [7, 5], + [6, 3], + [4, 6]]], + ['edgelist', + 'G838', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 2], + [7, 2], + [5, 7], + [7, 6], + [3, 7]]], + ['edgelist', + 'G839', + 7, + [[1, 4], + [1, 7], + [2, 3], + [2, 6], + [3, 5], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G840', + 7, + [[6, 2], + [7, 6], + [5, 7], + [4, 5], + [3, 4], + [1, 3], + [2, 1], + [6, 1], + [7, 1], + [3, 7], + [5, 3]]], + ['edgelist', + 'G841', + 7, + [[2, 1], + [3, 2], + [4, 3], + [5, 4], + [1, 5], + [6, 3], + [4, 6], + [7, 1], + [7, 6], + [7, 3], + [4, 7]]], + ['edgelist', + 'G842', + 7, + [[1, 4], + [5, 1], + [3, 5], + [4, 3], + [2, 4], + [5, 2], + [6, 2], + [7, 1], + [7, 2], + [6, 4], + [5, 6]]], + ['edgelist', + 'G843', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G844', + 7, + [[1, 3], + [2, 1], + [3, 2], + [1, 4], + [4, 2], + [6, 5], + [6, 4], + [7, 5], + [7, 3], + [7, 1], + [2, 7]]], + ['edgelist', + 'G845', + 7, + [[5, 2], + [6, 5], + [3, 6], + [2, 3], + [1, 2], + [6, 1], + [7, 6], + [4, 7], + [3, 4], + [1, 3], + [5, 1]]], + ['edgelist', + 'G846', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [7, 2], + [7, 3], + [6, 2], + [4, 6], + [6, 3], + [5, 6]]], + ['edgelist', + 'G847', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6]]], + ['edgelist', + 'G848', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 4], + [7, 5], + [7, 3], + [3, 1], + [5, 1]]], + ['edgelist', + 'G849', + 7, + [[1, 3], + [2, 1], + [3, 2], + [1, 4], + [4, 2], + [6, 5], + [6, 4], + [7, 5], + [7, 3], + [5, 1], + [2, 5]]], + ['edgelist', + 'G850', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [7, 3], + [1, 7], + [2, 7]]], + ['edgelist', + 'G851', + 7, + [[1, 4], + [5, 1], + [2, 5], + [4, 2], + [5, 4], + [1, 2], + [3, 5], + [7, 3], + [6, 7], + [3, 6], + [2, 3]]], + ['edgelist', + 'G852', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 5], + [6, 3], + [6, 4], + [7, 2], + [7, 6]]], + ['edgelist', + 'G853', + 7, + [[5, 2], + [6, 5], + [3, 6], + [2, 3], + [1, 2], + [5, 1], + [6, 1], + [7, 6], + [4, 7], + [3, 4], + [6, 4]]], + ['edgelist', + 'G854', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 4], + [5, 3], + [6, 2], + [6, 5], + [7, 6], + [5, 7]]], + ['edgelist', + 'G855', + 7, + [[1, 2], + [2, 3], + [3, 4], + [6, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [7, 4], + [7, 5]]], + ['edgelist', + 'G856', + 7, + [[1, 5], + [4, 1], + [2, 4], + [5, 2], + [4, 5], + [6, 2], + [7, 6], + [2, 7], + [3, 2], + [6, 3], + [7, 3]]], + ['edgelist', + 'G857', + 7, + [[5, 2], + [1, 5], + [4, 1], + [3, 6], + [6, 5], + [7, 6], + [3, 7], + [2, 3], + [4, 2], + [7, 4], + [3, 4]]], + ['edgelist', + 'G858', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 3], + [6, 5], + [6, 4], + [7, 1], + [7, 6], + [4, 7]]], + ['edgelist', + 'G859', + 7, + [[6, 3], + [3, 5], + [6, 4], + [5, 2], + [6, 5], + [1, 2], + [4, 1], + [1, 3], + [7, 3], + [7, 4], + [1, 7]]], + ['edgelist', + 'G860', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 5], + [6, 3], + [6, 4], + [7, 2], + [1, 7]]], + ['edgelist', + 'G861', + 7, + [[1, 4], + [1, 5], + [2, 3], + [2, 6], + [2, 7], + [3, 5], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G862', + 7, + [[5, 1], + [4, 5], + [6, 4], + [1, 6], + [2, 1], + [3, 2], + [4, 3], + [5, 2], + [6, 3], + [7, 5], + [6, 7]]], + ['edgelist', + 'G863', + 7, + [[3, 4], + [5, 3], + [1, 5], + [6, 1], + [2, 6], + [5, 2], + [4, 5], + [6, 4], + [2, 1], + [7, 6], + [7, 3]]], + ['edgelist', + 'G864', + 7, + [[5, 2], + [1, 5], + [4, 1], + [5, 4], + [6, 5], + [7, 6], + [3, 7], + [2, 3], + [4, 2], + [7, 4], + [3, 6]]], + ['edgelist', + 'G865', + 7, + [[1, 4], + [5, 1], + [3, 5], + [4, 3], + [2, 4], + [1, 2], + [7, 1], + [6, 7], + [3, 6], + [2, 6], + [5, 2]]], + ['edgelist', + 'G866', + 7, + [[1, 4], + [1, 5], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G867', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 4], + [5, 3], + [6, 2], + [6, 5], + [7, 2], + [6, 7]]], + ['edgelist', + 'G868', + 7, + [[5, 2], + [6, 5], + [7, 6], + [4, 7], + [3, 4], + [2, 3], + [1, 2], + [6, 1], + [5, 1], + [6, 3], + [7, 3]]], + ['edgelist', + 'G869', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [5, 3], + [4, 6], + [5, 6], + [4, 1], + [7, 6], + [7, 2]]], + ['edgelist', + 'G870', + 7, + [[1, 5], + [2, 1], + [5, 2], + [4, 5], + [3, 4], + [2, 3], + [7, 2], + [6, 7], + [4, 6], + [6, 5], + [3, 7]]], + ['edgelist', + 'G871', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [6, 2], + [5, 3], + [7, 3], + [4, 7], + [5, 7]]], + ['edgelist', + 'G872', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 3], + [2, 7], + [6, 3], + [5, 2], + [1, 4]]], + ['edgelist', + 'G873', + 7, + [[1, 4], + [1, 5], + [2, 3], + [2, 6], + [2, 7], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G874', + 7, + [[1, 2], + [2, 3], + [3, 4], + [6, 5], + [1, 5], + [6, 4], + [6, 2], + [7, 4], + [7, 5], + [5, 3], + [1, 4]]], + ['edgelist', + 'G875', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 7], + [4, 7], + [5, 6]]], + ['edgelist', + 'G876', + 7, + [[5, 4], + [3, 5], + [4, 3], + [1, 4], + [3, 2], + [6, 5], + [6, 1], + [7, 5], + [7, 2], + [2, 6], + [1, 7]]], + ['edgelist', + 'G877', + 7, + [[7, 5], + [4, 7], + [2, 4], + [5, 2], + [1, 5], + [3, 1], + [4, 3], + [1, 2], + [6, 1], + [7, 6], + [6, 3]]], + ['edgelist', + 'G878', + 7, + [[7, 2], + [3, 7], + [2, 3], + [1, 2], + [4, 1], + [5, 4], + [6, 5], + [4, 6], + [3, 1], + [5, 1], + [6, 7]]], + ['edgelist', + 'G879', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [5, 4], + [1, 3]]], + ['edgelist', + 'G880', + 7, + [[4, 7], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [7, 6], + [3, 7], + [6, 2], + [1, 4], + [2, 7], + [1, 2], + [1, 7]]], + ['edgelist', + 'G881', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [6, 2], + [3, 5]]], + ['edgelist', + 'G882', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [3, 4]]], + ['edgelist', + 'G883', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 3], + [4, 2], + [5, 1], + [3, 5], + [6, 2], + [1, 6], + [5, 6], + [4, 5], + [6, 4]]], + ['edgelist', + 'G884', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3], + [2, 6], + [7, 2]]], + ['edgelist', + 'G885', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3], + [5, 7], + [6, 4]]], + ['edgelist', + 'G886', + 7, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4], + [2, 7]]], + ['edgelist', + 'G887', + 7, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4], + [4, 7]]], + ['edgelist', + 'G888', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6], + [5, 7]]], + ['edgelist', + 'G889', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6], + [7, 2]]], + ['edgelist', + 'G890', + 7, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4], + [1, 7]]], + ['edgelist', + 'G891', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6], + [1, 7]]], + ['edgelist', + 'G892', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6], + [3, 7]]], + ['edgelist', + 'G893', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [1, 3], + [2, 7]]], + ['edgelist', + 'G894', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [1, 3], + [5, 7]]], + ['edgelist', + 'G895', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [1, 3], + [7, 2], + [7, 6]]], + ['edgelist', + 'G896', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 2], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [2, 7]]], + ['edgelist', + 'G897', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [4, 6], + [1, 4], + [6, 7]]], + ['edgelist', + 'G898', + 7, + [[4, 7], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [7, 6], + [3, 7], + [6, 2], + [1, 4], + [2, 7], + [1, 2], + [2, 5]]], + ['edgelist', + 'G899', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [4, 6], + [1, 4], + [4, 7]]], + ['edgelist', + 'G900', + 7, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4], + [5, 7]]], + ['edgelist', + 'G901', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 2], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [3, 7]]], + ['edgelist', + 'G902', + 7, + [[4, 7], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [7, 6], + [3, 7], + [6, 2], + [1, 4], + [2, 7], + [1, 2], + [1, 5]]], + ['edgelist', + 'G903', + 7, + [[2, 4], + [5, 2], + [4, 5], + [3, 4], + [1, 3], + [5, 1], + [6, 5], + [3, 6], + [5, 3], + [1, 6], + [2, 6], + [4, 7]]], + ['edgelist', + 'G904', + 7, + [[2, 4], + [5, 2], + [4, 5], + [3, 4], + [1, 3], + [5, 1], + [6, 5], + [3, 6], + [5, 3], + [1, 6], + [2, 6], + [1, 7]]], + ['edgelist', + 'G905', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 1], + [5, 6], + [6, 7]]], + ['edgelist', + 'G906', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [1, 4], + [6, 7]]], + ['edgelist', + 'G907', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3], + [5, 7]]], + ['edgelist', + 'G908', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3], + [1, 7]]], + ['edgelist', + 'G909', + 7, + [[4, 7], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [7, 6], + [3, 7], + [6, 2], + [1, 4], + [2, 7], + [1, 2], + [5, 6]]], + ['edgelist', + 'G910', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3], + [4, 7]]], + ['edgelist', + 'G911', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [1, 4], + [1, 7]]], + ['edgelist', + 'G912', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [1, 3], + [6, 7]]], + ['edgelist', + 'G913', + 7, + [[1, 4], + [7, 1], + [6, 7], + [4, 6], + [2, 4], + [7, 2], + [5, 7], + [4, 5], + [3, 4], + [7, 3], + [4, 7], + [6, 5]]], + ['edgelist', + 'G914', + 7, + [[1, 2], + [5, 1], + [6, 5], + [2, 6], + [5, 2], + [3, 5], + [2, 3], + [7, 2], + [5, 7], + [3, 7], + [4, 3], + [5, 4]]], + ['edgelist', + 'G915', + 7, + [[5, 2], + [4, 3], + [4, 1], + [5, 3], + [6, 2], + [6, 1], + [4, 6], + [5, 4], + [6, 5], + [7, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G916', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [5, 3], + [4, 5], + [6, 4], + [1, 6], + [7, 4], + [2, 7]]], + ['edgelist', + 'G917', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [7, 4], + [1, 7], + [6, 1], + [2, 6], + [5, 2], + [3, 5]]], + ['edgelist', + 'G918', + 7, + [[7, 3], + [6, 7], + [4, 6], + [3, 4], + [2, 3], + [5, 2], + [6, 5], + [3, 6], + [5, 3], + [1, 5], + [2, 1], + [6, 2]]], + ['edgelist', + 'G919', + 7, + [[6, 5], + [7, 6], + [4, 7], + [5, 4], + [1, 5], + [4, 1], + [2, 4], + [1, 2], + [5, 2], + [4, 6], + [3, 4], + [5, 3]]], + ['edgelist', + 'G920', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [6, 1], + [2, 6], + [7, 2], + [1, 7]]], + ['edgelist', + 'G921', + 7, + [[2, 3], + [1, 2], + [3, 1], + [4, 3], + [1, 4], + [2, 4], + [5, 3], + [1, 5], + [6, 5], + [3, 6], + [7, 3], + [2, 7]]], + ['edgelist', + 'G922', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 3], + [5, 6], + [7, 5], + [4, 7]]], + ['edgelist', + 'G923', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [6, 1], + [2, 6], + [7, 2], + [3, 7]]], + ['edgelist', + 'G924', + 7, + [[2, 3], + [1, 2], + [3, 1], + [4, 3], + [1, 4], + [2, 4], + [5, 3], + [1, 5], + [7, 5], + [3, 7], + [6, 3], + [5, 6]]], + ['edgelist', + 'G925', + 7, + [[2, 1], + [3, 2], + [1, 3], + [4, 1], + [5, 4], + [1, 5], + [6, 1], + [4, 6], + [5, 6], + [7, 5], + [4, 7], + [7, 1]]], + ['edgelist', + 'G926', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [7, 6], + [3, 7]]], + ['edgelist', + 'G927', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [7, 5], + [1, 7], + [6, 1], + [4, 6]]], + ['edgelist', + 'G928', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [2, 6], + [7, 2], + [1, 7], + [6, 1], + [5, 6]]], + ['edgelist', + 'G929', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [7, 2], + [1, 7], + [6, 1], + [3, 6]]], + ['edgelist', + 'G930', + 7, + [[6, 5], + [4, 6], + [5, 4], + [7, 5], + [4, 7], + [3, 4], + [5, 3], + [1, 5], + [4, 1], + [2, 1], + [3, 2], + [7, 3]]], + ['edgelist', + 'G931', + 7, + [[5, 2], + [4, 3], + [4, 1], + [5, 3], + [6, 2], + [6, 1], + [4, 6], + [5, 4], + [6, 5], + [7, 6], + [1, 7], + [4, 7]]], + ['edgelist', + 'G932', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [7, 2], + [1, 7], + [6, 1], + [2, 6]]], + ['edgelist', + 'G933', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [4, 2], + [6, 2], + [7, 6], + [5, 7]]], + ['edgelist', + 'G934', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [6, 5], + [4, 6], + [7, 4], + [5, 7]]], + ['edgelist', + 'G935', + 7, + [[2, 1], + [3, 2], + [4, 3], + [1, 4], + [5, 4], + [2, 5], + [5, 1], + [6, 5], + [1, 6], + [4, 6], + [7, 1], + [2, 7]]], + ['edgelist', + 'G936', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [7, 3], + [5, 7], + [6, 1], + [2, 6]]], + ['edgelist', + 'G937', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [6, 5], + [3, 6], + [2, 6], + [7, 2], + [6, 7]]], + ['edgelist', + 'G938', + 7, + [[1, 3], + [2, 1], + [3, 2], + [1, 4], + [4, 2], + [5, 3], + [6, 4], + [7, 2], + [7, 5], + [5, 1], + [4, 5], + [2, 6]]], + ['edgelist', + 'G939', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 3], + [5, 4], + [5, 2], + [1, 5], + [6, 3], + [6, 5], + [7, 1], + [4, 7]]], + ['edgelist', + 'G940', + 7, + [[6, 1], + [3, 6], + [7, 3], + [4, 7], + [3, 4], + [2, 3], + [1, 2], + [5, 1], + [2, 5], + [6, 2], + [7, 6], + [1, 3]]], + ['edgelist', + 'G941', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [7, 5], + [3, 7], + [6, 3], + [4, 6]]], + ['edgelist', + 'G942', + 7, + [[1, 3], + [2, 1], + [6, 2], + [4, 6], + [7, 4], + [3, 7], + [5, 3], + [4, 5], + [6, 5], + [3, 6], + [2, 3], + [5, 2]]], + ['edgelist', + 'G943', + 7, + [[1, 3], + [2, 1], + [3, 2], + [1, 4], + [4, 2], + [5, 1], + [2, 5], + [5, 3], + [4, 5], + [6, 5], + [7, 6], + [4, 7]]], + ['edgelist', + 'G944', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [4, 2], + [4, 3], + [7, 2], + [3, 7], + [5, 7], + [4, 5], + [6, 4], + [7, 6]]], + ['edgelist', + 'G945', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [6, 1], + [7, 6], + [1, 7], + [4, 5]]], + ['edgelist', + 'G946', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 2], + [3, 6], + [7, 1], + [4, 7]]], + ['edgelist', + 'G947', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 4], + [6, 2], + [6, 5], + [7, 4], + [5, 7], + [2, 7], + [7, 6]]], + ['edgelist', + 'G948', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G949', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 3], + [7, 6], + [1, 7], + [7, 3], + [1, 6], + [2, 6], + [7, 2]]], + ['edgelist', + 'G950', + 7, + [[1, 2], + [7, 6], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [7, 4], + [7, 5], + [6, 2]]], + ['edgelist', + 'G951', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5], + [7, 2], + [6, 7]]], + ['edgelist', + 'G952', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 2], + [5, 6], + [7, 5], + [6, 7]]], + ['edgelist', + 'G953', + 7, + [[3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 2], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [7, 4], + [7, 2]]], + ['edgelist', + 'G954', + 7, + [[1, 5], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G955', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G956', + 7, + [[1, 2], + [3, 5], + [1, 3], + [3, 2], + [5, 7], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4], + [7, 2], + [3, 7]]], + ['edgelist', + 'G957', + 7, + [[1, 2], + [2, 3], + [3, 4], + [6, 5], + [1, 5], + [6, 4], + [6, 2], + [7, 4], + [7, 5], + [5, 3], + [1, 4], + [5, 4]]], + ['edgelist', + 'G958', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [7, 2], + [7, 6]]], + ['edgelist', + 'G959', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 2], + [7, 6], + [1, 7], + [2, 7]]], + ['edgelist', + 'G960', + 7, + [[1, 4], + [5, 1], + [3, 5], + [4, 3], + [2, 4], + [5, 2], + [2, 1], + [6, 2], + [6, 3], + [7, 2], + [3, 7], + [5, 7]]], + ['edgelist', + 'G961', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [6, 5], + [5, 2], + [2, 7], + [6, 4], + [2, 6], + [7, 3]]], + ['edgelist', + 'G962', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [6, 5], + [5, 2], + [2, 6], + [6, 4], + [7, 2], + [5, 7]]], + ['edgelist', + 'G963', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [6, 5], + [5, 2], + [2, 6], + [6, 4], + [7, 2], + [1, 7]]], + ['edgelist', + 'G964', + 7, + [[5, 4], + [2, 3], + [1, 2], + [1, 4], + [5, 1], + [7, 5], + [5, 3], + [6, 5], + [7, 3], + [7, 4], + [4, 3], + [6, 2]]], + ['edgelist', + 'G965', + 7, + [[3, 4], + [5, 3], + [1, 5], + [7, 1], + [7, 6], + [5, 6], + [2, 4], + [6, 2], + [1, 6], + [7, 2], + [4, 7], + [6, 4]]], + ['edgelist', + 'G966', + 7, + [[1, 4], + [1, 6], + [2, 3], + [2, 6], + [2, 7], + [3, 5], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G967', + 7, + [[1, 4], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G968', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [5, 4], + [6, 4], + [7, 2], + [4, 7], + [7, 3], + [1, 7], + [5, 7]]], + ['edgelist', + 'G969', + 7, + [[1, 2], + [3, 5], + [1, 3], + [7, 2], + [4, 2], + [4, 3], + [5, 2], + [6, 2], + [6, 3], + [6, 4], + [1, 4], + [5, 7]]], + ['edgelist', + 'G970', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [7, 4], + [2, 7]]], + ['edgelist', + 'G971', + 7, + [[5, 4], + [2, 3], + [6, 1], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [6, 2], + [7, 3], + [7, 4], + [4, 3], + [7, 5]]], + ['edgelist', + 'G972', + 7, + [[3, 4], + [5, 3], + [6, 5], + [1, 6], + [7, 1], + [2, 7], + [4, 2], + [7, 4], + [6, 4], + [2, 6], + [5, 1], + [4, 1]]], + ['edgelist', + 'G973', + 7, + [[1, 4], + [1, 6], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G974', + 7, + [[4, 3], + [2, 3], + [6, 1], + [1, 4], + [5, 1], + [5, 2], + [7, 5], + [6, 2], + [7, 3], + [7, 4], + [7, 2], + [1, 7]]], + ['edgelist', + 'G975', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 7], + [3, 4], + [3, 5], + [3, 7], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G976', + 7, + [[1, 4], + [1, 6], + [2, 3], + [2, 5], + [2, 7], + [3, 5], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G977', + 7, + [[1, 4], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G978', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G979', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [6, 5], + [5, 2], + [4, 5], + [6, 4], + [3, 7], + [7, 2]]], + ['edgelist', + 'G980', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [6, 5], + [5, 2], + [4, 5], + [6, 4], + [7, 2], + [7, 6]]], + ['edgelist', + 'G981', + 7, + [[1, 3], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G982', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G983', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 2], + [3, 5], + [6, 3], + [1, 6], + [5, 4], + [7, 6], + [7, 5], + [4, 6]]], + ['edgelist', + 'G984', + 7, + [[1, 3], + [1, 7], + [2, 3], + [2, 5], + [2, 6], + [3, 4], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G985', + 7, + [[1, 3], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [3, 5], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G986', + 7, + [[1, 3], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G987', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 7], + [3, 4], + [3, 5], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [6, 7]]], + ['edgelist', + 'G988', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G989', + 7, + [[4, 1], + [3, 4], + [5, 3], + [1, 5], + [6, 2], + [6, 3], + [7, 2], + [7, 1], + [4, 7], + [6, 4], + [5, 6], + [7, 5]]], + ['edgelist', + 'G990', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G991', + 7, + [[1, 2], + [1, 3], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G992', + 7, + [[4, 1], + [3, 4], + [5, 3], + [1, 5], + [6, 2], + [6, 3], + [7, 2], + [7, 1], + [4, 7], + [6, 4], + [7, 5], + [2, 4]]], + ['edgelist', + 'G993', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G994', + 7, + [[3, 4], + [5, 3], + [6, 3], + [5, 2], + [7, 1], + [4, 1], + [4, 2], + [7, 4], + [6, 7], + [2, 6], + [5, 1], + [4, 5]]], + ['edgelist', + 'G995', + 7, + [[3, 4], + [5, 3], + [5, 2], + [3, 6], + [7, 1], + [7, 5], + [4, 2], + [7, 4], + [1, 4], + [2, 6], + [5, 1], + [6, 4]]], + ['edgelist', + 'G996', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 7], + [3, 4], + [3, 7], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G997', + 7, + [[4, 1], + [3, 4], + [5, 3], + [1, 5], + [6, 2], + [6, 3], + [7, 2], + [7, 1], + [4, 7], + [2, 4], + [7, 5], + [6, 5]]], + ['edgelist', + 'G998', + 7, + [[7, 4], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [1, 7], + [5, 2], + [4, 5], + [7, 6], + [6, 2], + [1, 5]]], + ['edgelist', + 'G999', + 7, + [[1, 4], + [1, 6], + [1, 7], + [2, 3], + [2, 6], + [2, 7], + [3, 5], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1000', + 7, + [[1, 4], + [1, 6], + [1, 7], + [2, 3], + [2, 5], + [2, 7], + [3, 4], + [3, 6], + [4, 5], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1001', + 7, + [[1, 4], + [1, 6], + [1, 7], + [2, 3], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1002', + 7, + [[1, 5], + [4, 1], + [2, 4], + [5, 2], + [2, 1], + [5, 6], + [3, 5], + [7, 3], + [6, 7], + [3, 6], + [4, 3], + [7, 4]]], + ['edgelist', + 'G1003', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [6, 3], + [6, 5], + [7, 5], + [7, 4], + [7, 3], + [6, 4]]], + ['edgelist', + 'G1004', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1005', + 7, + [[4, 1], + [5, 3], + [4, 2], + [5, 1], + [6, 3], + [6, 2], + [5, 4], + [6, 5], + [4, 6], + [7, 2], + [1, 7], + [3, 7]]], + ['edgelist', + 'G1006', + 7, + [[2, 1], + [5, 2], + [1, 5], + [6, 1], + [7, 6], + [2, 7], + [4, 5], + [6, 4], + [3, 4], + [6, 3], + [7, 4], + [3, 7]]], + ['edgelist', + 'G1007', + 7, + [[1, 2], + [3, 1], + [3, 4], + [4, 5], + [1, 5], + [1, 6], + [7, 2], + [5, 7], + [7, 6], + [3, 7], + [4, 2], + [6, 4]]], + ['edgelist', + 'G1008', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [7, 1], + [7, 2], + [7, 3], + [7, 4], + [7, 5], + [7, 6]]], + ['edgelist', + 'G1009', + 7, + [[4, 7], + [2, 3], + [1, 7], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 2], + [3, 6], + [5, 6], + [7, 5]]], + ['edgelist', + 'G1010', + 7, + [[2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1011', + 7, + [[2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1012', + 7, + [[1, 7], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1013', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [5, 4], + [1, 3], + [7, 5]]], + ['edgelist', + 'G1014', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [1, 5], + [2, 7]]], + ['edgelist', + 'G1015', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1016', + 7, + [[1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1017', + 7, + [[1, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1018', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1019', + 7, + [[1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1020', + 7, + [[1, 7], + [2, 4], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1021', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3], + [5, 6], + [2, 7]]], + ['edgelist', + 'G1022', + 7, + [[1, 2], + [2, 3], + [3, 4], + [5, 6], + [1, 5], + [2, 4], + [5, 2], + [3, 5], + [1, 4], + [6, 4], + [5, 4], + [1, 3], + [6, 7]]], + ['edgelist', + 'G1023', + 7, + [[1, 6], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1024', + 7, + [[1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1025', + 7, + [[6, 7], + [1, 6], + [7, 1], + [5, 7], + [6, 5], + [2, 6], + [7, 2], + [4, 7], + [6, 4], + [3, 6], + [7, 3], + [2, 1], + [3, 2]]], + ['edgelist', + 'G1026', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 7]]], + ['edgelist', + 'G1027', + 7, + [[4, 5], + [1, 4], + [5, 1], + [2, 5], + [4, 2], + [3, 4], + [5, 3], + [2, 1], + [3, 2], + [7, 1], + [4, 7], + [6, 4], + [5, 6]]], + ['edgelist', + 'G1028', + 7, + [[4, 5], + [1, 4], + [5, 1], + [2, 5], + [4, 2], + [3, 4], + [5, 3], + [2, 1], + [3, 2], + [7, 1], + [4, 7], + [6, 4], + [1, 6]]], + ['edgelist', + 'G1029', + 7, + [[4, 5], + [1, 4], + [5, 1], + [2, 5], + [4, 2], + [3, 4], + [5, 3], + [2, 1], + [3, 2], + [7, 5], + [1, 7], + [6, 1], + [4, 6]]], + ['edgelist', + 'G1030', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1031', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 6], + [5, 7]]], + ['edgelist', + 'G1032', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [6, 2], + [7, 6], + [2, 7]]], + ['edgelist', + 'G1033', + 7, + [[1, 5], + [1, 7], + [2, 4], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1034', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1035', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1036', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [6, 4], + [7, 6], + [5, 7]]], + ['edgelist', + 'G1037', + 7, + [[1, 6], + [1, 7], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1038', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [7, 2], + [7, 6], + [6, 2]]], + ['edgelist', + 'G1039', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 5], + [1, 6], + [7, 1], + [4, 7], + [7, 5]]], + ['edgelist', + 'G1040', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [7, 2], + [6, 2], + [3, 7]]], + ['edgelist', + 'G1041', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [6, 7]]], + ['edgelist', + 'G1042', + 7, + [[2, 1], + [3, 2], + [5, 3], + [2, 5], + [4, 2], + [1, 4], + [3, 4], + [6, 3], + [2, 6], + [1, 6], + [7, 1], + [2, 7], + [3, 7]]], + ['edgelist', + 'G1043', + 7, + [[3, 6], + [7, 3], + [6, 7], + [5, 6], + [4, 5], + [1, 4], + [5, 1], + [2, 5], + [4, 2], + [7, 4], + [3, 2], + [5, 3], + [4, 3]]], + ['edgelist', + 'G1044', + 7, + [[1, 4], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1045', + 7, + [[3, 5], + [4, 3], + [2, 4], + [5, 2], + [1, 5], + [4, 1], + [7, 4], + [2, 7], + [6, 2], + [5, 6], + [7, 5], + [4, 6], + [2, 3]]], + ['edgelist', + 'G1046', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 5], + [3, 6], + [4, 6], + [4, 7]]], + ['edgelist', + 'G1047', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 6], + [6, 7]]], + ['edgelist', + 'G1048', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 6], + [4, 7], + [5, 6]]], + ['edgelist', + 'G1049', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 7], + [3, 5], + [3, 6], + [4, 5], + [4, 6]]], + ['edgelist', + 'G1050', + 7, + [[1, 3], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1051', + 7, + [[3, 6], + [2, 3], + [6, 2], + [5, 6], + [4, 5], + [1, 4], + [5, 1], + [4, 3], + [5, 3], + [2, 4], + [7, 4], + [3, 7], + [2, 7]]], + ['edgelist', + 'G1052', + 7, + [[1, 5], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1053', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [1, 5], + [7, 2], + [5, 7]]], + ['edgelist', + 'G1054', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [2, 1], + [6, 3], + [6, 1], + [7, 6], + [2, 7], + [5, 1]]], + ['edgelist', + 'G1055', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [2, 4], + [4, 3], + [5, 1], + [3, 6], + [4, 5], + [6, 4], + [1, 6], + [7, 5], + [7, 2]]], + ['edgelist', + 'G1056', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [2, 1], + [6, 3], + [7, 3], + [6, 7], + [1, 6], + [2, 3]]], + ['edgelist', + 'G1057', + 7, + [[6, 5], + [7, 3], + [7, 5], + [5, 4], + [6, 1], + [4, 2], + [4, 3], + [7, 4], + [6, 7], + [5, 1], + [2, 5], + [6, 2], + [1, 4]]], + ['edgelist', + 'G1058', + 7, + [[1, 3], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1059', + 7, + [[2, 6], + [5, 2], + [1, 5], + [6, 1], + [3, 6], + [5, 3], + [4, 5], + [6, 4], + [1, 2], + [3, 1], + [4, 3], + [7, 6], + [7, 5]]], + ['edgelist', + 'G1060', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [1, 5], + [1, 7], + [5, 7]]], + ['edgelist', + 'G1061', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [6, 1], + [5, 6], + [2, 6], + [7, 2], + [1, 7], + [4, 7]]], + ['edgelist', + 'G1062', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1063', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [5, 2], + [6, 4], + [3, 6], + [2, 1], + [7, 4], + [5, 7], + [6, 2]]], + ['edgelist', + 'G1064', + 7, + [[6, 3], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [2, 1], + [7, 3], + [7, 4], + [2, 3], + [4, 2], + [5, 1]]], + ['edgelist', + 'G1065', + 7, + [[2, 1], + [3, 2], + [1, 3], + [1, 4], + [4, 3], + [7, 3], + [2, 7], + [6, 2], + [7, 6], + [5, 7], + [6, 5], + [1, 6], + [5, 1]]], + ['edgelist', + 'G1066', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G1067', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1068', + 7, + [[1, 2], + [2, 3], + [5, 2], + [4, 2], + [1, 5], + [3, 4], + [1, 4], + [3, 1], + [6, 1], + [7, 6], + [5, 7], + [4, 6], + [5, 3]]], + ['edgelist', + 'G1069', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1070', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [1, 5], + [3, 4], + [7, 6], + [1, 7]]], + ['edgelist', + 'G1071', + 7, + [[6, 3], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [2, 1], + [7, 3], + [7, 4], + [3, 4], + [6, 1], + [5, 1]]], + ['edgelist', + 'G1072', + 7, + [[1, 2], + [2, 3], + [3, 4], + [6, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [7, 4], + [7, 5], + [5, 3], + [1, 4]]], + ['edgelist', + 'G1073', + 7, + [[1, 2], + [1, 7], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1074', + 7, + [[1, 2], + [1, 7], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1075', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [7, 3], + [1, 7], + [6, 1], + [3, 6], + [6, 4], + [5, 6], + [7, 5], + [4, 7]]], + ['edgelist', + 'G1076', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [7, 6], + [1, 7], + [1, 3], + [3, 6], + [6, 4], + [5, 6], + [7, 5], + [4, 7]]], + ['edgelist', + 'G1077', + 7, + [[4, 5], + [1, 4], + [5, 1], + [4, 7], + [4, 2], + [3, 4], + [5, 3], + [2, 1], + [3, 2], + [6, 3], + [4, 6], + [7, 3], + [6, 7]]], + ['edgelist', + 'G1078', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5], + [7, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G1079', + 7, + [[2, 1], + [3, 2], + [7, 1], + [2, 5], + [4, 2], + [1, 4], + [3, 4], + [2, 7], + [2, 6], + [3, 7], + [5, 4], + [6, 5], + [7, 6]]], + ['edgelist', + 'G1080', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 6], + [3, 7], + [4, 6], + [5, 7]]], + ['edgelist', + 'G1081', + 7, + [[1, 7], + [2, 3], + [3, 4], + [1, 4], + [5, 3], + [6, 1], + [7, 4], + [5, 2], + [4, 5], + [7, 6], + [2, 6], + [4, 6], + [2, 4]]], + ['edgelist', + 'G1082', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [7, 5], + [5, 4], + [6, 5], + [6, 3], + [7, 1], + [4, 7], + [4, 6]]], + ['edgelist', + 'G1083', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 7], + [1, 5], + [7, 6], + [1, 7], + [7, 5], + [3, 6], + [6, 4], + [5, 6], + [2, 6], + [7, 2]]], + ['edgelist', + 'G1084', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1085', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1086', + 7, + [[3, 4], + [6, 3], + [7, 6], + [4, 7], + [5, 4], + [6, 5], + [1, 6], + [3, 1], + [2, 3], + [1, 2], + [6, 2], + [5, 3], + [7, 5]]], + ['edgelist', + 'G1087', + 7, + [[3, 2], + [1, 6], + [7, 1], + [5, 7], + [6, 5], + [2, 6], + [7, 2], + [4, 7], + [6, 4], + [3, 6], + [7, 3], + [2, 1], + [4, 5]]], + ['edgelist', + 'G1088', + 7, + [[1, 2], + [3, 1], + [3, 4], + [4, 5], + [1, 5], + [1, 6], + [7, 2], + [5, 7], + [7, 6], + [3, 7], + [4, 2], + [6, 4], + [7, 1]]], + ['edgelist', + 'G1089', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 6], + [2, 7], + [3, 4], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1090', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [5, 7], + [6, 2], + [5, 6], + [4, 2], + [6, 3], + [7, 1], + [7, 2], + [3, 2], + [5, 2]]], + ['edgelist', + 'G1091', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 4], + [6, 5], + [6, 3], + [6, 2], + [7, 6], + [2, 7], + [3, 7]]], + ['edgelist', + 'G1092', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G1093', + 7, + [[4, 1], + [3, 4], + [5, 3], + [1, 5], + [6, 2], + [6, 3], + [7, 2], + [7, 1], + [4, 7], + [2, 4], + [7, 5], + [6, 5], + [6, 4]]], + ['edgelist', + 'G1094', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 5], + [2, 6], + [3, 4], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1095', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 5], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1096', + 7, + [[1, 3], + [1, 6], + [1, 7], + [2, 3], + [2, 5], + [2, 7], + [3, 4], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1097', + 7, + [[4, 5], + [6, 1], + [4, 6], + [1, 7], + [7, 5], + [3, 4], + [5, 3], + [2, 1], + [3, 2], + [2, 7], + [6, 2], + [3, 6], + [7, 3]]], + ['edgelist', + 'G1098', + 7, + [[1, 3], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1099', + 7, + [[4, 1], + [3, 4], + [5, 3], + [1, 5], + [6, 4], + [6, 3], + [6, 5], + [2, 4], + [2, 1], + [5, 2], + [7, 1], + [4, 7], + [2, 7]]], + ['edgelist', + 'G1100', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [7, 1], + [2, 7], + [7, 4], + [5, 7], + [2, 3], + [6, 1]]], + ['edgelist', + 'G1101', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1102', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6]]], + ['edgelist', + 'G1103', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [6, 4], + [6, 5], + [7, 5], + [7, 3], + [7, 6], + [6, 3], + [4, 7]]], + ['edgelist', + 'G1104', + 7, + [[1, 2], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1105', + 7, + [[1, 2], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1106', + 7, + [[1, 2], + [3, 1], + [3, 4], + [4, 5], + [1, 5], + [1, 6], + [7, 2], + [5, 7], + [7, 6], + [3, 7], + [4, 2], + [6, 4], + [3, 2]]], + ['edgelist', + 'G1107', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [2, 4], + [3, 1], + [5, 1], + [6, 4]]], + ['edgelist', + 'G1108', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [3, 4], + [1, 5], + [2, 7]]], + ['edgelist', + 'G1109', + 7, + [[1, 7], + [2, 4], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1110', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1111', + 7, + [[1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1112', + 7, + [[1, 4], + [2, 3], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1113', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [4, 7], + [6, 4], + [5, 6], + [7, 5]]], + ['edgelist', + 'G1114', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [7, 3], + [2, 7], + [6, 2], + [1, 6]]], + ['edgelist', + 'G1115', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [6, 3], + [4, 6], + [7, 5], + [1, 7]]], + ['edgelist', + 'G1116', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [3, 5], + [6, 2], + [1, 4], + [2, 5], + [1, 2], + [1, 5], + [7, 5], + [1, 7]]], + ['edgelist', + 'G1117', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [4, 5], + [4, 6], + [5, 7]]], + ['edgelist', + 'G1118', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 7], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1119', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1120', + 7, + [[4, 5], + [2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [1, 2], + [6, 2], + [1, 5], + [2, 5], + [6, 4], + [3, 6], + [7, 5], + [1, 7]]], + ['edgelist', + 'G1121', + 7, + [[2, 4], + [3, 2], + [1, 3], + [6, 1], + [5, 6], + [4, 5], + [6, 4], + [3, 6], + [2, 1], + [5, 2], + [7, 2], + [6, 2], + [3, 7], + [1, 5]]], + ['edgelist', + 'G1122', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 7], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1123', + 7, + [[3, 4], + [5, 3], + [7, 4], + [5, 1], + [7, 1], + [4, 5], + [4, 2], + [6, 5], + [6, 1], + [1, 4], + [2, 6], + [6, 4], + [7, 5], + [7, 2]]], + ['edgelist', + 'G1124', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [6, 4], + [7, 6], + [5, 7], + [6, 5]]], + ['edgelist', + 'G1125', + 7, + [[4, 2], + [2, 5], + [3, 4], + [4, 5], + [1, 5], + [2, 6], + [1, 2], + [1, 3], + [3, 6], + [6, 4], + [5, 6], + [2, 3], + [7, 3], + [6, 7]]], + ['edgelist', + 'G1126', + 7, + [[1, 4], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1127', + 7, + [[1, 4], + [1, 7], + [2, 3], + [2, 5], + [2, 6], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1128', + 7, + [[1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1129', + 7, + [[1, 2], + [1, 7], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1130', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1131', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [5, 7], + [6, 3], + [2, 6], + [1, 6], + [7, 4]]], + ['edgelist', + 'G1132', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5], + [5, 3], + [4, 1], + [7, 2], + [6, 7]]], + ['edgelist', + 'G1133', + 7, + [[1, 5], + [1, 7], + [2, 3], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1134', + 7, + [[1, 5], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1135', + 7, + [[1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1136', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [2, 3], + [6, 3], + [5, 1], + [4, 2], + [6, 1], + [7, 6], + [7, 5], + [1, 2]]], + ['edgelist', + 'G1137', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [6, 3], + [7, 1], + [7, 2], + [6, 1], + [2, 3], + [4, 2], + [5, 1]]], + ['edgelist', + 'G1138', + 7, + [[6, 7], + [1, 6], + [7, 1], + [5, 7], + [6, 5], + [2, 6], + [7, 2], + [4, 7], + [6, 4], + [3, 6], + [7, 3], + [2, 1], + [3, 2], + [4, 5]]], + ['edgelist', + 'G1139', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 6], + [2, 7], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1140', + 7, + [[1, 2], + [3, 1], + [3, 4], + [4, 5], + [1, 5], + [1, 6], + [7, 2], + [5, 7], + [7, 6], + [3, 7], + [4, 2], + [6, 4], + [7, 1], + [4, 7]]], + ['edgelist', + 'G1141', + 7, + [[4, 2], + [5, 3], + [5, 6], + [5, 1], + [2, 5], + [1, 4], + [6, 1], + [6, 3], + [7, 2], + [4, 7], + [7, 1], + [6, 7], + [7, 3], + [5, 7]]], + ['edgelist', + 'G1142', + 7, + [[1, 5], + [4, 1], + [3, 4], + [5, 3], + [2, 5], + [4, 2], + [2, 1], + [3, 2], + [6, 5], + [2, 6], + [7, 2], + [4, 7], + [1, 6], + [7, 1]]], + ['edgelist', + 'G1143', + 7, + [[4, 5], + [5, 3], + [2, 6], + [5, 1], + [2, 5], + [6, 4], + [4, 1], + [6, 3], + [7, 5], + [1, 7], + [4, 7], + [3, 7], + [6, 7], + [2, 7]]], + ['edgelist', + 'G1144', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 5], + [2, 6], + [3, 4], + [3, 7], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1145', + 7, + [[3, 4], + [5, 3], + [7, 4], + [5, 1], + [5, 6], + [4, 5], + [4, 2], + [6, 3], + [2, 7], + [6, 7], + [7, 1], + [6, 4], + [7, 5], + [1, 2]]], + ['edgelist', + 'G1146', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1147', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1148', + 7, + [[3, 4], + [5, 3], + [7, 4], + [5, 1], + [2, 5], + [7, 1], + [4, 2], + [6, 3], + [5, 6], + [6, 7], + [2, 6], + [6, 4], + [7, 5], + [4, 1]]], + ['edgelist', + 'G1149', + 7, + [[4, 2], + [5, 3], + [1, 4], + [5, 1], + [2, 5], + [6, 4], + [6, 1], + [6, 3], + [7, 5], + [2, 7], + [7, 4], + [1, 7], + [7, 6], + [3, 7]]], + ['edgelist', + 'G1150', + 7, + [[1, 2], + [5, 3], + [4, 1], + [5, 1], + [5, 6], + [6, 4], + [2, 4], + [6, 3], + [7, 5], + [3, 7], + [7, 6], + [4, 7], + [7, 2], + [1, 7]]], + ['edgelist', + 'G1151', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 7], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1152', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1153', + 7, + [[3, 4], + [5, 3], + [7, 4], + [5, 1], + [2, 5], + [7, 2], + [4, 2], + [6, 3], + [6, 1], + [6, 7], + [5, 6], + [6, 4], + [7, 1], + [4, 1]]], + ['edgelist', + 'G1154', + 7, + [[3, 4], + [5, 3], + [4, 1], + [5, 1], + [5, 6], + [4, 5], + [4, 2], + [6, 3], + [1, 2], + [6, 7], + [7, 1], + [6, 4], + [7, 5], + [2, 7]]], + ['edgelist', + 'G1155', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1156', + 7, + [[1, 4], + [1, 5], + [1, 7], + [2, 3], + [2, 5], + [2, 6], + [3, 4], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1157', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1158', + 7, + [[1, 2], + [1, 6], + [1, 7], + [2, 5], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1159', + 7, + [[1, 2], + [1, 5], + [1, 7], + [2, 4], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1160', + 7, + [[3, 4], + [5, 3], + [7, 4], + [5, 1], + [2, 5], + [5, 6], + [4, 2], + [6, 3], + [6, 1], + [7, 2], + [1, 7], + [6, 4], + [7, 5], + [4, 1]]], + ['edgelist', + 'G1161', + 7, + [[1, 2], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1162', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1163', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [6, 7]]], + ['edgelist', + 'G1164', + 7, + [[3, 4], + [5, 3], + [7, 4], + [5, 1], + [5, 6], + [4, 6], + [4, 2], + [6, 3], + [4, 1], + [2, 5], + [7, 1], + [2, 7], + [7, 5], + [1, 2]]], + ['edgelist', + 'G1165', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G1166', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1167', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 7], + [4, 5], + [4, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1168', + 7, + [[1, 4], + [1, 5], + [1, 6], + [2, 3], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1169', + 7, + [[1, 4], + [1, 5], + [1, 6], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 7], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1170', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [1, 7], + [1, 3], + [6, 1], + [4, 6], + [2, 4], + [7, 2], + [5, 7], + [3, 5]]], + ['edgelist', + 'G1171', + 7, + [[1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 7], + [5, 6]]], + ['edgelist', + 'G1172', + 7, + [[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]]], + ['edgelist', + 'G1173', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [2, 4], + [3, 1], + [5, 1], + [6, 4], + [2, 7]]], + ['edgelist', + 'G1174', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [3, 5], + [6, 3], + [2, 6], + [2, 5], + [2, 4], + [3, 1], + [5, 1], + [6, 4], + [1, 7]]], + ['edgelist', + 'G1175', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [4, 5], + [4, 6], + [4, 7], + [6, 7]]], + ['edgelist', + 'G1176', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 6], + [2, 7], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1177', + 7, + [[4, 5], + [5, 6], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [4, 7], + [5, 7], + [6, 7], + [2, 6], + [4, 6], + [3, 4], + [3, 5], + [2, 7], + [1, 2]]], + ['edgelist', + 'G1178', + 7, + [[4, 5], + [5, 6], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [4, 7], + [2, 4], + [2, 5], + [2, 6], + [4, 6], + [3, 4], + [3, 5], + [7, 2], + [5, 7]]], + ['edgelist', + 'G1179', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 2], + [5, 3], + [5, 4], + [6, 2], + [1, 6], + [6, 3], + [4, 6], + [5, 6], + [7, 2], + [6, 7]]], + ['edgelist', + 'G1180', + 7, + [[5, 4], + [5, 6], + [6, 4], + [1, 2], + [1, 6], + [1, 4], + [3, 5], + [2, 6], + [2, 4], + [7, 6], + [4, 7], + [7, 1], + [2, 7], + [7, 3], + [5, 7]]], + ['edgelist', + 'G1181', + 7, + [[4, 5], + [5, 6], + [6, 7], + [1, 5], + [1, 6], + [1, 7], + [4, 7], + [2, 4], + [5, 7], + [2, 6], + [4, 6], + [3, 4], + [3, 5], + [2, 7], + [1, 2]]], + ['edgelist', + 'G1182', + 7, + [[1, 3], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1183', + 7, + [[7, 2], + [5, 6], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [4, 7], + [2, 4], + [2, 5], + [2, 6], + [4, 6], + [3, 4], + [3, 5], + [6, 7], + [5, 7]]], + ['edgelist', + 'G1184', + 7, + [[4, 5], + [5, 6], + [1, 4], + [1, 5], + [1, 6], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [2, 6], + [4, 6], + [3, 4], + [3, 5], + [6, 7], + [6, 3]]], + ['edgelist', + 'G1185', + 7, + [[4, 5], + [5, 6], + [1, 4], + [1, 5], + [7, 1], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [2, 6], + [4, 6], + [3, 4], + [3, 5], + [6, 7], + [6, 3]]], + ['edgelist', + 'G1186', + 7, + [[1, 2], + [2, 3], + [1, 3], + [4, 1], + [2, 4], + [3, 4], + [6, 2], + [4, 6], + [5, 4], + [3, 5], + [7, 3], + [4, 7], + [7, 2], + [1, 6], + [5, 1]]], + ['edgelist', + 'G1187', + 7, + [[1, 2], + [3, 1], + [4, 3], + [5, 4], + [2, 5], + [4, 2], + [5, 3], + [1, 5], + [4, 1], + [7, 4], + [5, 7], + [6, 5], + [4, 6], + [7, 3], + [6, 2]]], + ['edgelist', + 'G1188', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [2, 3], + [2, 4], + [2, 5], + [3, 4], + [3, 5], + [4, 5], + [6, 4], + [5, 6], + [7, 5], + [6, 7], + [7, 4]]], + ['edgelist', + 'G1189', + 7, + [[1, 2], + [2, 3], + [3, 4], + [1, 4], + [5, 1], + [5, 4], + [5, 3], + [7, 2], + [3, 7], + [6, 3], + [5, 6], + [7, 5], + [1, 7], + [7, 6], + [4, 7]]], + ['edgelist', + 'G1190', + 7, + [[1, 2], + [6, 4], + [2, 4], + [1, 5], + [4, 1], + [5, 4], + [3, 5], + [6, 3], + [5, 6], + [7, 5], + [3, 7], + [7, 6], + [4, 7], + [7, 2], + [1, 7]]], + ['edgelist', + 'G1191', + 7, + [[6, 3], + [5, 6], + [4, 2], + [1, 5], + [1, 6], + [1, 4], + [3, 5], + [2, 6], + [2, 5], + [7, 4], + [2, 7], + [7, 1], + [6, 7], + [7, 3], + [5, 7]]], + ['edgelist', + 'G1192', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [1, 6], + [1, 4], + [3, 1], + [4, 2], + [7, 5], + [6, 7], + [7, 4], + [1, 7], + [7, 3], + [2, 7]]], + ['edgelist', + 'G1193', + 7, + [[6, 3], + [4, 1], + [6, 4], + [1, 5], + [1, 6], + [5, 4], + [3, 5], + [2, 6], + [2, 5], + [7, 5], + [3, 7], + [7, 1], + [6, 7], + [7, 4], + [2, 7]]], + ['edgelist', + 'G1194', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1195', + 7, + [[7, 2], + [5, 6], + [1, 4], + [1, 5], + [1, 6], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [1, 7], + [4, 6], + [3, 4], + [3, 5], + [6, 7], + [6, 3]]], + ['edgelist', + 'G1196', + 7, + [[4, 5], + [1, 2], + [1, 4], + [1, 5], + [1, 6], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [2, 7], + [3, 4], + [3, 5], + [6, 7], + [6, 3]]], + ['edgelist', + 'G1197', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 7]]], + ['edgelist', + 'G1198', + 7, + [[6, 3], + [5, 6], + [6, 4], + [1, 5], + [1, 2], + [2, 4], + [3, 5], + [4, 1], + [2, 5], + [7, 5], + [2, 7], + [7, 1], + [4, 7], + [7, 3], + [6, 7]]], + ['edgelist', + 'G1199', + 7, + [[6, 1], + [5, 4], + [6, 4], + [6, 3], + [1, 2], + [2, 4], + [3, 5], + [4, 1], + [2, 5], + [7, 3], + [6, 7], + [7, 5], + [4, 7], + [7, 1], + [2, 7]]], + ['edgelist', + 'G1200', + 7, + [[4, 5], + [5, 6], + [1, 4], + [5, 7], + [1, 2], + [2, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [1, 6], + [3, 4], + [3, 5], + [6, 7], + [6, 3]]], + ['edgelist', + 'G1201', + 7, + [[1, 3], + [1, 4], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1202', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1203', + 7, + [[4, 5], + [6, 1], + [1, 4], + [1, 5], + [5, 7], + [2, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [2, 6], + [3, 4], + [3, 5], + [6, 7], + [6, 3]]], + ['edgelist', + 'G1204', + 7, + [[7, 5], + [6, 3], + [1, 4], + [1, 5], + [3, 5], + [2, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [4, 6], + [3, 4], + [1, 2], + [6, 7], + [5, 6]]], + ['edgelist', + 'G1205', + 7, + [[1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1206', + 7, + [[1, 2], + [1, 3], + [1, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1207', + 7, + [[3, 4], + [1, 3], + [4, 1], + [5, 4], + [2, 5], + [6, 2], + [5, 6], + [2, 1], + [6, 3], + [7, 3], + [6, 7], + [7, 2], + [1, 7], + [7, 5], + [4, 7]]], + ['edgelist', + 'G1208', + 7, + [[4, 1], + [4, 6], + [4, 5], + [3, 1], + [3, 6], + [3, 5], + [2, 5], + [2, 6], + [2, 1], + [7, 1], + [2, 7], + [7, 6], + [4, 7], + [7, 5], + [3, 7]]], + ['edgelist', + 'G1209', + 7, + [[1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1210', + 7, + [[4, 5], + [7, 3], + [1, 4], + [1, 5], + [6, 1], + [2, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [5, 6], + [3, 4], + [3, 5], + [6, 2], + [6, 3]]], + ['edgelist', + 'G1211', + 7, + [[4, 5], + [7, 3], + [1, 4], + [1, 5], + [6, 1], + [6, 7], + [4, 7], + [2, 4], + [2, 5], + [1, 2], + [5, 7], + [3, 4], + [3, 5], + [6, 2], + [6, 3]]], + ['edgelist', + 'G1212', + 7, + [[1, 2], + [2, 3], + [3, 4], + [4, 5], + [1, 5], + [7, 3], + [2, 7], + [7, 1], + [5, 7], + [6, 5], + [1, 6], + [4, 6], + [7, 4], + [6, 3], + [2, 6]]], + ['edgelist', + 'G1213', + 7, + [[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], + [3, 7]]], + ['edgelist', + 'G1214', + 7, + [[4, 1], + [5, 2], + [5, 4], + [2, 4], + [5, 1], + [3, 6], + [7, 3], + [6, 7], + [2, 6], + [5, 6], + [4, 6], + [1, 6], + [1, 7], + [4, 7], + [5, 7], + [7, 2]]], + ['edgelist', + 'G1215', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1216', + 7, + [[1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1217', + 7, + [[4, 5], + [6, 2], + [1, 4], + [1, 5], + [6, 1], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [4, 6], + [3, 4], + [3, 5], + [6, 7], + [5, 6], + [3, 6]]], + ['edgelist', + 'G1218', + 7, + [[3, 5], + [4, 2], + [4, 1], + [5, 4], + [5, 1], + [6, 3], + [5, 6], + [6, 1], + [4, 6], + [6, 2], + [7, 6], + [2, 7], + [4, 7], + [7, 1], + [5, 7], + [7, 3]]], + ['edgelist', + 'G1219', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1220', + 7, + [[3, 5], + [5, 2], + [4, 1], + [4, 2], + [5, 1], + [6, 3], + [5, 6], + [6, 1], + [4, 6], + [7, 6], + [2, 6], + [7, 2], + [4, 7], + [5, 7], + [7, 3], + [7, 1]]], + ['edgelist', + 'G1221', + 7, + [[1, 2], + [1, 4], + [1, 6], + [1, 7], + [2, 4], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1222', + 7, + [[3, 6], + [1, 2], + [5, 6], + [2, 4], + [6, 1], + [5, 4], + [6, 4], + [3, 5], + [2, 5], + [4, 1], + [7, 4], + [3, 7], + [7, 5], + [6, 7], + [7, 2], + [1, 7]]], + ['edgelist', + 'G1223', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 4], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1224', + 7, + [[3, 6], + [6, 2], + [4, 2], + [1, 5], + [6, 1], + [5, 4], + [6, 4], + [3, 5], + [2, 5], + [4, 1], + [7, 3], + [5, 7], + [6, 7], + [7, 2], + [1, 7], + [4, 7]]], + ['edgelist', + 'G1225', + 7, + [[2, 7], + [1, 2], + [1, 4], + [1, 5], + [6, 1], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [4, 6], + [3, 4], + [3, 5], + [6, 7], + [5, 6], + [3, 6]]], + ['edgelist', + 'G1226', + 7, + [[4, 5], + [6, 2], + [1, 4], + [1, 5], + [6, 1], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [2, 7], + [3, 4], + [3, 5], + [6, 7], + [1, 2], + [3, 6]]], + ['edgelist', + 'G1227', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6]]], + ['edgelist', + 'G1228', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [6, 7]]], + ['edgelist', + 'G1229', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 5], + [2, 6], + [2, 7], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [6, 7]]], + ['edgelist', + 'G1230', + 7, + [[3, 6], + [6, 2], + [4, 6], + [1, 5], + [1, 2], + [5, 4], + [4, 3], + [3, 5], + [2, 5], + [1, 6], + [7, 5], + [3, 7], + [7, 4], + [6, 7], + [7, 2], + [1, 7]]], + ['edgelist', + 'G1231', + 7, + [[6, 7], + [6, 2], + [1, 4], + [1, 5], + [1, 2], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [4, 6], + [3, 4], + [3, 5], + [7, 3], + [5, 6], + [3, 6]]], + ['edgelist', + 'G1232', + 7, + [[4, 5], + [6, 2], + [1, 4], + [1, 5], + [1, 2], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [6, 1], + [3, 4], + [3, 5], + [7, 3], + [7, 6], + [3, 6]]], + ['edgelist', + 'G1233', + 7, + [[6, 1], + [6, 2], + [1, 4], + [1, 5], + [7, 2], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [7, 1], + [4, 6], + [3, 4], + [3, 5], + [7, 3], + [5, 6], + [3, 6]]], + ['edgelist', + 'G1234', + 7, + [[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, 3], + [2, 7]]], + ['edgelist', + 'G1235', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1236', + 7, + [[5, 1], + [5, 4], + [1, 2], + [4, 1], + [3, 5], + [4, 2], + [6, 4], + [5, 6], + [6, 3], + [7, 6], + [6, 1], + [2, 6], + [7, 2], + [1, 7], + [7, 5], + [3, 7], + [4, 7]]], + ['edgelist', + 'G1237', + 7, + [[1, 2], + [6, 2], + [6, 4], + [1, 5], + [6, 1], + [5, 4], + [4, 2], + [3, 6], + [2, 5], + [4, 1], + [3, 5], + [7, 3], + [6, 7], + [7, 4], + [2, 7], + [7, 1], + [5, 7]]], + ['edgelist', + 'G1238', + 7, + [[4, 5], + [6, 2], + [1, 4], + [1, 5], + [5, 6], + [5, 7], + [4, 7], + [2, 4], + [2, 5], + [1, 2], + [4, 6], + [3, 4], + [3, 5], + [3, 6], + [6, 1], + [6, 7], + [7, 3]]], + ['edgelist', + 'G1239', + 7, + [[4, 3], + [5, 2], + [1, 2], + [4, 1], + [3, 5], + [5, 4], + [6, 2], + [5, 6], + [6, 3], + [1, 6], + [6, 4], + [7, 6], + [3, 7], + [7, 4], + [1, 7], + [2, 7], + [5, 7]]], + ['edgelist', + 'G1240', + 7, + [[4, 3], + [5, 2], + [5, 1], + [4, 1], + [3, 5], + [4, 2], + [6, 3], + [5, 6], + [6, 1], + [4, 6], + [6, 2], + [7, 6], + [3, 7], + [7, 1], + [4, 7], + [7, 5], + [2, 7]]], + ['edgelist', + 'G1241', + 7, + [[4, 3], + [6, 2], + [6, 1], + [1, 5], + [1, 2], + [5, 4], + [6, 4], + [3, 6], + [2, 5], + [4, 1], + [3, 5], + [7, 5], + [6, 7], + [7, 3], + [4, 7], + [7, 1], + [2, 7]]], + ['edgelist', + 'G1242', + 7, + [[4, 3], + [6, 2], + [6, 1], + [1, 5], + [5, 6], + [1, 2], + [4, 2], + [3, 6], + [2, 5], + [4, 1], + [3, 5], + [7, 1], + [4, 7], + [7, 2], + [6, 7], + [7, 3], + [5, 7]]], + ['edgelist', + 'G1243', + 7, + [[1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 6], + [4, 7], + [5, 6], + [5, 7]]], + ['edgelist', + 'G1244', + 7, + [[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, 2], + [1, 7], + [6, 7]]], + ['edgelist', + 'G1245', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7]]], + ['edgelist', + 'G1246', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 5], + [2, 6], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1247', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1248', + 7, + [[5, 1], + [5, 6], + [4, 1], + [4, 6], + [3, 1], + [3, 6], + [2, 4], + [2, 5], + [2, 6], + [2, 1], + [3, 4], + [3, 5], + [7, 1], + [6, 7], + [7, 2], + [3, 7], + [7, 5], + [4, 7]]], + ['edgelist', + 'G1249', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1250', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1251', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]], + ['edgelist', + 'G1252', + 7, + [[1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 5], + [4, 6], + [4, 7], + [5, 6], + [5, 7], + [6, 7]]]] + + GAG=[] + + for i in range(1253): + g=make_small_graph(descr_list[i]) + GAG.append(g) + + return GAG + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/bipartite.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/bipartite.py new file mode 100644 index 0000000..435ed0d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/bipartite.py @@ -0,0 +1,529 @@ +# -*- coding: utf-8 -*- +""" +Generators and functions for bipartite graphs. + +""" +# Copyright (C) 2006-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import math +import random +import networkx +from functools import reduce +import networkx as nx +__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) +__all__=['bipartite_configuration_model', + 'bipartite_havel_hakimi_graph', + 'bipartite_reverse_havel_hakimi_graph', + 'bipartite_alternating_havel_hakimi_graph', + 'bipartite_preferential_attachment_graph', + 'bipartite_random_graph', + 'bipartite_gnmk_random_graph', + ] + + +def bipartite_configuration_model(aseq, bseq, create_using=None, seed=None): + """Return a random bipartite graph from two given degree sequences. + + Parameters + ---------- + aseq : list or iterator + Degree sequence for node set A. + bseq : list or iterator + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + seed : integer, optional + Seed for random number generator. + + Nodes from the set A are connected to nodes in the set B by + choosing randomly from the possible free stubs, one in A and + one in B. + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + """ + if create_using is None: + create_using=networkx.MultiGraph() + elif create_using.is_directed(): + raise networkx.NetworkXError(\ + "Directed Graph not supported") + + + G=networkx.empty_graph(0,create_using) + + if not seed is None: + random.seed(seed) + + # length and sum of each sequence + lena=len(aseq) + lenb=len(bseq) + suma=sum(aseq) + sumb=sum(bseq) + + if not suma==sumb: + raise networkx.NetworkXError(\ + 'invalid degree sequences, sum(aseq)!=sum(bseq),%s,%s'\ + %(suma,sumb)) + + G=_add_nodes_with_bipartite_label(G,lena,lenb) + + if max(aseq)==0: return G # done if no edges + + # build lists of degree-repeated vertex numbers + stubs=[] + stubs.extend([[v]*aseq[v] for v in range(0,lena)]) + astubs=[] + astubs=[x for subseq in stubs for x in subseq] + + stubs=[] + stubs.extend([[v]*bseq[v-lena] for v in range(lena,lena+lenb)]) + bstubs=[] + bstubs=[x for subseq in stubs for x in subseq] + + # shuffle lists + random.shuffle(astubs) + random.shuffle(bstubs) + + G.add_edges_from([[astubs[i],bstubs[i]] for i in range(suma)]) + + G.name="bipartite_configuration_model" + return G + + +def bipartite_havel_hakimi_graph(aseq, bseq, create_using=None): + """Return a bipartite graph from two given degree sequences using a + Havel-Hakimi style construction. + + Nodes from the set A are connected to nodes in the set B by + connecting the highest degree nodes in set A to the highest degree + nodes in set B until all stubs are connected. + + Parameters + ---------- + aseq : list or iterator + Degree sequence for node set A. + bseq : list or iterator + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + """ + if create_using is None: + create_using=networkx.MultiGraph() + elif create_using.is_directed(): + raise networkx.NetworkXError(\ + "Directed Graph not supported") + + G=networkx.empty_graph(0,create_using) + + # length of the each sequence + naseq=len(aseq) + nbseq=len(bseq) + + suma=sum(aseq) + sumb=sum(bseq) + + if not suma==sumb: + raise networkx.NetworkXError(\ + 'invalid degree sequences, sum(aseq)!=sum(bseq),%s,%s'\ + %(suma,sumb)) + + G=_add_nodes_with_bipartite_label(G,naseq,nbseq) + + if max(aseq)==0: return G # done if no edges + + # build list of degree-repeated vertex numbers + astubs=[[aseq[v],v] for v in range(0,naseq)] + bstubs=[[bseq[v-naseq],v] for v in range(naseq,naseq+nbseq)] + astubs.sort() + while astubs: + (degree,u)=astubs.pop() # take of largest degree node in the a set + if degree==0: break # done, all are zero + # connect the source to largest degree nodes in the b set + bstubs.sort() + for target in bstubs[-degree:]: + v=target[1] + G.add_edge(u,v) + target[0] -= 1 # note this updates bstubs too. + if target[0]==0: + bstubs.remove(target) + + G.name="bipartite_havel_hakimi_graph" + return G + +def bipartite_reverse_havel_hakimi_graph(aseq, bseq, create_using=None): + """Return a bipartite graph from two given degree sequences using a + Havel-Hakimi style construction. + + Nodes from set A are connected to nodes in the set B by connecting + the highest degree nodes in set A to the lowest degree nodes in + set B until all stubs are connected. + + Parameters + ---------- + aseq : list or iterator + Degree sequence for node set A. + bseq : list or iterator + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + """ + if create_using is None: + create_using=networkx.MultiGraph() + elif create_using.is_directed(): + raise networkx.NetworkXError(\ + "Directed Graph not supported") + + G=networkx.empty_graph(0,create_using) + + + # length of the each sequence + lena=len(aseq) + lenb=len(bseq) + suma=sum(aseq) + sumb=sum(bseq) + + if not suma==sumb: + raise networkx.NetworkXError(\ + 'invalid degree sequences, sum(aseq)!=sum(bseq),%s,%s'\ + %(suma,sumb)) + + G=_add_nodes_with_bipartite_label(G,lena,lenb) + + if max(aseq)==0: return G # done if no edges + + # build list of degree-repeated vertex numbers + astubs=[[aseq[v],v] for v in range(0,lena)] + bstubs=[[bseq[v-lena],v] for v in range(lena,lena+lenb)] + astubs.sort() + bstubs.sort() + while astubs: + (degree,u)=astubs.pop() # take of largest degree node in the a set + if degree==0: break # done, all are zero + # connect the source to the smallest degree nodes in the b set + for target in bstubs[0:degree]: + v=target[1] + G.add_edge(u,v) + target[0] -= 1 # note this updates bstubs too. + if target[0]==0: + bstubs.remove(target) + + G.name="bipartite_reverse_havel_hakimi_graph" + return G + + +def bipartite_alternating_havel_hakimi_graph(aseq, bseq,create_using=None): + """Return a bipartite graph from two given degree sequences using + an alternating Havel-Hakimi style construction. + + Nodes from the set A are connected to nodes in the set B by + connecting the highest degree nodes in set A to alternatively the + highest and the lowest degree nodes in set B until all stubs are + connected. + + Parameters + ---------- + aseq : list or iterator + Degree sequence for node set A. + bseq : list or iterator + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + """ + if create_using is None: + create_using=networkx.MultiGraph() + elif create_using.is_directed(): + raise networkx.NetworkXError(\ + "Directed Graph not supported") + + G=networkx.empty_graph(0,create_using) + + # length of the each sequence + naseq=len(aseq) + nbseq=len(bseq) + suma=sum(aseq) + sumb=sum(bseq) + + if not suma==sumb: + raise networkx.NetworkXError(\ + 'invalid degree sequences, sum(aseq)!=sum(bseq),%s,%s'\ + %(suma,sumb)) + + G=_add_nodes_with_bipartite_label(G,naseq,nbseq) + + if max(aseq)==0: return G # done if no edges + # build list of degree-repeated vertex numbers + astubs=[[aseq[v],v] for v in range(0,naseq)] + bstubs=[[bseq[v-naseq],v] for v in range(naseq,naseq+nbseq)] + while astubs: + astubs.sort() + (degree,u)=astubs.pop() # take of largest degree node in the a set + if degree==0: break # done, all are zero + bstubs.sort() + small=bstubs[0:degree // 2] # add these low degree targets + large=bstubs[(-degree+degree // 2):] # and these high degree targets + stubs=[x for z in zip(large,small) for x in z] # combine, sorry + if len(stubs) 1: + raise networkx.NetworkXError("probability %s > 1"%(p)) + + G=networkx.empty_graph(0,create_using) + + if not seed is None: + random.seed(seed) + + naseq=len(aseq) + G=_add_nodes_with_bipartite_label(G,naseq,0) + vv=[ [v]*aseq[v] for v in range(0,naseq)] + while vv: + while vv[0]: + source=vv[0][0] + vv[0].remove(source) + if random.random() < p or G.number_of_nodes() == naseq: + target=G.number_of_nodes() + G.add_node(target,bipartite=1) + G.add_edge(source,target) + else: + bb=[ [b]*G.degree(b) for b in range(naseq,G.number_of_nodes())] + # flatten the list of lists into a list. + bbstubs=reduce(lambda x,y: x+y, bb) + # choose preferentially a bottom node. + target=random.choice(bbstubs) + G.add_node(target,bipartite=1) + G.add_edge(source,target) + vv.remove(vv[0]) + G.name="bipartite_preferential_attachment_model" + return G + + + +def bipartite_random_graph(n, m, p, seed=None, directed=False): + """Return a bipartite random graph. + + This is a bipartite version of the binomial (Erdős-Rényi) graph. + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set. + m : int + The number of nodes in the second bipartite set. + p : float + Probability for edge creation. + seed : int, optional + Seed for random number generator (default=None). + directed : bool, optional (default=False) + If True return a directed graph + + Notes + ----- + The bipartite random graph algorithm chooses each of the n*m (undirected) + or 2*nm (directed) possible edges with probability p. + + This algorithm is O(n+m) where m is the expected number of edges. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + See Also + -------- + gnp_random_graph, bipartite_configuration_model + + References + ---------- + .. [1] Vladimir Batagelj and Ulrik Brandes, + "Efficient generation of large random networks", + Phys. Rev. E, 71, 036113, 2005. + """ + G=nx.Graph() + G=_add_nodes_with_bipartite_label(G,n,m) + if directed: + G=nx.DiGraph(G) + G.name="fast_gnp_random_graph(%s,%s,%s)"%(n,m,p) + + if not seed is None: + random.seed(seed) + + if p <= 0: + return G + if p >= 1: + return nx.complete_bipartite_graph(n,m) + + lp = math.log(1.0 - p) + + v = 0 + w = -1 + while v < n: + lr = math.log(1.0 - random.random()) + w = w + 1 + int(lr/lp) + while w >= m and v < n: + w = w - m + v = v + 1 + if v < n: + G.add_edge(v, n+w) + + if directed: + # use the same algorithm to + # add edges from the "m" to "n" set + v = 0 + w = -1 + while v < n: + lr = math.log(1.0 - random.random()) + w = w + 1 + int(lr/lp) + while w>= m and v < n: + w = w - m + v = v + 1 + if v < n: + G.add_edge(n+w, v) + + return G + +def bipartite_gnmk_random_graph(n, m, k, seed=None, directed=False): + """Return a random bipartite graph G_{n,m,k}. + + Produces a bipartite graph chosen randomly out of the set of all graphs + with n top nodes, m bottom nodes, and k edges. + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set. + m : int + The number of nodes in the second bipartite set. + k : int + The number of edges + seed : int, optional + Seed for random number generator (default=None). + directed : bool, optional (default=False) + If True return a directed graph + + Examples + -------- + G = nx.bipartite_gnmk_random_graph(10,20,50) + + See Also + -------- + gnm_random_graph + + Notes + ----- + If k > m * n then a complete bipartite graph is returned. + + This graph is a bipartite version of the `G_{nm}` random graph model. + """ + G = networkx.Graph() + G=_add_nodes_with_bipartite_label(G,n,m) + if directed: + G=nx.DiGraph(G) + G.name="bipartite_gnm_random_graph(%s,%s,%s)"%(n,m,k) + if seed is not None: + random.seed(seed) + if n == 1 or m == 1: + return G + max_edges = n*m # max_edges for bipartite networks + if k >= max_edges: # Maybe we should raise an exception here + return networkx.complete_bipartite_graph(n, m, create_using=G) + + top = [n for n,d in G.nodes(data=True) if d['bipartite']==0] + bottom = list(set(G) - set(top)) + edge_count = 0 + while edge_count < k: + # generate random edge,u,v + u = random.choice(top) + v = random.choice(bottom) + if v in G[u]: + continue + else: + G.add_edge(u,v) + edge_count += 1 + return G + +def _add_nodes_with_bipartite_label(G, lena, lenb): + G.add_nodes_from(range(0,lena+lenb)) + b=dict(zip(range(0,lena),[0]*lena)) + b.update(dict(zip(range(lena,lena+lenb),[1]*lenb))) + nx.set_node_attributes(G,'bipartite',b) + return G diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/classic.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/classic.py new file mode 100644 index 0000000..f8ca43b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/classic.py @@ -0,0 +1,508 @@ +""" +Generators for some classic graphs. + +The typical graph generator is called as follows: + +>>> G=nx.complete_graph(100) + +returning the complete graph on n nodes labeled 0,..,99 +as a simple graph. Except for empty_graph, all the generators +in this module return a Graph class (i.e. a simple, undirected graph). + +""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import itertools +__author__ ="""Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)""" + +__all__ = [ 'balanced_tree', + 'barbell_graph', + 'complete_graph', + 'complete_bipartite_graph', + 'circular_ladder_graph', + 'cycle_graph', + 'dorogovtsev_goltsev_mendes_graph', + 'empty_graph', + 'full_rary_tree', + 'grid_graph', + 'grid_2d_graph', + 'hypercube_graph', + 'ladder_graph', + 'lollipop_graph', + 'null_graph', + 'path_graph', + 'star_graph', + 'trivial_graph', + 'wheel_graph'] + + +#------------------------------------------------------------------- +# Some Classic Graphs +#------------------------------------------------------------------- +import networkx as nx +from networkx.utils import is_list_of_ints, flatten + +def _tree_edges(n,r): + # helper function for trees + # yields edges in rooted tree at 0 with n nodes and branching ratio r + nodes=iter(range(n)) + parents=[next(nodes)] # stack of max length r + while parents: + source=parents.pop(0) + for i in range(r): + try: + target=next(nodes) + parents.append(target) + yield source,target + except StopIteration: + break + +def full_rary_tree(r, n, create_using=None): + """Creates a full r-ary tree of n vertices. + + Sometimes called a k-ary, n-ary, or m-ary tree. "... all non-leaf + vertices have exactly r children and all levels are full except + for some rightmost position of the bottom level (if a leaf at the + bottom level is missing, then so are all of the leaves to its + right." [1]_ + + Parameters + ---------- + r : int + branching factor of the tree + n : int + Number of nodes in the tree + create_using : NetworkX graph type, optional + Use specified type to construct graph (default = networkx.Graph) + + Returns + ------- + G : networkx Graph + An r-ary tree with n nodes + + References + ---------- + .. [1] An introduction to data structures and algorithms, + James Andrew Storer, Birkhauser Boston 2001, (page 225). + """ + G=nx.empty_graph(n,create_using) + G.add_edges_from(_tree_edges(n,r)) + return G + +def balanced_tree(r, h, create_using=None): + """Return the perfectly balanced r-tree of height h. + + Parameters + ---------- + r : int + Branching factor of the tree + h : int + Height of the tree + create_using : NetworkX graph type, optional + Use specified type to construct graph (default = networkx.Graph) + + Returns + ------- + G : networkx Graph + A tree with n nodes + + Notes + ----- + This is the rooted tree where all leaves are at distance h from + the root. The root has degree r and all other internal nodes have + degree r+1. + + Node labels are the integers 0 (the root) up to number_of_nodes - 1. + + Also refered to as a complete r-ary tree. + """ + # number of nodes is n=1+r+..+r^h + if r==1: + n=2 + else: + n = int((1-r**(h+1))/(1-r)) # sum of geometric series r!=1 + G=nx.empty_graph(n,create_using) + G.add_edges_from(_tree_edges(n,r)) + return G + + return nx.full_rary_tree(r,n,create_using) + +def barbell_graph(m1,m2,create_using=None): + """Return the Barbell Graph: two complete graphs connected by a path. + + For m1 > 1 and m2 >= 0. + + Two identical complete graphs K_{m1} form the left and right bells, + and are connected by a path P_{m2}. + + The 2*m1+m2 nodes are numbered + 0,...,m1-1 for the left barbell, + m1,...,m1+m2-1 for the path, + and m1+m2,...,2*m1+m2-1 for the right barbell. + + The 3 subgraphs are joined via the edges (m1-1,m1) and (m1+m2-1,m1+m2). + If m2=0, this is merely two complete graphs joined together. + + This graph is an extremal example in David Aldous + and Jim Fill's etext on Random Walks on Graphs. + + """ + if create_using is not None and create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + if m1<2: + raise nx.NetworkXError(\ + "Invalid graph description, m1 should be >=2") + if m2<0: + raise nx.NetworkXError(\ + "Invalid graph description, m2 should be >=0") + + # left barbell + G=complete_graph(m1,create_using) + G.name="barbell_graph(%d,%d)"%(m1,m2) + + # connecting path + G.add_nodes_from([v for v in range(m1,m1+m2-1)]) + if m2>1: + G.add_edges_from([(v,v+1) for v in range(m1,m1+m2-1)]) + # right barbell + G.add_edges_from( (u,v) for u in range(m1+m2,2*m1+m2) for v in range(u+1,2*m1+m2)) + # connect it up + G.add_edge(m1-1,m1) + if m2>0: + G.add_edge(m1+m2-1,m1+m2) + return G + +def complete_graph(n,create_using=None): + """ Return the complete graph K_n with n nodes. + + Node labels are the integers 0 to n-1. + """ + G=empty_graph(n,create_using) + G.name="complete_graph(%d)"%(n) + if n>1: + if G.is_directed(): + edges=itertools.permutations(range(n),2) + else: + edges=itertools.combinations(range(n),2) + G.add_edges_from(edges) + return G + + +def complete_bipartite_graph(n1,n2,create_using=None): + """Return the complete bipartite graph K_{n1_n2}. + + Composed of two partitions with n1 nodes in the first + and n2 nodes in the second. Each node in the first is + connected to each node in the second. + + Node labels are the integers 0 to n1+n2-1 + + """ + if create_using is not None and create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + G=empty_graph(n1+n2,create_using) + G.name="complete_bipartite_graph(%d,%d)"%(n1,n2) + for v1 in range(n1): + for v2 in range(n2): + G.add_edge(v1,n1+v2) + return G + +def circular_ladder_graph(n,create_using=None): + """Return the circular ladder graph CL_n of length n. + + CL_n consists of two concentric n-cycles in which + each of the n pairs of concentric nodes are joined by an edge. + + Node labels are the integers 0 to n-1 + + """ + G=ladder_graph(n,create_using) + G.name="circular_ladder_graph(%d)"%n + G.add_edge(0,n-1) + G.add_edge(n,2*n-1) + return G + +def cycle_graph(n,create_using=None): + """Return the cycle graph C_n over n nodes. + + C_n is the n-path with two end-nodes connected. + + Node labels are the integers 0 to n-1 + If create_using is a DiGraph, the direction is in increasing order. + + """ + G=path_graph(n,create_using) + G.name="cycle_graph(%d)"%n + if n>1: G.add_edge(n-1,0) + return G + +def dorogovtsev_goltsev_mendes_graph(n,create_using=None): + """Return the hierarchically constructed Dorogovtsev-Goltsev-Mendes graph. + + n is the generation. + See: arXiv:/cond-mat/0112143 by Dorogovtsev, Goltsev and Mendes. + + """ + if create_using is not None: + if create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + if create_using.is_multigraph(): + raise nx.NetworkXError("Multigraph not supported") + G=empty_graph(0,create_using) + G.name="Dorogovtsev-Goltsev-Mendes Graph" + G.add_edge(0,1) + if n==0: + return G + new_node = 2 # next node to be added + for i in range(1,n+1): #iterate over number of generations. + last_generation_edges = G.edges() + number_of_edges_in_last_generation = len(last_generation_edges) + for j in range(0,number_of_edges_in_last_generation): + G.add_edge(new_node,last_generation_edges[j][0]) + G.add_edge(new_node,last_generation_edges[j][1]) + new_node += 1 + return G + +def empty_graph(n=0,create_using=None): + """Return the empty graph with n nodes and zero edges. + + Node labels are the integers 0 to n-1 + + For example: + >>> G=nx.empty_graph(10) + >>> G.number_of_nodes() + 10 + >>> G.number_of_edges() + 0 + + The variable create_using should point to a "graph"-like object that + will be cleaned (nodes and edges will be removed) and refitted as + an empty "graph" with n nodes with integer labels. This capability + is useful for specifying the class-nature of the resulting empty + "graph" (i.e. Graph, DiGraph, MyWeirdGraphClass, etc.). + + The variable create_using has two main uses: + Firstly, the variable create_using can be used to create an + empty digraph, network,etc. For example, + + >>> n=10 + >>> G=nx.empty_graph(n,create_using=nx.DiGraph()) + + will create an empty digraph on n nodes. + + Secondly, one can pass an existing graph (digraph, pseudograph, + etc.) via create_using. For example, if G is an existing graph + (resp. digraph, pseudograph, etc.), then empty_graph(n,create_using=G) + will empty G (i.e. delete all nodes and edges using G.clear() in + base) and then add n nodes and zero edges, and return the modified + graph (resp. digraph, pseudograph, etc.). + + See also create_empty_copy(G). + + """ + if create_using is None: + # default empty graph is a simple graph + G=nx.Graph() + else: + G=create_using + G.clear() + + G.add_nodes_from(range(n)) + G.name="empty_graph(%d)"%n + return G + +def grid_2d_graph(m,n,periodic=False,create_using=None): + """ Return the 2d grid graph of mxn nodes, + each connected to its nearest neighbors. + Optional argument periodic=True will connect + boundary nodes via periodic boundary conditions. + """ + G=empty_graph(0,create_using) + G.name="grid_2d_graph" + rows=range(m) + columns=range(n) + G.add_nodes_from( (i,j) for i in rows for j in columns ) + G.add_edges_from( ((i,j),(i-1,j)) for i in rows for j in columns if i>0 ) + G.add_edges_from( ((i,j),(i,j-1)) for i in rows for j in columns if j>0 ) + if G.is_directed(): + G.add_edges_from( ((i,j),(i+1,j)) for i in rows for j in columns if i2: + G.add_edges_from( ((i,0),(i,n-1)) for i in rows ) + if G.is_directed(): + G.add_edges_from( ((i,n-1),(i,0)) for i in rows ) + if m>2: + G.add_edges_from( ((0,j),(m-1,j)) for j in columns ) + if G.is_directed(): + G.add_edges_from( ((m-1,j),(0,j)) for j in columns ) + G.name="periodic_grid_2d_graph(%d,%d)"%(m,n) + return G + + +def grid_graph(dim,periodic=False): + """ Return the n-dimensional grid graph. + + The dimension is the length of the list 'dim' and the + size in each dimension is the value of the list element. + + E.g. G=grid_graph(dim=[2,3]) produces a 2x3 grid graph. + + If periodic=True then join grid edges with periodic boundary conditions. + + """ + dlabel="%s"%dim + if dim==[]: + G=empty_graph(0) + G.name="grid_graph(%s)"%dim + return G + if not is_list_of_ints(dim): + raise nx.NetworkXError("dim is not a list of integers") + if min(dim)<=0: + raise nx.NetworkXError(\ + "dim is not a list of strictly positive integers") + if periodic: + func=cycle_graph + else: + func=path_graph + + dim=list(dim) + current_dim=dim.pop() + G=func(current_dim) + while len(dim)>0: + current_dim=dim.pop() + # order matters: copy before it is cleared during the creation of Gnew + Gold=G.copy() + Gnew=func(current_dim) + # explicit: create_using=None + # This is so that we get a new graph of Gnew's class. + G=nx.cartesian_product(Gnew,Gold) + # graph G is done but has labels of the form (1,(2,(3,1))) + # so relabel + H=nx.relabel_nodes(G, flatten) + H.name="grid_graph(%s)"%dlabel + return H + +def hypercube_graph(n): + """Return the n-dimensional hypercube. + + Node labels are the integers 0 to 2**n - 1. + + """ + dim=n*[2] + G=grid_graph(dim) + G.name="hypercube_graph_(%d)"%n + return G + +def ladder_graph(n,create_using=None): + """Return the Ladder graph of length n. + + This is two rows of n nodes, with + each pair connected by a single edge. + + Node labels are the integers 0 to 2*n - 1. + + """ + if create_using is not None and create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + G=empty_graph(2*n,create_using) + G.name="ladder_graph_(%d)"%n + G.add_edges_from([(v,v+1) for v in range(n-1)]) + G.add_edges_from([(v,v+1) for v in range(n,2*n-1)]) + G.add_edges_from([(v,v+n) for v in range(n)]) + return G + +def lollipop_graph(m,n,create_using=None): + """Return the Lollipop Graph; K_m connected to P_n. + + This is the Barbell Graph without the right barbell. + + For m>1 and n>=0, the complete graph K_m is connected to the + path P_n. The resulting m+n nodes are labelled 0,...,m-1 for the + complete graph and m,...,m+n-1 for the path. The 2 subgraphs + are joined via the edge (m-1,m). If n=0, this is merely a complete + graph. + + Node labels are the integers 0 to number_of_nodes - 1. + + (This graph is an extremal example in David Aldous and Jim + Fill's etext on Random Walks on Graphs.) + + """ + if create_using is not None and create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + if m<2: + raise nx.NetworkXError(\ + "Invalid graph description, m should be >=2") + if n<0: + raise nx.NetworkXError(\ + "Invalid graph description, n should be >=0") + # the ball + G=complete_graph(m,create_using) + # the stick + G.add_nodes_from([v for v in range(m,m+n)]) + if n>1: + G.add_edges_from([(v,v+1) for v in range(m,m+n-1)]) + # connect ball to stick + if m>0: G.add_edge(m-1,m) + G.name="lollipop_graph(%d,%d)"%(m,n) + return G + +def null_graph(create_using=None): + """ Return the Null graph with no nodes or edges. + + See empty_graph for the use of create_using. + + """ + G=empty_graph(0,create_using) + G.name="null_graph()" + return G + +def path_graph(n,create_using=None): + """Return the Path graph P_n of n nodes linearly connected by n-1 edges. + + Node labels are the integers 0 to n - 1. + If create_using is a DiGraph then the edges are directed in + increasing order. + + """ + G=empty_graph(n,create_using) + G.name="path_graph(%d)"%n + G.add_edges_from([(v,v+1) for v in range(n-1)]) + return G + +def star_graph(n,create_using=None): + """ Return the Star graph with n+1 nodes: one center node, connected to n outer nodes. + + Node labels are the integers 0 to n. + + """ + G=complete_bipartite_graph(1,n,create_using) + G.name="star_graph(%d)"%n + return G + +def trivial_graph(create_using=None): + """ Return the Trivial graph with one node (with integer label 0) and no edges. + + """ + G=empty_graph(1,create_using) + G.name="trivial_graph()" + return G + +def wheel_graph(n,create_using=None): + """ Return the wheel graph: a single hub node connected to each node of the (n-1)-node cycle graph. + + Node labels are the integers 0 to n - 1. + + """ + G=star_graph(n-1,create_using) + G.name="wheel_graph(%d)"%n + G.add_edges_from([(v,v+1) for v in range(1,n-1)]) + if n>2: + G.add_edge(1,n-1) + return G + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/degree_seq.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/degree_seq.py new file mode 100644 index 0000000..c5b8f95 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/degree_seq.py @@ -0,0 +1,793 @@ +# -*- coding: utf-8 -*- +"""Generate graphs with a given degree sequence or expected degree sequence. +""" +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import heapq +from itertools import combinations, permutations +import math +from operator import itemgetter +import random +import networkx as nx +from networkx.utils import random_weighted_sample + +__author__ = "\n".join(['Aric Hagberg ', + 'Pieter Swart ', + 'Dan Schult ' + 'Joel Miller ', + 'Nathan Lemons ' + 'Brian Cloteaux ']) + +__all__ = ['configuration_model', + 'directed_configuration_model', + 'expected_degree_graph', + 'havel_hakimi_graph', + 'directed_havel_hakimi_graph', + 'degree_sequence_tree', + 'random_degree_sequence_graph'] + + +def configuration_model(deg_sequence,create_using=None,seed=None): + """Return a random graph with the given degree sequence. + + The configuration model generates a random pseudograph (graph with + parallel edges and self loops) by randomly assigning edges to + match the given degree sequence. + + Parameters + ---------- + deg_sequence : list of integers + Each list entry corresponds to the degree of a node. + create_using : graph, optional (default MultiGraph) + Return graph of this type. The instance will be cleared. + seed : hashable object, optional + Seed for random number generator. + + Returns + ------- + G : MultiGraph + A graph with the specified degree sequence. + Nodes are labeled starting at 0 with an index + corresponding to the position in deg_sequence. + + Raises + ------ + NetworkXError + If the degree sequence does not have an even sum. + + See Also + -------- + is_valid_degree_sequence + + Notes + ----- + As described by Newman [1]_. + + A non-graphical degree sequence (not realizable by some simple + graph) is allowed since this function returns graphs with self + loops and parallel edges. An exception is raised if the degree + sequence does not have an even sum. + + This configuration model construction process can lead to + duplicate edges and loops. You can remove the self-loops and + parallel edges (see below) which will likely result in a graph + that doesn't have the exact degree sequence specified. This + "finite-size effect" decreases as the size of the graph increases. + + References + ---------- + .. [1] M.E.J. Newman, "The structure and function of complex networks", + SIAM REVIEW 45-2, pp 167-256, 2003. + + Examples + -------- + >>> from networkx.utils import powerlaw_sequence + >>> z=nx.utils.create_degree_sequence(100,powerlaw_sequence) + >>> G=nx.configuration_model(z) + + To remove parallel edges: + + >>> G=nx.Graph(G) + + To remove self loops: + + >>> G.remove_edges_from(G.selfloop_edges()) + """ + if not sum(deg_sequence)%2 ==0: + raise nx.NetworkXError('Invalid degree sequence') + + if create_using is None: + create_using = nx.MultiGraph() + elif create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + if not seed is None: + random.seed(seed) + + # start with empty N-node graph + N=len(deg_sequence) + + # allow multiedges and selfloops + G=nx.empty_graph(N,create_using) + + if N==0 or max(deg_sequence)==0: # done if no edges + return G + + # build stublist, a list of available degree-repeated stubs + # e.g. for deg_sequence=[3,2,1,1,1] + # initially, stublist=[1,1,1,2,2,3,4,5] + # i.e., node 1 has degree=3 and is repeated 3 times, etc. + stublist=[] + for n in G: + for i in range(deg_sequence[n]): + stublist.append(n) + + # shuffle stublist and assign pairs by removing 2 elements at a time + random.shuffle(stublist) + while stublist: + n1 = stublist.pop() + n2 = stublist.pop() + G.add_edge(n1,n2) + + G.name="configuration_model %d nodes %d edges"%(G.order(),G.size()) + return G + + +def directed_configuration_model(in_degree_sequence, + out_degree_sequence, + create_using=None,seed=None): + """Return a directed_random graph with the given degree sequences. + + The configuration model generates a random directed pseudograph + (graph with parallel edges and self loops) by randomly assigning + edges to match the given degree sequences. + + Parameters + ---------- + in_degree_sequence : list of integers + Each list entry corresponds to the in-degree of a node. + out_degree_sequence : list of integers + Each list entry corresponds to the out-degree of a node. + create_using : graph, optional (default MultiDiGraph) + Return graph of this type. The instance will be cleared. + seed : hashable object, optional + Seed for random number generator. + + Returns + ------- + G : MultiDiGraph + A graph with the specified degree sequences. + Nodes are labeled starting at 0 with an index + corresponding to the position in deg_sequence. + + Raises + ------ + NetworkXError + If the degree sequences do not have the same sum. + + See Also + -------- + configuration_model + + Notes + ----- + Algorithm as described by Newman [1]_. + + A non-graphical degree sequence (not realizable by some simple + graph) is allowed since this function returns graphs with self + loops and parallel edges. An exception is raised if the degree + sequences does not have the same sum. + + This configuration model construction process can lead to + duplicate edges and loops. You can remove the self-loops and + parallel edges (see below) which will likely result in a graph + that doesn't have the exact degree sequence specified. This + "finite-size effect" decreases as the size of the graph increases. + + References + ---------- + .. [1] Newman, M. E. J. and Strogatz, S. H. and Watts, D. J. + Random graphs with arbitrary degree distributions and their applications + Phys. Rev. E, 64, 026118 (2001) + + Examples + -------- + >>> D=nx.DiGraph([(0,1),(1,2),(2,3)]) # directed path graph + >>> din=list(D.in_degree().values()) + >>> dout=list(D.out_degree().values()) + >>> din.append(1) + >>> dout[0]=2 + >>> D=nx.directed_configuration_model(din,dout) + + To remove parallel edges: + + >>> D=nx.DiGraph(D) + + To remove self loops: + + >>> D.remove_edges_from(D.selfloop_edges()) + """ + if not sum(in_degree_sequence) == sum(out_degree_sequence): + raise nx.NetworkXError('Invalid degree sequences. ' + 'Sequences must have equal sums.') + + if create_using is None: + create_using = nx.MultiDiGraph() + + if not seed is None: + random.seed(seed) + + nin=len(in_degree_sequence) + nout=len(out_degree_sequence) + + # pad in- or out-degree sequence with zeros to match lengths + if nin>nout: + out_degree_sequence.extend((nin-nout)*[0]) + else: + in_degree_sequence.extend((nout-nin)*[0]) + + # start with empty N-node graph + N=len(in_degree_sequence) + + # allow multiedges and selfloops + G=nx.empty_graph(N,create_using) + + if N==0 or max(in_degree_sequence)==0: # done if no edges + return G + + # build stublists of available degree-repeated stubs + # e.g. for degree_sequence=[3,2,1,1,1] + # initially, stublist=[1,1,1,2,2,3,4,5] + # i.e., node 1 has degree=3 and is repeated 3 times, etc. + in_stublist=[] + for n in G: + for i in range(in_degree_sequence[n]): + in_stublist.append(n) + + out_stublist=[] + for n in G: + for i in range(out_degree_sequence[n]): + out_stublist.append(n) + + # shuffle stublists and assign pairs by removing 2 elements at a time + random.shuffle(in_stublist) + random.shuffle(out_stublist) + while in_stublist and out_stublist: + source = out_stublist.pop() + target = in_stublist.pop() + G.add_edge(source,target) + + G.name="directed configuration_model %d nodes %d edges"%(G.order(),G.size()) + return G + + +def expected_degree_graph(w, seed=None, selfloops=True): + r"""Return a random graph with given expected degrees. + + Given a sequence of expected degrees `W=(w_0,w_1,\ldots,w_{n-1}`) + of length `n` this algorithm assigns an edge between node `u` and + node `v` with probability + + .. math:: + + p_{uv} = \frac{w_u w_v}{\sum_k w_k} . + + Parameters + ---------- + w : list + The list of expected degrees. + selfloops: bool (default=True) + Set to False to remove the possibility of self-loop edges. + seed : hashable object, optional + The seed for the random number generator. + + Returns + ------- + Graph + + Examples + -------- + >>> z=[10 for i in range(100)] + >>> G=nx.expected_degree_graph(z) + + Notes + ----- + The nodes have integer labels corresponding to index of expected degrees + input sequence. + + The complexity of this algorithm is `\mathcal{O}(n+m)` where `n` is the + number of nodes and `m` is the expected number of edges. + + The model in [1]_ includes the possibility of self-loop edges. + Set selfloops=False to produce a graph without self loops. + + For finite graphs this model doesn't produce exactly the given + expected degree sequence. Instead the expected degrees are as + follows. + + For the case without self loops (selfloops=False), + + .. math:: + + E[deg(u)] = \sum_{v \ne u} p_{uv} + = w_u \left( 1 - \frac{w_u}{\sum_k w_k} \right) . + + + NetworkX uses the standard convention that a self-loop edge counts 2 + in the degree of a node, so with self loops (selfloops=True), + + .. math:: + + E[deg(u)] = \sum_{v \ne u} p_{uv} + 2 p_{uu} + = w_u \left( 1 + \frac{w_u}{\sum_k w_k} \right) . + + References + ---------- + .. [1] Fan Chung and L. Lu, Connected components in random graphs with + given expected degree sequences, Ann. Combinatorics, 6, + pp. 125-145, 2002. + .. [2] Joel Miller and Aric Hagberg, + Efficient generation of networks with given expected degrees, + in Algorithms and Models for the Web-Graph (WAW 2011), + Alan Frieze, Paul Horn, and Paweł Prałat (Eds), LNCS 6732, + pp. 115-126, 2011. + """ + n = len(w) + G=nx.empty_graph(n) + if n==0 or max(w)==0: # done if no edges + return G + if seed is not None: + random.seed(seed) + rho = 1/float(sum(w)) + # sort weights, largest first + # preserve order of weights for integer node label mapping + order = sorted(enumerate(w),key=itemgetter(1),reverse=True) + mapping = dict((c,uv[0]) for c,uv in enumerate(order)) + seq = [v for u,v in order] + last=n + if not selfloops: + last-=1 + for u in range(last): + v = u + if not selfloops: + v += 1 + factor = seq[u] * rho + p = seq[v]*factor + if p>1: + p = 1 + while v0: + if p != 1: + r = random.random() + v += int(math.floor(math.log(r)/math.log(1-p))) + if v < n: + q = seq[v]*factor + if q>1: + q = 1 + if random.random() < q/p: + G.add_edge(mapping[u],mapping[v]) + v += 1 + p = q + return G + +def havel_hakimi_graph(deg_sequence,create_using=None): + """Return a simple graph with given degree sequence constructed + using the Havel-Hakimi algorithm. + + Parameters + ---------- + deg_sequence: list of integers + Each integer corresponds to the degree of a node (need not be sorted). + create_using : graph, optional (default Graph) + Return graph of this type. The instance will be cleared. + Directed graphs are not allowed. + + Raises + ------ + NetworkXException + For a non-graphical degree sequence (i.e. one + not realizable by some simple graph). + + Notes + ----- + The Havel-Hakimi algorithm constructs a simple graph by + successively connecting the node of highest degree to other nodes + of highest degree, resorting remaining nodes by degree, and + repeating the process. The resulting graph has a high + degree-associativity. Nodes are labeled 1,.., len(deg_sequence), + corresponding to their position in deg_sequence. + + The basic algorithm is from Hakimi [1]_ and was generalized by + Kleitman and Wang [2]_. + + References + ---------- + .. [1] Hakimi S., On Realizability of a Set of Integers as + Degrees of the Vertices of a Linear Graph. I, + Journal of SIAM, 10(3), pp. 496-506 (1962) + .. [2] Kleitman D.J. and Wang D.L. + Algorithms for Constructing Graphs and Digraphs with Given Valences + and Factors Discrete Mathematics, 6(1), pp. 79-88 (1973) + """ + if not nx.is_valid_degree_sequence(deg_sequence): + raise nx.NetworkXError('Invalid degree sequence') + if create_using is not None: + if create_using.is_directed(): + raise nx.NetworkXError("Directed graphs are not supported") + + p = len(deg_sequence) + G=nx.empty_graph(p,create_using) + num_degs = [] + for i in range(p): + num_degs.append([]) + dmax, dsum, n = 0, 0, 0 + for d in deg_sequence: + # Process only the non-zero integers + if d>0: + num_degs[d].append(n) + dmax, dsum, n = max(dmax,d), dsum+d, n+1 + # Return graph if no edges + if n==0: + return G + + modstubs = [(0,0)]*(dmax+1) + # Successively reduce degree sequence by removing the maximum degree + while n > 0: + # Retrieve the maximum degree in the sequence + while len(num_degs[dmax]) == 0: + dmax -= 1; + # If there are not enough stubs to connect to, then the sequence is + # not graphical + if dmax > n-1: + raise nx.NetworkXError('Non-graphical integer sequence') + + # Remove largest stub in list + source = num_degs[dmax].pop() + n -= 1 + # Reduce the next dmax largest stubs + mslen = 0 + k = dmax + for i in range(dmax): + while len(num_degs[k]) == 0: + k -= 1 + target = num_degs[k].pop() + G.add_edge(source, target) + n -= 1 + if k > 1: + modstubs[mslen] = (k-1,target) + mslen += 1 + # Add back to the list any nonzero stubs that were removed + for i in range(mslen): + (stubval, stubtarget) = modstubs[i] + num_degs[stubval].append(stubtarget) + n += 1 + + G.name="havel_hakimi_graph %d nodes %d edges"%(G.order(),G.size()) + return G + +def directed_havel_hakimi_graph(in_deg_sequence, + out_deg_sequence, + create_using=None): + """Return a directed graph with the given degree sequences. + + Parameters + ---------- + in_deg_sequence : list of integers + Each list entry corresponds to the in-degree of a node. + out_deg_sequence : list of integers + Each list entry corresponds to the out-degree of a node. + create_using : graph, optional (default DiGraph) + Return graph of this type. The instance will be cleared. + + Returns + ------- + G : DiGraph + A graph with the specified degree sequences. + Nodes are labeled starting at 0 with an index + corresponding to the position in deg_sequence + + Raises + ------ + NetworkXError + If the degree sequences are not digraphical. + + See Also + -------- + configuration_model + + Notes + ----- + Algorithm as described by Kleitman and Wang [1]_. + + References + ---------- + .. [1] D.J. Kleitman and D.L. Wang + Algorithms for Constructing Graphs and Digraphs with Given Valences + and Factors Discrete Mathematics, 6(1), pp. 79-88 (1973) + """ + assert(nx.utils.is_list_of_ints(in_deg_sequence)) + assert(nx.utils.is_list_of_ints(out_deg_sequence)) + + if create_using is None: + create_using = nx.DiGraph() + + # Process the sequences and form two heaps to store degree pairs with + # either zero or nonzero out degrees + sumin, sumout, nin, nout = 0, 0, len(in_deg_sequence), len(out_deg_sequence) + maxn = max(nin, nout) + G = nx.empty_graph(maxn,create_using) + if maxn==0: + return G + maxin = 0 + stubheap, zeroheap = [ ], [ ] + for n in range(maxn): + in_deg, out_deg = 0, 0 + if n 0: + stubheap.append((-1*out_deg, -1*in_deg,n)) + elif out_deg > 0: + zeroheap.append((-1*out_deg,n)) + if sumin != sumout: + raise nx.NetworkXError( + 'Invalid degree sequences. Sequences must have equal sums.') + heapq.heapify(stubheap) + heapq.heapify(zeroheap) + + modstubs = [(0,0,0)]*(maxin+1) + # Successively reduce degree sequence by removing the maximum + while stubheap: + # Remove first value in the sequence with a non-zero in degree + (freeout, freein, target) = heapq.heappop(stubheap) + freein *= -1 + if freein > len(stubheap)+len(zeroheap): + raise nx.NetworkXError('Non-digraphical integer sequence') + + # Attach arcs from the nodes with the most stubs + mslen = 0 + for i in range(freein): + if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0][0]): + (stubout, stubsource) = heapq.heappop(zeroheap) + stubin = 0 + else: + (stubout, stubin, stubsource) = heapq.heappop(stubheap) + if stubout == 0: + raise nx.NetworkXError('Non-digraphical integer sequence') + G.add_edge(stubsource, target) + # Check if source is now totally connected + if stubout+1<0 or stubin<0: + modstubs[mslen] = (stubout+1, stubin, stubsource) + mslen += 1 + + # Add the nodes back to the heaps that still have available stubs + for i in range(mslen): + stub = modstubs[i] + if stub[1] < 0: + heapq.heappush(stubheap, stub) + else: + heapq.heappush(zeroheap, (stub[0], stub[2])) + if freeout<0: + heapq.heappush(zeroheap, (freeout, target)) + + G.name="directed_havel_hakimi_graph %d nodes %d edges"%(G.order(),G.size()) + return G + +def degree_sequence_tree(deg_sequence,create_using=None): + """Make a tree for the given degree sequence. + + A tree has #nodes-#edges=1 so + the degree sequence must have + len(deg_sequence)-sum(deg_sequence)/2=1 + """ + + if not len(deg_sequence)-sum(deg_sequence)/2.0 == 1.0: + raise nx.NetworkXError("Degree sequence invalid") + if create_using is not None and create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + # single node tree + if len(deg_sequence)==1: + G=nx.empty_graph(0,create_using) + return G + + # all degrees greater than 1 + deg=[s for s in deg_sequence if s>1] + deg.sort(reverse=True) + + # make path graph as backbone + n=len(deg)+2 + G=nx.path_graph(n,create_using) + last=n + + # add the leaves + for source in range(1,n-1): + nedges=deg.pop()-2 + for target in range(last,last+nedges): + G.add_edge(source, target) + last+=nedges + + # in case we added one too many + if len(G.degree())>len(deg_sequence): + G.remove_node(0) + return G + +def random_degree_sequence_graph(sequence, seed=None, tries=10): + r"""Return a simple random graph with the given degree sequence. + + If the maximum degree `d_m` in the sequence is `O(m^{1/4})` then the + algorithm produces almost uniform random graphs in `O(m d_m)` time + where `m` is the number of edges. + + Parameters + ---------- + sequence : list of integers + Sequence of degrees + seed : hashable object, optional + Seed for random number generator + tries : int, optional + Maximum number of tries to create a graph + + Returns + ------- + G : Graph + A graph with the specified degree sequence. + Nodes are labeled starting at 0 with an index + corresponding to the position in the sequence. + + Raises + ------ + NetworkXUnfeasible + If the degree sequence is not graphical. + NetworkXError + If a graph is not produced in specified number of tries + + See Also + -------- + is_valid_degree_sequence, configuration_model + + Notes + ----- + The generator algorithm [1]_ is not guaranteed to produce a graph. + + References + ---------- + .. [1] Moshen Bayati, Jeong Han Kim, and Amin Saberi, + A sequential algorithm for generating random graphs. + Algorithmica, Volume 58, Number 4, 860-910, + DOI: 10.1007/s00453-009-9340-1 + + Examples + -------- + >>> sequence = [1, 2, 2, 3] + >>> G = nx.random_degree_sequence_graph(sequence) + >>> sorted(G.degree().values()) + [1, 2, 2, 3] + """ + DSRG = DegreeSequenceRandomGraph(sequence, seed=seed) + for try_n in range(tries): + try: + return DSRG.generate() + except nx.NetworkXUnfeasible: + pass + raise nx.NetworkXError('failed to generate graph in %d tries'%tries) + +class DegreeSequenceRandomGraph(object): + # class to generate random graphs with a given degree sequence + # use random_degree_sequence_graph() + def __init__(self, degree, seed=None): + if not nx.is_valid_degree_sequence(degree): + raise nx.NetworkXUnfeasible('degree sequence is not graphical') + if seed is not None: + random.seed(seed) + self.degree = list(degree) + # node labels are integers 0,...,n-1 + self.m = sum(self.degree)/2.0 # number of edges + try: + self.dmax = max(self.degree) # maximum degree + except ValueError: + self.dmax = 0 + + def generate(self): + # remaining_degree is mapping from int->remaining degree + self.remaining_degree = dict(enumerate(self.degree)) + # add all nodes to make sure we get isolated nodes + self.graph = nx.Graph() + self.graph.add_nodes_from(self.remaining_degree) + # remove zero degree nodes + for n,d in list(self.remaining_degree.items()): + if d == 0: + del self.remaining_degree[n] + if len(self.remaining_degree) > 0: + # build graph in three phases according to how many unmatched edges + self.phase1() + self.phase2() + self.phase3() + return self.graph + + def update_remaining(self, u, v, aux_graph=None): + # decrement remaining nodes, modify auxilliary graph if in phase3 + if aux_graph is not None: + # remove edges from auxilliary graph + aux_graph.remove_edge(u,v) + if self.remaining_degree[u] == 1: + del self.remaining_degree[u] + if aux_graph is not None: + aux_graph.remove_node(u) + else: + self.remaining_degree[u] -= 1 + if self.remaining_degree[v] == 1: + del self.remaining_degree[v] + if aux_graph is not None: + aux_graph.remove_node(v) + else: + self.remaining_degree[v] -= 1 + + def p(self,u,v): + # degree probability + return 1 - self.degree[u]*self.degree[v]/(4.0*self.m) + + def q(self,u,v): + # remaining degree probability + norm = float(max(self.remaining_degree.values()))**2 + return self.remaining_degree[u]*self.remaining_degree[v]/norm + + def suitable_edge(self): + # Check if there is a suitable edge that is not in the graph + # True if an (arbitrary) remaining node has at least one possible + # connection to another remaining node + nodes = iter(self.remaining_degree) + u = next(nodes) # one arbitrary node + for v in nodes: # loop over all other remaining nodes + if not self.graph.has_edge(u, v): + return True + return False + + def phase1(self): + # choose node pairs from (degree) weighted distribution + while sum(self.remaining_degree.values()) >= 2 * self.dmax**2: + u,v = sorted(random_weighted_sample(self.remaining_degree, 2)) + if self.graph.has_edge(u,v): + continue + if random.random() < self.p(u,v): # accept edge + self.graph.add_edge(u,v) + self.update_remaining(u,v) + + def phase2(self): + # choose remaining nodes uniformly at random and use rejection sampling + while len(self.remaining_degree) >= 2 * self.dmax: + norm = float(max(self.remaining_degree.values()))**2 + while True: + u,v = sorted(random.sample(self.remaining_degree.keys(), 2)) + if self.graph.has_edge(u,v): + continue + if random.random() < self.q(u,v): + break + if random.random() < self.p(u,v): # accept edge + self.graph.add_edge(u,v) + self.update_remaining(u,v) + + def phase3(self): + # build potential remaining edges and choose with rejection sampling + potential_edges = combinations(self.remaining_degree, 2) + # build auxilliary graph of potential edges not already in graph + H = nx.Graph([(u,v) for (u,v) in potential_edges + if not self.graph.has_edge(u,v)]) + while self.remaining_degree: + if not self.suitable_edge(): + raise nx.NetworkXUnfeasible('no suitable edges left') + while True: + u,v = sorted(random.choice(H.edges())) + if random.random() < self.q(u,v): + break + if random.random() < self.p(u,v): # accept edge + self.graph.add_edge(u,v) + self.update_remaining(u,v, aux_graph=H) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/directed.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/directed.py new file mode 100644 index 0000000..d1dc712 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/directed.py @@ -0,0 +1,304 @@ +""" +Generators for some directed graphs. + +gn_graph: growing network +gnc_graph: growing network with copying +gnr_graph: growing network with redirection +scale_free_graph: scale free directed graph + +""" +# Copyright (C) 2006-2009 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ ="""Aric Hagberg (hagberg@lanl.gov)\nWillem Ligtenberg (W.P.A.Ligtenberg@tue.nl)""" + +__all__ = ['gn_graph', 'gnc_graph', 'gnr_graph','scale_free_graph'] + +import random + +import networkx as nx +from networkx.generators.classic import empty_graph +from networkx.utils import discrete_sequence + + +def gn_graph(n,kernel=None,create_using=None,seed=None): + """Return the GN digraph with n nodes. + + The GN (growing network) graph is built by adding nodes one at a time with + a link to one previously added node. The target node for the link is + chosen with probability based on degree. The default attachment kernel is + a linear function of degree. + + The graph is always a (directed) tree. + + Parameters + ---------- + n : int + The number of nodes for the generated graph. + kernel : function + The attachment kernel. + create_using : graph, optional (default DiGraph) + Return graph of this type. The instance will be cleared. + seed : hashable object, optional + The seed for the random number generator. + + Examples + -------- + >>> D=nx.gn_graph(10) # the GN graph + >>> G=D.to_undirected() # the undirected version + + To specify an attachment kernel use the kernel keyword + + >>> D=nx.gn_graph(10,kernel=lambda x:x**1.5) # A_k=k^1.5 + + References + ---------- + .. [1] P. L. Krapivsky and S. Redner, + Organization of Growing Random Networks, + Phys. Rev. E, 63, 066123, 2001. + """ + if create_using is None: + create_using = nx.DiGraph() + elif not create_using.is_directed(): + raise nx.NetworkXError("Directed Graph required in create_using") + + if kernel is None: + kernel = lambda x: x + + if seed is not None: + random.seed(seed) + + G=empty_graph(1,create_using) + G.name="gn_graph(%s)"%(n) + + if n==1: + return G + + G.add_edge(1,0) # get started + ds=[1,1] # degree sequence + + for source in range(2,n): + # compute distribution from kernel and degree + dist=[kernel(d) for d in ds] + # choose target from discrete distribution + target=discrete_sequence(1,distribution=dist)[0] + G.add_edge(source,target) + ds.append(1) # the source has only one link (degree one) + ds[target]+=1 # add one to the target link degree + return G + + +def gnr_graph(n,p,create_using=None,seed=None): + """Return the GNR digraph with n nodes and redirection probability p. + + The GNR (growing network with redirection) graph is built by adding nodes + one at a time with a link to one previously added node. The previous + target node is chosen uniformly at random. With probabiliy p the link is + instead "redirected" to the successor node of the target. The graph is + always a (directed) tree. + + Parameters + ---------- + n : int + The number of nodes for the generated graph. + p : float + The redirection probability. + create_using : graph, optional (default DiGraph) + Return graph of this type. The instance will be cleared. + seed : hashable object, optional + The seed for the random number generator. + + Examples + -------- + >>> D=nx.gnr_graph(10,0.5) # the GNR graph + >>> G=D.to_undirected() # the undirected version + + References + ---------- + .. [1] P. L. Krapivsky and S. Redner, + Organization of Growing Random Networks, + Phys. Rev. E, 63, 066123, 2001. + """ + if create_using is None: + create_using = nx.DiGraph() + elif not create_using.is_directed(): + raise nx.NetworkXError("Directed Graph required in create_using") + + if not seed is None: + random.seed(seed) + + G=empty_graph(1,create_using) + G.name="gnr_graph(%s,%s)"%(n,p) + + if n==1: + return G + + for source in range(1,n): + target=random.randrange(0,source) + if random.random() < p and target !=0: + target=G.successors(target)[0] + G.add_edge(source,target) + + return G + + +def gnc_graph(n,create_using=None,seed=None): + """Return the GNC digraph with n nodes. + + The GNC (growing network with copying) graph is built by adding nodes one + at a time with a links to one previously added node (chosen uniformly at + random) and to all of that node's successors. + + Parameters + ---------- + n : int + The number of nodes for the generated graph. + create_using : graph, optional (default DiGraph) + Return graph of this type. The instance will be cleared. + seed : hashable object, optional + The seed for the random number generator. + + References + ---------- + .. [1] P. L. Krapivsky and S. Redner, + Network Growth by Copying, + Phys. Rev. E, 71, 036118, 2005k.}, + """ + if create_using is None: + create_using = nx.DiGraph() + elif not create_using.is_directed(): + raise nx.NetworkXError("Directed Graph required in create_using") + + if not seed is None: + random.seed(seed) + + G=empty_graph(1,create_using) + G.name="gnc_graph(%s)"%(n) + + if n==1: + return G + + for source in range(1,n): + target=random.randrange(0,source) + for succ in G.successors(target): + G.add_edge(source,succ) + G.add_edge(source,target) + + return G + + +def scale_free_graph(n, + alpha=0.41, + beta=0.54, + gamma=0.05, + delta_in=0.2, + delta_out=0, + create_using=None, + seed=None): + """Return a scale free directed graph. + + Parameters + ---------- + n : integer + Number of nodes in graph + alpha : float + Probability for adding a new node connected to an existing node + chosen randomly according to the in-degree distribution. + beta : float + Probability for adding an edge between two existing nodes. + One existing node is chosen randomly according the in-degree + distribution and the other chosen randomly according to the out-degree + distribution. + gamma : float + Probability for adding a new node conecgted to an existing node + chosen randomly according to the out-degree distribution. + delta_in : float + Bias for choosing ndoes from in-degree distribution. + delta_out : float + Bias for choosing ndoes from out-degree distribution. + create_using : graph, optional (default MultiDiGraph) + Use this graph instance to start the process (default=3-cycle). + seed : integer, optional + Seed for random number generator + + Examples + -------- + >>> G=nx.scale_free_graph(100) + + Notes + ----- + The sum of alpha, beta, and gamma must be 1. + + References + ---------- + .. [1] B. Bollob{\'a}s, C. Borgs, J. Chayes, and O. Riordan, + Directed scale-free graphs, + Proceedings of the fourteenth annual ACM-SIAM symposium on + Discrete algorithms, 132--139, 2003. + """ + + def _choose_node(G,distribution,delta): + cumsum=0.0 + # normalization + psum=float(sum(distribution.values()))+float(delta)*len(distribution) + r=random.random() + for i in range(0,len(distribution)): + cumsum+=(distribution[i]+delta)/psum + if r < cumsum: + break + return i + + if create_using is None: + # start with 3-cycle + G = nx.MultiDiGraph() + G.add_edges_from([(0,1),(1,2),(2,0)]) + else: + # keep existing graph structure? + G = create_using + if not (G.is_directed() and G.is_multigraph()): + raise nx.NetworkXError(\ + "MultiDiGraph required in create_using") + + if alpha <= 0: + raise ValueError('alpha must be >= 0.') + if beta <= 0: + raise ValueError('beta must be >= 0.') + if gamma <= 0: + raise ValueError('beta must be >= 0.') + + if alpha+beta+gamma !=1.0: + raise ValueError('alpha+beta+gamma must equal 1.') + + G.name="directed_scale_free_graph(%s,alpha=%s,beta=%s,gamma=%s,delta_in=%s,delta_out=%s)"%(n,alpha,beta,gamma,delta_in,delta_out) + + # seed random number generated (uses None as default) + random.seed(seed) + + while len(G) +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """\n""".join(['Drew Conway ', + 'Aric Hagberg ']) +__all__ = ['ego_graph'] + +import networkx as nx + +def ego_graph(G,n,radius=1,center=True,undirected=False,distance=None): + """Returns induced subgraph of neighbors centered at node n within + a given radius. + + Parameters + ---------- + G : graph + A NetworkX Graph or DiGraph + + n : node + A single node + + radius : number, optional + Include all neighbors of distance<=radius from n. + + center : bool, optional + If False, do not include center node in graph + + undirected : bool, optional + If True use both in- and out-neighbors of directed graphs. + + distance : key, optional + Use specified edge data key as distance. For example, setting + distance='weight' will use the edge weight to measure the + distance from the node n. + + Notes + ----- + For directed graphs D this produces the "out" neighborhood + or successors. If you want the neighborhood of predecessors + first reverse the graph with D.reverse(). If you want both + directions use the keyword argument undirected=True. + + Node, edge, and graph attributes are copied to the returned subgraph. + """ + if undirected: + if distance is not None: + sp,_=nx.single_source_dijkstra(G.to_undirected(), + n,cutoff=radius, + weight=distance) + else: + sp=nx.single_source_shortest_path_length(G.to_undirected(), + n,cutoff=radius) + else: + if distance is not None: + sp,_=nx.single_source_dijkstra(G, + n,cutoff=radius, + weight=distance) + else: + sp=nx.single_source_shortest_path_length(G,n,cutoff=radius) + + H=G.subgraph(sp).copy() + if not center: + H.remove_node(n) + return H diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/geometric.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/geometric.py new file mode 100644 index 0000000..b64e0c2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/geometric.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +""" +Generators for geometric graphs. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +from __future__ import print_function + +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Dan Schult (dschult@colgate.edu)', + 'Ben Edwards (BJEdwards@gmail.com)']) + +__all__ = ['random_geometric_graph', + 'waxman_graph', + 'geographical_threshold_graph', + 'navigable_small_world_graph'] + +from bisect import bisect_left +from functools import reduce +from itertools import product +import math, random, sys +import networkx as nx + +#--------------------------------------------------------------------------- +# Random Geometric Graphs +#--------------------------------------------------------------------------- + +def random_geometric_graph(n, radius, dim=2, pos=None): + r"""Return the random geometric graph in the unit cube. + + The random geometric graph model places n nodes uniformly at random + in the unit cube Two nodes `u,v` are connected with an edge if + `d(u,v)<=r` where `d` is the Euclidean distance and `r` is a radius + threshold. + + Parameters + ---------- + n : int + Number of nodes + radius: float + Distance threshold value + dim : int, optional + Dimension of graph + pos : dict, optional + A dictionary keyed by node with node positions as values. + + Returns + ------- + Graph + + Examples + -------- + >>> G = nx.random_geometric_graph(20,0.1) + + Notes + ----- + This uses an `n^2` algorithm to build the graph. A faster algorithm + is possible using k-d trees. + + The pos keyword can be used to specify node positions so you can create + an arbitrary distribution and domain for positions. If you need a distance + function other than Euclidean you'll have to hack the algorithm. + + E.g to use a 2d Gaussian distribution of node positions with mean (0,0) + and std. dev. 2 + + >>> import random + >>> n=20 + >>> p=dict((i,(random.gauss(0,2),random.gauss(0,2))) for i in range(n)) + >>> G = nx.random_geometric_graph(n,0.2,pos=p) + + References + ---------- + .. [1] Penrose, Mathew, Random Geometric Graphs, + Oxford Studies in Probability, 5, 2003. + """ + G=nx.Graph() + G.name="Random Geometric Graph" + G.add_nodes_from(range(n)) + if pos is None: + # random positions + for n in G: + G.node[n]['pos']=[random.random() for i in range(0,dim)] + else: + nx.set_node_attributes(G,'pos',pos) + # connect nodes within "radius" of each other + # n^2 algorithm, could use a k-d tree implementation + nodes = G.nodes(data=True) + while nodes: + u,du = nodes.pop() + pu = du['pos'] + for v,dv in nodes: + pv = dv['pos'] + d = sum(((a-b)**2 for a,b in zip(pu,pv))) + if d <= radius**2: + G.add_edge(u,v) + return G + +def geographical_threshold_graph(n, theta, alpha=2, dim=2, + pos=None, weight=None): + r"""Return a geographical threshold graph. + + The geographical threshold graph model places n nodes uniformly at random + in a rectangular domain. Each node `u` is assigned a weight `w_u`. + Two nodes `u,v` are connected with an edge if + + .. math:: + + w_u + w_v \ge \theta r^{\alpha} + + where `r` is the Euclidean distance between `u` and `v`, + and `\theta`, `\alpha` are parameters. + + Parameters + ---------- + n : int + Number of nodes + theta: float + Threshold value + alpha: float, optional + Exponent of distance function + dim : int, optional + Dimension of graph + pos : dict + Node positions as a dictionary of tuples keyed by node. + weight : dict + Node weights as a dictionary of numbers keyed by node. + + Returns + ------- + Graph + + Examples + -------- + >>> G = nx.geographical_threshold_graph(20,50) + + Notes + ----- + If weights are not specified they are assigned to nodes by drawing randomly + from an the exponential distribution with rate parameter `\lambda=1`. + To specify a weights from a different distribution assign them to a + dictionary and pass it as the weight= keyword + + >>> import random + >>> n = 20 + >>> w=dict((i,random.expovariate(5.0)) for i in range(n)) + >>> G = nx.geographical_threshold_graph(20,50,weight=w) + + If node positions are not specified they are randomly assigned from the + uniform distribution. + + References + ---------- + .. [1] Masuda, N., Miwa, H., Konno, N.: + Geographical threshold graphs with small-world and scale-free properties. + Physical Review E 71, 036108 (2005) + .. [2] Milan Bradonjić, Aric Hagberg and Allon G. Percus, + Giant component and connectivity in geographical threshold graphs, + in Algorithms and Models for the Web-Graph (WAW 2007), + Antony Bonato and Fan Chung (Eds), pp. 209--216, 2007 + """ + G=nx.Graph() + # add n nodes + G.add_nodes_from([v for v in range(n)]) + if weight is None: + # choose weights from exponential distribution + for n in G: + G.node[n]['weight'] = random.expovariate(1.0) + else: + nx.set_node_attributes(G,'weight',weight) + if pos is None: + # random positions + for n in G: + G.node[n]['pos']=[random.random() for i in range(0,dim)] + else: + nx.set_node_attributes(G,'pos',pos) + G.add_edges_from(geographical_threshold_edges(G, theta, alpha)) + return G + +def geographical_threshold_edges(G, theta, alpha=2): + # generate edges for a geographical threshold graph given a graph + # with positions and weights assigned as node attributes 'pos' and 'weight'. + nodes = G.nodes(data=True) + while nodes: + u,du = nodes.pop() + wu = du['weight'] + pu = du['pos'] + for v,dv in nodes: + wv = dv['weight'] + pv = dv['pos'] + r = math.sqrt(sum(((a-b)**2 for a,b in zip(pu,pv)))) + if wu+wv >= theta*r**alpha: + yield(u,v) + +def waxman_graph(n, alpha=0.4, beta=0.1, L=None, domain=(0,0,1,1)): + r"""Return a Waxman random graph. + + The Waxman random graph models place n nodes uniformly at random + in a rectangular domain. Two nodes u,v are connected with an edge + with probability + + .. math:: + p = \alpha*exp(-d/(\beta*L)). + + This function implements both Waxman models. + + Waxman-1: `L` not specified + The distance `d` is the Euclidean distance between the nodes u and v. + `L` is the maximum distance between all nodes in the graph. + + Waxman-2: `L` specified + The distance `d` is chosen randomly in `[0,L]`. + + Parameters + ---------- + n : int + Number of nodes + alpha: float + Model parameter + beta: float + Model parameter + L : float, optional + Maximum distance between nodes. If not specified the actual distance + is calculated. + domain : tuple of numbers, optional + Domain size (xmin, ymin, xmax, ymax) + + Returns + ------- + G: Graph + + References + ---------- + .. [1] B. M. Waxman, Routing of multipoint connections. + IEEE J. Select. Areas Commun. 6(9),(1988) 1617-1622. + """ + # build graph of n nodes with random positions in the unit square + G = nx.Graph() + G.add_nodes_from(range(n)) + (xmin,ymin,xmax,ymax)=domain + for n in G: + G.node[n]['pos']=((xmin + (xmax-xmin))*random.random(), + (ymin + (ymax-ymin))*random.random()) + if L is None: + # find maximum distance L between two nodes + l = 0 + pos = list(nx.get_node_attributes(G,'pos').values()) + while pos: + x1,y1 = pos.pop() + for x2,y2 in pos: + r2 = (x1-x2)**2 + (y1-y2)**2 + if r2 > l: + l = r2 + l=math.sqrt(l) + else: + # user specified maximum distance + l = L + + nodes=G.nodes() + if L is None: + # Waxman-1 model + # try all pairs, connect randomly based on euclidean distance + while nodes: + u = nodes.pop() + x1,y1 = G.node[u]['pos'] + for v in nodes: + x2,y2 = G.node[v]['pos'] + r = math.sqrt((x1-x2)**2 + (y1-y2)**2) + if random.random() < alpha*math.exp(-r/(beta*l)): + G.add_edge(u,v) + else: + # Waxman-2 model + # try all pairs, connect randomly based on randomly chosen l + while nodes: + u = nodes.pop() + for v in nodes: + r = random.random()*l + if random.random() < alpha*math.exp(-r/(beta*l)): + G.add_edge(u,v) + return G + + +def navigable_small_world_graph(n, p=1, q=1, r=2, dim=2, seed=None): + r"""Return a navigable small-world graph. + + A navigable small-world graph is a directed grid with additional + long-range connections that are chosen randomly. From [1]_: + + Begin with a set of nodes that are identified with the set of lattice + points in an `n \times n` square, `{(i,j): i\in {1,2,\ldots,n}, j\in {1,2,\ldots,n}}` + and define the lattice distance between two nodes `(i,j)` and `(k,l)` + to be the number of "lattice steps" separating them: `d((i,j),(k,l)) = |k-i|+|l-j|`. + + For a universal constant `p`, the node `u` has a directed edge to every other + node within lattice distance `p` (local contacts) . + + For universal constants `q\ge 0` and `r\ge 0` construct directed edges from `u` to `q` + other nodes (long-range contacts) using independent random trials; the i'th + directed edge from `u` has endpoint `v` with probability proportional to `d(u,v)^{-r}`. + + Parameters + ---------- + n : int + The number of nodes. + p : int + The diameter of short range connections. Each node is connected + to every other node within lattice distance p. + q : int + The number of long-range connections for each node. + r : float + Exponent for decaying probability of connections. The probability of + connecting to a node at lattice distance d is 1/d^r. + dim : int + Dimension of grid + seed : int, optional + Seed for random number generator (default=None). + + References + ---------- + .. [1] J. Kleinberg. The small-world phenomenon: An algorithmic + perspective. Proc. 32nd ACM Symposium on Theory of Computing, 2000. + """ + if (p < 1): + raise nx.NetworkXException("p must be >= 1") + if (q < 0): + raise nx.NetworkXException("q must be >= 0") + if (r < 0): + raise nx.NetworkXException("r must be >= 1") + if not seed is None: + random.seed(seed) + G = nx.DiGraph() + nodes = list(product(range(n),repeat=dim)) + for p1 in nodes: + probs = [0] + for p2 in nodes: + if p1==p2: + continue + d = sum((abs(b-a) for a,b in zip(p1,p2))) + if d <= p: + G.add_edge(p1,p2) + probs.append(d**-r) + cdf = list(nx.utils.cumulative_sum(probs)) + for _ in range(q): + target = nodes[bisect_left(cdf,random.uniform(0, cdf[-1]))] + G.add_edge(p1,target) + return G + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/hybrid.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/hybrid.py new file mode 100644 index 0000000..b4936fa --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/hybrid.py @@ -0,0 +1,116 @@ +""" +Hybrid + +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nDan Schult (dschult@colgate.edu)""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +_all__ = ['kl_connected_subgraph', 'is_kl_connected'] + +import copy +import networkx as nx + +def kl_connected_subgraph(G,k,l,low_memory=False,same_as_graph=False): + """ Returns the maximum locally (k,l) connected subgraph of G. + + (k,l)-connected subgraphs are presented by Fan Chung and Li + in "The Small World Phenomenon in hybrid power law graphs" + to appear in "Complex Networks" (Ed. E. Ben-Naim) Lecture + Notes in Physics, Springer (2004) + + low_memory=True then use a slightly slower, but lower memory version + same_as_graph=True then return a tuple with subgraph and + pflag for if G is kl-connected + """ + H=copy.deepcopy(G) # subgraph we construct by removing from G + + graphOK=True + deleted_some=True # hack to start off the while loop + while deleted_some: + deleted_some=False + for edge in H.edges(): + (u,v)=edge + ### Get copy of graph needed for this search + if low_memory: + verts=set([u,v]) + for i in range(k): + [verts.update(G.neighbors(w)) for w in verts.copy()] + G2=G.subgraph(list(verts)) + else: + G2=copy.deepcopy(G) + ### + path=[u,v] + cnt=0 + accept=0 + while path: + cnt += 1 # Found a path + if cnt>=l: + accept=1 + break + # record edges along this graph + prev=u + for w in path: + if prev!=w: + G2.remove_edge(prev,w) + prev=w +# path=shortest_path(G2,u,v,k) # ??? should "Cutoff" be k+1? + try: + path=nx.shortest_path(G2,u,v) # ??? should "Cutoff" be k+1? + except nx.NetworkXNoPath: + path = False + # No Other Paths + if accept==0: + H.remove_edge(u,v) + deleted_some=True + if graphOK: graphOK=False + # We looked through all edges and removed none of them. + # So, H is the maximal (k,l)-connected subgraph of G + if same_as_graph: + return (H,graphOK) + return H + +def is_kl_connected(G,k,l,low_memory=False): + """Returns True if G is kl connected.""" + graphOK=True + for edge in G.edges(): + (u,v)=edge + ### Get copy of graph needed for this search + if low_memory: + verts=set([u,v]) + for i in range(k): + [verts.update(G.neighbors(w)) for w in verts.copy()] + G2=G.subgraph(verts) + else: + G2=copy.deepcopy(G) + ### + path=[u,v] + cnt=0 + accept=0 + while path: + cnt += 1 # Found a path + if cnt>=l: + accept=1 + break + # record edges along this graph + prev=u + for w in path: + if w!=prev: + G2.remove_edge(prev,w) + prev=w +# path=shortest_path(G2,u,v,k) # ??? should "Cutoff" be k+1? + try: + path=nx.shortest_path(G2,u,v) # ??? should "Cutoff" be k+1? + except nx.NetworkXNoPath: + path = False + # No Other Paths + if accept==0: + graphOK=False + break + # return status + return graphOK + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/intersection.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/intersection.py new file mode 100644 index 0000000..cc7903d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/intersection.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +""" +Generators for random intersection graphs. +""" +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import random +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)']) + +__all__ = ['uniform_random_intersection_graph', + 'k_random_intersection_graph', + 'general_random_intersection_graph', + ] + +def uniform_random_intersection_graph(n, m, p, seed=None): + """Return a uniform random intersection graph. + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set (nodes) + m : int + The number of nodes in the second bipartite set (attributes) + p : float + Probability of connecting nodes between bipartite sets + seed : int, optional + Seed for random number generator (default=None). + + See Also + -------- + gnp_random_graph + + References + ---------- + .. [1] K.B. Singer-Cohen, Random Intersection Graphs, 1995, + PhD thesis, Johns Hopkins University + .. [2] Fill, J. A., Scheinerman, E. R., and Singer-Cohen, K. B., + Random intersection graphs when m = !(n): + An equivalence theorem relating the evolution of the g(n, m, p) + and g(n, p) models. Random Struct. Algorithms 16, 2 (2000), 156–176. + """ + G=nx.bipartite_random_graph(n, m, p, seed=seed) + return nx.projected_graph(G, range(n)) + +def k_random_intersection_graph(n,m,k): + """Return a intersection graph with randomly chosen attribute sets for + each node that are of equal size (k). + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set (nodes) + m : int + The number of nodes in the second bipartite set (attributes) + k : float + Size of attribute set to assign to each node. + seed : int, optional + Seed for random number generator (default=None). + + See Also + -------- + gnp_random_graph, uniform_random_intersection_graph + + References + ---------- + .. [1] Godehardt, E., and Jaworski, J. + Two models of random intersection graphs and their applications. + Electronic Notes in Discrete Mathematics 10 (2001), 129--132. + """ + G = nx.empty_graph(n + m) + mset = range(n,n+m) + for v in range(n): + targets = random.sample(mset, k) + G.add_edges_from(zip([v]*len(targets), targets)) + return nx.projected_graph(G, range(n)) + +def general_random_intersection_graph(n,m,p): + """Return a random intersection graph with independent probabilities + for connections between node and attribute sets. + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set (nodes) + m : int + The number of nodes in the second bipartite set (attributes) + p : list of floats of length m + Probabilities for connecting nodes to each attribute + seed : int, optional + Seed for random number generator (default=None). + + See Also + -------- + gnp_random_graph, uniform_random_intersection_graph + + References + ---------- + .. [1] Nikoletseas, S. E., Raptopoulos, C., and Spirakis, P. G. + The existence and efficient construction of large independent sets + in general random intersection graphs. In ICALP (2004), J. D´ıaz, + J. Karhum¨aki, A. Lepist¨o, and D. Sannella, Eds., vol. 3142 + of Lecture Notes in Computer Science, Springer, pp. 1029–1040. + """ + if len(p)!=m: + raise ValueError("Probability list p must have m elements.") + G = nx.empty_graph(n + m) + mset = range(n,n+m) + for u in range(n): + for v,q in zip(mset,p): + if random.random() +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)\nDan Schult(dschult@colgate.edu)""" + +__all__ = ['line_graph'] + +import networkx as nx + +def line_graph(G): + """Return the line graph of the graph or digraph G. + + The line graph of a graph G has a node for each edge + in G and an edge between those nodes if the two edges + in G share a common node. + + For DiGraphs an edge an edge represents a directed path of length 2. + + The original node labels are kept as two-tuple node labels + in the line graph. + + Parameters + ---------- + G : graph + A NetworkX Graph or DiGraph + + Examples + -------- + >>> G=nx.star_graph(3) + >>> L=nx.line_graph(G) + >>> print(sorted(L.edges())) # makes a clique, K3 + [((0, 1), (0, 2)), ((0, 1), (0, 3)), ((0, 3), (0, 2))] + + Notes + ----- + Not implemented for MultiGraph or MultiDiGraph classes. + + Graph, node, and edge data are not propagated to the new graph. + + """ + if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph: + raise Exception("Line graph not implemented for Multi(Di)Graphs") + L=G.__class__() + if G.is_directed(): + for u,nlist in G.adjacency_iter(): # same as successors for digraph + # look for directed path of length two + for n in nlist: + nbrs=G[n] # successors + for nbr in nbrs: + if nbr!=u: + L.add_edge((u,n),(n,nbr)) + else: + for u,nlist in G.adjacency_iter(): + # label nodes as tuple of edge endpoints in original graph + # "node tuple" must be in lexigraphical order + nodes=[tuple(sorted(n)) for n in zip([u]*len(nlist),nlist)] + # add clique of nodes to graph + while nodes: + u=nodes.pop() + L.add_edges_from((u,v) for v in nodes) + return L + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/random_clustered.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/random_clustered.py new file mode 100644 index 0000000..fe294e6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/random_clustered.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +"""Generate graphs with given degree and triangle sequence. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import random +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Joel Miller (joel.c.miller.research@gmail.com)']) + +__all__ = ['random_clustered_graph'] + + +def random_clustered_graph(joint_degree_sequence, create_using=None, seed=None): + """Generate a random graph with the given joint degree and triangle + degree sequence. + + This uses a configuration model-like approach to generate a + random pseudograph (graph with parallel edges and self loops) by + randomly assigning edges to match the given indepdenent edge + and triangle degree sequence. + + Parameters + ---------- + joint_degree_sequence : list of integer pairs + Each list entry corresponds to the independent edge degree and + triangle degree of a node. + create_using : graph, optional (default MultiGraph) + Return graph of this type. The instance will be cleared. + seed : hashable object, optional + The seed for the random number generator. + + Returns + ------- + G : MultiGraph + A graph with the specified degree sequence. Nodes are labeled + starting at 0 with an index corresponding to the position in + deg_sequence. + + Raises + ------ + NetworkXError + If the independent edge degree sequence sum is not even + or the triangle degree sequence sum is not divisible by 3. + + Notes + ----- + As described by Miller [1]_ (see also Newman [2]_ for an equivalent + description). + + A non-graphical degree sequence (not realizable by some simple + graph) is allowed since this function returns graphs with self + loops and parallel edges. An exception is raised if the + independent degree sequence does not have an even sum or the + triangle degree sequence sum is not divisible by 3. + + This configuration model-like construction process can lead to + duplicate edges and loops. You can remove the self-loops and + parallel edges (see below) which will likely result in a graph + that doesn't have the exact degree sequence specified. This + "finite-size effect" decreases as the size of the graph increases. + + References + ---------- + .. [1] J. C. Miller "Percolation and Epidemics on Random Clustered Graphs." + Physical Review E, Rapid Communication (to appear). + .. [2] M.E.J. Newman, "Random clustered networks". + Physical Review Letters (to appear). + + Examples + -------- + >>> deg_tri=[[1,0],[1,0],[1,0],[2,0],[1,0],[2,1],[0,1],[0,1]] + >>> G = nx.random_clustered_graph(deg_tri) + + To remove parallel edges: + + >>> G=nx.Graph(G) + + To remove self loops: + + >>> G.remove_edges_from(G.selfloop_edges()) + + """ + if create_using is None: + create_using = nx.MultiGraph() + elif create_using.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + if not seed is None: + random.seed(seed) + + # In Python 3, zip() returns an iterator. Make this into a list. + joint_degree_sequence = list(joint_degree_sequence) + + N = len(joint_degree_sequence) + G = nx.empty_graph(N,create_using) + + ilist = [] + tlist = [] + for n in G: + degrees = joint_degree_sequence[n] + for icount in range(degrees[0]): + ilist.append(n) + for tcount in range(degrees[1]): + tlist.append(n) + + if len(ilist)%2 != 0 or len(tlist)%3 != 0: + raise nx.NetworkXError('Invalid degree sequence') + + random.shuffle(ilist) + random.shuffle(tlist) + while ilist: + G.add_edge(ilist.pop(),ilist.pop()) + while tlist: + n1 = tlist.pop() + n2 = tlist.pop() + n3 = tlist.pop() + G.add_edges_from([(n1,n2),(n1,n3),(n2,n3)]) + G.name = "random_clustered %d nodes %d edges"%(G.order(),G.size()) + return G + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/random_graphs.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/random_graphs.py new file mode 100644 index 0000000..81c20ab --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/random_graphs.py @@ -0,0 +1,890 @@ +# -*- coding: utf-8 -*- +""" +Generators for random graphs. + +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult (dschult@colgate.edu)']) +import itertools +import random +import math +import networkx as nx +from networkx.generators.classic import empty_graph, path_graph, complete_graph + +from collections import defaultdict + +__all__ = ['fast_gnp_random_graph', + 'gnp_random_graph', + 'dense_gnm_random_graph', + 'gnm_random_graph', + 'erdos_renyi_graph', + 'binomial_graph', + 'newman_watts_strogatz_graph', + 'watts_strogatz_graph', + 'connected_watts_strogatz_graph', + 'random_regular_graph', + 'barabasi_albert_graph', + 'powerlaw_cluster_graph', + 'random_lobster', + 'random_shell_graph', + 'random_powerlaw_tree', + 'random_powerlaw_tree_sequence'] + + +#------------------------------------------------------------------------- +# Some Famous Random Graphs +#------------------------------------------------------------------------- + + +def fast_gnp_random_graph(n, p, seed=None, directed=False): + """Return a random graph G_{n,p} (Erdős-Rényi graph, binomial graph). + + Parameters + ---------- + n : int + The number of nodes. + p : float + Probability for edge creation. + seed : int, optional + Seed for random number generator (default=None). + directed : bool, optional (default=False) + If True return a directed graph + + Notes + ----- + The G_{n,p} graph algorithm chooses each of the [n(n-1)]/2 + (undirected) or n(n-1) (directed) possible edges with probability p. + + This algorithm is O(n+m) where m is the expected number of + edges m=p*n*(n-1)/2. + + It should be faster than gnp_random_graph when p is small and + the expected number of edges is small (sparse graph). + + See Also + -------- + gnp_random_graph + + References + ---------- + .. [1] Vladimir Batagelj and Ulrik Brandes, + "Efficient generation of large random networks", + Phys. Rev. E, 71, 036113, 2005. + """ + G = empty_graph(n) + G.name="fast_gnp_random_graph(%s,%s)"%(n,p) + + if not seed is None: + random.seed(seed) + + if p <= 0 or p >= 1: + return nx.gnp_random_graph(n,p,directed=directed) + + v = 1 # Nodes in graph are from 0,n-1 (this is the second node index). + w = -1 + lp = math.log(1.0 - p) + + if directed: + G=nx.DiGraph(G) + while v < n: + lr = math.log(1.0 - random.random()) + w = w + 1 + int(lr/lp) + if v == w: # avoid self loops + w = w + 1 + while w >= n and v < n: + w = w - n + v = v + 1 + if v == w: # avoid self loops + w = w + 1 + if v < n: + G.add_edge(v, w) + else: + while v < n: + lr = math.log(1.0 - random.random()) + w = w + 1 + int(lr/lp) + while w >= v and v < n: + w = w - v + v = v + 1 + if v < n: + G.add_edge(v, w) + return G + + +def gnp_random_graph(n, p, seed=None, directed=False): + """Return a random graph G_{n,p} (Erdős-Rényi graph, binomial graph). + + Chooses each of the possible edges with probability p. + + This is also called binomial_graph and erdos_renyi_graph. + + Parameters + ---------- + n : int + The number of nodes. + p : float + Probability for edge creation. + seed : int, optional + Seed for random number generator (default=None). + directed : bool, optional (default=False) + If True return a directed graph + + See Also + -------- + fast_gnp_random_graph + + Notes + ----- + This is an O(n^2) algorithm. For sparse graphs (small p) see + fast_gnp_random_graph for a faster algorithm. + + References + ---------- + .. [1] P. Erdős and A. Rényi, On Random Graphs, Publ. Math. 6, 290 (1959). + .. [2] E. N. Gilbert, Random Graphs, Ann. Math. Stat., 30, 1141 (1959). + """ + if directed: + G=nx.DiGraph() + else: + G=nx.Graph() + G.add_nodes_from(range(n)) + G.name="gnp_random_graph(%s,%s)"%(n,p) + if p<=0: + return G + if p>=1: + return complete_graph(n,create_using=G) + + if not seed is None: + random.seed(seed) + + if G.is_directed(): + edges=itertools.permutations(range(n),2) + else: + edges=itertools.combinations(range(n),2) + + for e in edges: + if random.random() < p: + G.add_edge(*e) + return G + + +# add some aliases to common names +binomial_graph=gnp_random_graph +erdos_renyi_graph=gnp_random_graph + +def dense_gnm_random_graph(n, m, seed=None): + """Return the random graph G_{n,m}. + + Gives a graph picked randomly out of the set of all graphs + with n nodes and m edges. + This algorithm should be faster than gnm_random_graph for dense graphs. + + Parameters + ---------- + n : int + The number of nodes. + m : int + The number of edges. + seed : int, optional + Seed for random number generator (default=None). + + See Also + -------- + gnm_random_graph() + + Notes + ----- + Algorithm by Keith M. Briggs Mar 31, 2006. + Inspired by Knuth's Algorithm S (Selection sampling technique), + in section 3.4.2 of [1]_. + + References + ---------- + .. [1] Donald E. Knuth, The Art of Computer Programming, + Volume 2/Seminumerical algorithms, Third Edition, Addison-Wesley, 1997. + """ + mmax=n*(n-1)/2 + if m>=mmax: + G=complete_graph(n) + else: + G=empty_graph(n) + G.name="dense_gnm_random_graph(%s,%s)"%(n,m) + + if n==1 or m>=mmax: + return G + + if seed is not None: + random.seed(seed) + + u=0 + v=1 + t=0 + k=0 + while True: + if random.randrange(mmax-t)=max_edges: + return complete_graph(n,create_using=G) + + nlist=G.nodes() + edge_count=0 + while edge_count < m: + # generate random edge,u,v + u = random.choice(nlist) + v = random.choice(nlist) + if u==v or G.has_edge(u,v): + continue + else: + G.add_edge(u,v) + edge_count=edge_count+1 + return G + + +def newman_watts_strogatz_graph(n, k, p, seed=None): + """Return a Newman-Watts-Strogatz small world graph. + + Parameters + ---------- + n : int + The number of nodes + k : int + Each node is connected to k nearest neighbors in ring topology + p : float + The probability of adding a new edge for each edge + seed : int, optional + seed for random number generator (default=None) + + Notes + ----- + First create a ring over n nodes. Then each node in the ring is + connected with its k nearest neighbors (k-1 neighbors if k is odd). + Then shortcuts are created by adding new edges as follows: + for each edge u-v in the underlying "n-ring with k nearest neighbors" + with probability p add a new edge u-w with randomly-chosen existing + node w. In contrast with watts_strogatz_graph(), no edges are removed. + + See Also + -------- + watts_strogatz_graph() + + References + ---------- + .. [1] M. E. J. Newman and D. J. Watts, + Renormalization group analysis of the small-world network model, + Physics Letters A, 263, 341, 1999. + http://dx.doi.org/10.1016/S0375-9601(99)00757-4 + """ + if seed is not None: + random.seed(seed) + if k>=n: + raise nx.NetworkXError("k>=n, choose smaller k or larger n") + G=empty_graph(n) + G.name="newman_watts_strogatz_graph(%s,%s,%s)"%(n,k,p) + nlist = G.nodes() + fromv = nlist + # connect the k/2 neighbors + for j in range(1, k // 2+1): + tov = fromv[j:] + fromv[0:j] # the first j are now last + for i in range(len(fromv)): + G.add_edge(fromv[i], tov[i]) + # for each edge u-v, with probability p, randomly select existing + # node w and add new edge u-w + e = G.edges() + for (u, v) in e: + if random.random() < p: + w = random.choice(nlist) + # no self-loops and reject if edge u-w exists + # is that the correct NWS model? + while w == u or G.has_edge(u, w): + w = random.choice(nlist) + if G.degree(u) >= n-1: + break # skip this rewiring + else: + G.add_edge(u,w) + return G + + +def watts_strogatz_graph(n, k, p, seed=None): + """Return a Watts-Strogatz small-world graph. + + + Parameters + ---------- + n : int + The number of nodes + k : int + Each node is connected to k nearest neighbors in ring topology + p : float + The probability of rewiring each edge + seed : int, optional + Seed for random number generator (default=None) + + See Also + -------- + newman_watts_strogatz_graph() + connected_watts_strogatz_graph() + + Notes + ----- + First create a ring over n nodes. Then each node in the ring is + connected with its k nearest neighbors (k-1 neighbors if k is odd). + Then shortcuts are created by replacing some edges as follows: + for each edge u-v in the underlying "n-ring with k nearest neighbors" + with probability p replace it with a new edge u-w with uniformly + random choice of existing node w. + + In contrast with newman_watts_strogatz_graph(), the random + rewiring does not increase the number of edges. The rewired graph + is not guaranteed to be connected as in connected_watts_strogatz_graph(). + + References + ---------- + .. [1] Duncan J. Watts and Steven H. Strogatz, + Collective dynamics of small-world networks, + Nature, 393, pp. 440--442, 1998. + """ + if k>=n: + raise nx.NetworkXError("k>=n, choose smaller k or larger n") + if seed is not None: + random.seed(seed) + + G = nx.Graph() + G.name="watts_strogatz_graph(%s,%s,%s)"%(n,k,p) + nodes = list(range(n)) # nodes are labeled 0 to n-1 + # connect each node to k/2 neighbors + for j in range(1, k // 2+1): + targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list + G.add_edges_from(zip(nodes,targets)) + # rewire edges from each node + # loop over all nodes in order (label) and neighbors in order (distance) + # no self loops or multiple edges allowed + for j in range(1, k // 2+1): # outer loop is neighbors + targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list + # inner loop in node order + for u,v in zip(nodes,targets): + if random.random() < p: + w = random.choice(nodes) + # Enforce no self-loops or multiple edges + while w == u or G.has_edge(u, w): + w = random.choice(nodes) + if G.degree(u) >= n-1: + break # skip this rewiring + else: + G.remove_edge(u,v) + G.add_edge(u,w) + return G + +def connected_watts_strogatz_graph(n, k, p, tries=100, seed=None): + """Return a connected Watts-Strogatz small-world graph. + + Attempt to generate a connected realization by repeated + generation of Watts-Strogatz small-world graphs. + An exception is raised if the maximum number of tries is exceeded. + + Parameters + ---------- + n : int + The number of nodes + k : int + Each node is connected to k nearest neighbors in ring topology + p : float + The probability of rewiring each edge + tries : int + Number of attempts to generate a connected graph. + seed : int, optional + The seed for random number generator. + + See Also + -------- + newman_watts_strogatz_graph() + watts_strogatz_graph() + + """ + G = watts_strogatz_graph(n, k, p, seed) + t=1 + while not nx.is_connected(G): + G = watts_strogatz_graph(n, k, p, seed) + t=t+1 + if t>tries: + raise nx.NetworkXError("Maximum number of tries exceeded") + return G + + +def random_regular_graph(d, n, seed=None): + """Return a random regular graph of n nodes each with degree d. + + The resulting graph G has no self-loops or parallel edges. + + Parameters + ---------- + d : int + Degree + n : integer + Number of nodes. The value of n*d must be even. + seed : hashable object + The seed for random number generator. + + Notes + ----- + The nodes are numbered form 0 to n-1. + + Kim and Vu's paper [2]_ shows that this algorithm samples in an + asymptotically uniform way from the space of random graphs when + d = O(n**(1/3-epsilon)). + + References + ---------- + .. [1] A. Steger and N. Wormald, + Generating random regular graphs quickly, + Probability and Computing 8 (1999), 377-396, 1999. + http://citeseer.ist.psu.edu/steger99generating.html + + .. [2] Jeong Han Kim and Van H. Vu, + Generating random regular graphs, + Proceedings of the thirty-fifth ACM symposium on Theory of computing, + San Diego, CA, USA, pp 213--222, 2003. + http://portal.acm.org/citation.cfm?id=780542.780576 + """ + if (n * d) % 2 != 0: + raise nx.NetworkXError("n * d must be even") + + if not 0 <= d < n: + raise nx.NetworkXError("the 0 <= d < n inequality must be satisfied") + + if seed is not None: + random.seed(seed) + + def _suitable(edges, potential_edges): + # Helper subroutine to check if there are suitable edges remaining + # If False, the generation of the graph has failed + if not potential_edges: + return True + for s1 in potential_edges: + for s2 in potential_edges: + # Two iterators on the same dictionary are guaranteed + # to visit it in the same order if there are no + # intervening modifications. + if s1 == s2: + # Only need to consider s1-s2 pair one time + break + if s1 > s2: + s1, s2 = s2, s1 + if (s1, s2) not in edges: + return True + return False + + def _try_creation(): + # Attempt to create an edge set + + edges = set() + stubs = list(range(n)) * d + + while stubs: + potential_edges = defaultdict(lambda: 0) + random.shuffle(stubs) + stubiter = iter(stubs) + for s1, s2 in zip(stubiter, stubiter): + if s1 > s2: + s1, s2 = s2, s1 + if s1 != s2 and ((s1, s2) not in edges): + edges.add((s1, s2)) + else: + potential_edges[s1] += 1 + potential_edges[s2] += 1 + + if not _suitable(edges, potential_edges): + return None # failed to find suitable edge set + + stubs = [node for node, potential in potential_edges.items() + for _ in range(potential)] + return edges + + # Even though a suitable edge set exists, + # the generation of such a set is not guaranteed. + # Try repeatedly to find one. + edges = _try_creation() + while edges is None: + edges = _try_creation() + + G = nx.Graph() + G.name = "random_regular_graph(%s, %s)" % (d, n) + G.add_edges_from(edges) + + return G + +def _random_subset(seq,m): + """ Return m unique elements from seq. + + This differs from random.sample which can return repeated + elements if seq holds repeated elements. + """ + targets=set() + while len(targets)=n: + raise nx.NetworkXError(\ + "Barabási-Albert network must have m>=1 and m1 and m 1 or p < 0: + raise nx.NetworkXError(\ + "NetworkXError p must be in [0,1], p=%f"%(p)) + if seed is not None: + random.seed(seed) + + G=empty_graph(m) # add m initial nodes (m0 in barabasi-speak) + G.name="Powerlaw-Cluster Graph" + repeated_nodes=G.nodes() # list of existing nodes to sample from + # with nodes repeated once for each adjacent edge + source=m # next node is m + while source>> constructor=[(10,20,0.8),(20,40,0.8)] + >>> G=nx.random_shell_graph(constructor) + + """ + G=empty_graph(0) + G.name="random_shell_graph(constructor)" + + if seed is not None: + random.seed(seed) + + glist=[] + intra_edges=[] + nnodes=0 + # create gnm graphs for each shell + for (n,m,d) in constructor: + inter_edges=int(m*d) + intra_edges.append(m-inter_edges) + g=nx.convert_node_labels_to_integers( + gnm_random_graph(n,inter_edges), + first_label=nnodes) + glist.append(g) + nnodes+=n + G=nx.operators.union(G,g) + + # connect the shells randomly + for gi in range(len(glist)-1): + nlist1=glist[gi].nodes() + nlist2=glist[gi+1].nodes() + total_edges=intra_edges[gi] + edge_count=0 + while edge_count < total_edges: + u = random.choice(nlist1) + v = random.choice(nlist2) + if u==v or G.has_edge(u,v): + continue + else: + G.add_edge(u,v) + edge_count=edge_count+1 + return G + + +def random_powerlaw_tree(n, gamma=3, seed=None, tries=100): + """Return a tree with a powerlaw degree distribution. + + Parameters + ---------- + n : int, + The number of nodes + gamma : float + Exponent of the power-law + seed : int, optional + Seed for random number generator (default=None). + tries : int + Number of attempts to adjust sequence to make a tree + + Notes + ----- + A trial powerlaw degree sequence is chosen and then elements are + swapped with new elements from a powerlaw distribution until + the sequence makes a tree (#edges=#nodes-1). + + """ + from networkx.generators.degree_seq import degree_sequence_tree + try: + s=random_powerlaw_tree_sequence(n, + gamma=gamma, + seed=seed, + tries=tries) + except: + raise nx.NetworkXError(\ + "Exceeded max (%d) attempts for a valid tree sequence."%tries) + G=degree_sequence_tree(s) + G.name="random_powerlaw_tree(%s,%s)"%(n,gamma) + return G + + +def random_powerlaw_tree_sequence(n, gamma=3, seed=None, tries=100): + """ Return a degree sequence for a tree with a powerlaw distribution. + + Parameters + ---------- + n : int, + The number of nodes + gamma : float + Exponent of the power-law + seed : int, optional + Seed for random number generator (default=None). + tries : int + Number of attempts to adjust sequence to make a tree + + Notes + ----- + A trial powerlaw degree sequence is chosen and then elements are + swapped with new elements from a powerlaw distribution until + the sequence makes a tree (#edges=#nodes-1). + + + """ + if seed is not None: + random.seed(seed) + + # get trial sequence + z=nx.utils.powerlaw_sequence(n,exponent=gamma) + # round to integer values in the range [0,n] + zseq=[min(n, max( int(round(s)),0 )) for s in z] + + # another sequence to swap values from + z=nx.utils.powerlaw_sequence(tries,exponent=gamma) + # round to integer values in the range [0,n] + swap=[min(n, max( int(round(s)),0 )) for s in z] + + for deg in swap: + if n-sum(zseq)/2.0 == 1.0: # is a tree, return sequence + return zseq + index=random.randint(0,n-1) + zseq[index]=swap.pop() + + raise nx.NetworkXError(\ + "Exceeded max (%d) attempts for a valid tree sequence."%tries) + return False + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/small.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/small.py new file mode 100644 index 0000000..f41f8d0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/small.py @@ -0,0 +1,412 @@ +# -*- coding: utf-8 -*- +""" +Various small and named graphs, together with some compact generators. + +""" +__author__ ="""Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['make_small_graph', + 'LCF_graph', + 'bull_graph', + 'chvatal_graph', + 'cubical_graph', + 'desargues_graph', + 'diamond_graph', + 'dodecahedral_graph', + 'frucht_graph', + 'heawood_graph', + 'house_graph', + 'house_x_graph', + 'icosahedral_graph', + 'krackhardt_kite_graph', + 'moebius_kantor_graph', + 'octahedral_graph', + 'pappus_graph', + 'petersen_graph', + 'sedgewick_maze_graph', + 'tetrahedral_graph', + 'truncated_cube_graph', + 'truncated_tetrahedron_graph', + 'tutte_graph'] + +import networkx as nx +from networkx.generators.classic import empty_graph, cycle_graph, path_graph, complete_graph +from networkx.exception import NetworkXError + +#------------------------------------------------------------------------------ +# Tools for creating small graphs +#------------------------------------------------------------------------------ +def make_small_undirected_graph(graph_description, create_using=None): + """ + Return a small undirected graph described by graph_description. + + See make_small_graph. + """ + if create_using is not None and create_using.is_directed(): + raise NetworkXError("Directed Graph not supported") + return make_small_graph(graph_description, create_using) + +def make_small_graph(graph_description, create_using=None): + """ + Return the small graph described by graph_description. + + graph_description is a list of the form [ltype,name,n,xlist] + + Here ltype is one of "adjacencylist" or "edgelist", + name is the name of the graph and n the number of nodes. + This constructs a graph of n nodes with integer labels 0,..,n-1. + + If ltype="adjacencylist" then xlist is an adjacency list + with exactly n entries, in with the j'th entry (which can be empty) + specifies the nodes connected to vertex j. + e.g. the "square" graph C_4 can be obtained by + + >>> G=nx.make_small_graph(["adjacencylist","C_4",4,[[2,4],[1,3],[2,4],[1,3]]]) + + or, since we do not need to add edges twice, + + >>> G=nx.make_small_graph(["adjacencylist","C_4",4,[[2,4],[3],[4],[]]]) + + If ltype="edgelist" then xlist is an edge list + written as [[v1,w2],[v2,w2],...,[vk,wk]], + where vj and wj integers in the range 1,..,n + e.g. the "square" graph C_4 can be obtained by + + >>> G=nx.make_small_graph(["edgelist","C_4",4,[[1,2],[3,4],[2,3],[4,1]]]) + + Use the create_using argument to choose the graph class/type. + """ + ltype=graph_description[0] + name=graph_description[1] + n=graph_description[2] + + G=empty_graph(n, create_using) + nodes=G.nodes() + + if ltype=="adjacencylist": + adjlist=graph_description[3] + if len(adjlist) != n: + raise NetworkXError("invalid graph_description") + G.add_edges_from([(u-1,v) for v in nodes for u in adjlist[v]]) + elif ltype=="edgelist": + edgelist=graph_description[3] + for e in edgelist: + v1=e[0]-1 + v2=e[1]-1 + if v1<0 or v1>n-1 or v2<0 or v2>n-1: + raise NetworkXError("invalid graph_description") + else: + G.add_edge(v1,v2) + G.name=name + return G + + +def LCF_graph(n,shift_list,repeats,create_using=None): + """ + Return the cubic graph specified in LCF notation. + + LCF notation (LCF=Lederberg-Coxeter-Fruchte) is a compressed + notation used in the generation of various cubic Hamiltonian + graphs of high symmetry. See, for example, dodecahedral_graph, + desargues_graph, heawood_graph and pappus_graph below. + + n (number of nodes) + The starting graph is the n-cycle with nodes 0,...,n-1. + (The null graph is returned if n < 0.) + + shift_list = [s1,s2,..,sk], a list of integer shifts mod n, + + repeats + integer specifying the number of times that shifts in shift_list + are successively applied to each v_current in the n-cycle + to generate an edge between v_current and v_current+shift mod n. + + For v1 cycling through the n-cycle a total of k*repeats + with shift cycling through shiftlist repeats times connect + v1 with v1+shift mod n + + The utility graph K_{3,3} + + >>> G=nx.LCF_graph(6,[3,-3],3) + + The Heawood graph + + >>> G=nx.LCF_graph(14,[5,-5],7) + + See http://mathworld.wolfram.com/LCFNotation.html for a description + and references. + + """ + if create_using is not None and create_using.is_directed(): + raise NetworkXError("Directed Graph not supported") + + if n <= 0: + return empty_graph(0, create_using) + + # start with the n-cycle + G=cycle_graph(n, create_using) + G.name="LCF_graph" + nodes=G.nodes() + + n_extra_edges=repeats*len(shift_list) + # edges are added n_extra_edges times + # (not all of these need be new) + if n_extra_edges < 1: + return G + + for i in range(n_extra_edges): + shift=shift_list[i%len(shift_list)] #cycle through shift_list + v1=nodes[i%n] # cycle repeatedly through nodes + v2=nodes[(i + shift)%n] + G.add_edge(v1, v2) + return G + + +#------------------------------------------------------------------------------- +# Various small and named graphs +#------------------------------------------------------------------------------- + +def bull_graph(create_using=None): + """Return the Bull graph. """ + description=[ + "adjacencylist", + "Bull Graph", + 5, + [[2,3],[1,3,4],[1,2,5],[2],[3]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def chvatal_graph(create_using=None): + """Return the Chvátal graph.""" + description=[ + "adjacencylist", + "Chvatal Graph", + 12, + [[2,5,7,10],[3,6,8],[4,7,9],[5,8,10], + [6,9],[11,12],[11,12],[9,12], + [11],[11,12],[],[]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def cubical_graph(create_using=None): + """Return the 3-regular Platonic Cubical graph.""" + description=[ + "adjacencylist", + "Platonic Cubical Graph", + 8, + [[2,4,5],[1,3,8],[2,4,7],[1,3,6], + [1,6,8],[4,5,7],[3,6,8],[2,5,7]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def desargues_graph(create_using=None): + """ Return the Desargues graph.""" + G=LCF_graph(20, [5,-5,9,-9], 5, create_using) + G.name="Desargues Graph" + return G + +def diamond_graph(create_using=None): + """Return the Diamond graph. """ + description=[ + "adjacencylist", + "Diamond Graph", + 4, + [[2,3],[1,3,4],[1,2,4],[2,3]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def dodecahedral_graph(create_using=None): + """ Return the Platonic Dodecahedral graph. """ + G=LCF_graph(20, [10,7,4,-4,-7,10,-4,7,-7,4], 2, create_using) + G.name="Dodecahedral Graph" + return G + +def frucht_graph(create_using=None): + """Return the Frucht Graph. + + The Frucht Graph is the smallest cubical graph whose + automorphism group consists only of the identity element. + + """ + G=cycle_graph(7, create_using) + G.add_edges_from([[0,7],[1,7],[2,8],[3,9],[4,9],[5,10],[6,10], + [7,11],[8,11],[8,9],[10,11]]) + + G.name="Frucht Graph" + return G + +def heawood_graph(create_using=None): + """ Return the Heawood graph, a (3,6) cage. """ + G=LCF_graph(14, [5,-5], 7, create_using) + G.name="Heawood Graph" + return G + +def house_graph(create_using=None): + """Return the House graph (square with triangle on top).""" + description=[ + "adjacencylist", + "House Graph", + 5, + [[2,3],[1,4],[1,4,5],[2,3,5],[3,4]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def house_x_graph(create_using=None): + """Return the House graph with a cross inside the house square.""" + description=[ + "adjacencylist", + "House-with-X-inside Graph", + 5, + [[2,3,4],[1,3,4],[1,2,4,5],[1,2,3,5],[3,4]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def icosahedral_graph(create_using=None): + """Return the Platonic Icosahedral graph.""" + description=[ + "adjacencylist", + "Platonic Icosahedral Graph", + 12, + [[2,6,8,9,12],[3,6,7,9],[4,7,9,10],[5,7,10,11], + [6,7,11,12],[7,12],[],[9,10,11,12], + [10],[11],[12],[]] + ] + G=make_small_undirected_graph(description, create_using) + return G + + +def krackhardt_kite_graph(create_using=None): + """ + Return the Krackhardt Kite Social Network. + + A 10 actor social network introduced by David Krackhardt + to illustrate: degree, betweenness, centrality, closeness, etc. + The traditional labeling is: + Andre=1, Beverley=2, Carol=3, Diane=4, + Ed=5, Fernando=6, Garth=7, Heather=8, Ike=9, Jane=10. + + """ + description=[ + "adjacencylist", + "Krackhardt Kite Social Network", + 10, + [[2,3,4,6],[1,4,5,7],[1,4,6],[1,2,3,5,6,7],[2,4,7], + [1,3,4,7,8],[2,4,5,6,8],[6,7,9],[8,10],[9]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def moebius_kantor_graph(create_using=None): + """Return the Moebius-Kantor graph.""" + G=LCF_graph(16, [5,-5], 8, create_using) + G.name="Moebius-Kantor Graph" + return G + +def octahedral_graph(create_using=None): + """Return the Platonic Octahedral graph.""" + description=[ + "adjacencylist", + "Platonic Octahedral Graph", + 6, + [[2,3,4,5],[3,4,6],[5,6],[5,6],[6],[]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def pappus_graph(): + """ Return the Pappus graph.""" + G=LCF_graph(18,[5,7,-7,7,-7,-5],3) + G.name="Pappus Graph" + return G + +def petersen_graph(create_using=None): + """Return the Petersen graph.""" + description=[ + "adjacencylist", + "Petersen Graph", + 10, + [[2,5,6],[1,3,7],[2,4,8],[3,5,9],[4,1,10],[1,8,9],[2,9,10], + [3,6,10],[4,6,7],[5,7,8]] + ] + G=make_small_undirected_graph(description, create_using) + return G + + +def sedgewick_maze_graph(create_using=None): + """ + Return a small maze with a cycle. + + This is the maze used in Sedgewick,3rd Edition, Part 5, Graph + Algorithms, Chapter 18, e.g. Figure 18.2 and following. + Nodes are numbered 0,..,7 + """ + G=empty_graph(0, create_using) + G.add_nodes_from(range(8)) + G.add_edges_from([[0,2],[0,7],[0,5]]) + G.add_edges_from([[1,7],[2,6]]) + G.add_edges_from([[3,4],[3,5]]) + G.add_edges_from([[4,5],[4,7],[4,6]]) + G.name="Sedgewick Maze" + return G + +def tetrahedral_graph(create_using=None): + """ Return the 3-regular Platonic Tetrahedral graph.""" + G=complete_graph(4, create_using) + G.name="Platonic Tetrahedral graph" + return G + +def truncated_cube_graph(create_using=None): + """Return the skeleton of the truncated cube.""" + description=[ + "adjacencylist", + "Truncated Cube Graph", + 24, + [[2,3,5],[12,15],[4,5],[7,9], + [6],[17,19],[8,9],[11,13], + [10],[18,21],[12,13],[15], + [14],[22,23],[16],[20,24], + [18,19],[21],[20],[24], + [22],[23],[24],[]] + ] + G=make_small_undirected_graph(description, create_using) + return G + +def truncated_tetrahedron_graph(create_using=None): + """Return the skeleton of the truncated Platonic tetrahedron.""" + G=path_graph(12, create_using) +# G.add_edges_from([(1,3),(1,10),(2,7),(4,12),(5,12),(6,8),(9,11)]) + G.add_edges_from([(0,2),(0,9),(1,6),(3,11),(4,11),(5,7),(8,10)]) + G.name="Truncated Tetrahedron Graph" + return G + +def tutte_graph(create_using=None): + """Return the Tutte graph.""" + description=[ + "adjacencylist", + "Tutte's Graph", + 46, + [[2,3,4],[5,27],[11,12],[19,20],[6,34], + [7,30],[8,28],[9,15],[10,39],[11,38], + [40],[13,40],[14,36],[15,16],[35], + [17,23],[18,45],[19,44],[46],[21,46], + [22,42],[23,24],[41],[25,28],[26,33], + [27,32],[34],[29],[30,33],[31], + [32,34],[33],[],[],[36,39], + [37],[38,40],[39],[],[], + [42,45],[43],[44,46],[45],[],[]] + ] + G=make_small_undirected_graph(description, create_using) + return G + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/social.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/social.py new file mode 100644 index 0000000..212dd9c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/social.py @@ -0,0 +1,280 @@ +""" +Famous social networks. +""" +import networkx as nx +__author__ = """\n""".join(['Jordi Torrents ', + 'Katy Bold ', + 'Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = "Aric Hagberg " +__all__ = ['stochastic_graph'] + +def stochastic_graph(G, copy=True, weight='weight'): + """Return a right-stochastic representation of G. + + A right-stochastic graph is a weighted digraph in which all of + the node (out) neighbors edge weights sum to 1. + + Parameters + ----------- + G : graph + A NetworkX graph + + copy : boolean, optional + If True make a copy of the graph, otherwise modify the original graph + + weight : edge attribute key (optional, default='weight') + Edge data key used for weight. If no attribute is found for an edge + the edge weight is set to 1. + """ + if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph: + raise nx.NetworkXError('stochastic_graph not implemented ' + 'for multigraphs') + + if not G.is_directed(): + raise nx.NetworkXError('stochastic_graph not implemented ' + 'for undirected graphs') + + if copy: + W = nx.DiGraph(G) + else: + W = G # reference original graph, no copy + + degree = W.out_degree(weight=weight) + for (u,v,d) in W.edges(data=True): + d[weight] = float(d.get(weight,1.0))/degree[u] + return W diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_atlas.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_atlas.py new file mode 100644 index 0000000..2428385 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_atlas.py @@ -0,0 +1,55 @@ +from nose.tools import * +import networkx as nx + + +class TestAtlas(object): + def setUp(self): + self.GAG=nx.graph_atlas_g() + + def test_sizes(self): + G=self.GAG[0] + assert_equal(G.number_of_nodes(),0) + assert_equal(G.number_of_edges(),0) + + G=self.GAG[7] + assert_equal(G.number_of_nodes(),3) + assert_equal(G.number_of_edges(),3) + + def test_names(self): + i=0 + for g in self.GAG: + name=g.name + assert_equal(int(name[1:]),i) + i+=1 + + def test_monotone_nodes(self): + # check for monotone increasing number of nodes + previous=self.GAG[0] + for g in self.GAG: + assert_false(len(g)-len(previous) > 1) + previous=g.copy() + + def test_monotone_nodes(self): + # check for monotone increasing number of edges + # (for fixed number of nodes) + previous=self.GAG[0] + for g in self.GAG: + if len(g)==len(previous): + assert_false(g.size()-previous.size() > 1) + previous=g.copy() + + def test_monotone_degree_sequence(self): + # check for monotone increasing degree sequence + # (for fixed number f nodes and edges) + # note that 111223 < 112222 + previous=self.GAG[0] + for g in self.GAG: + if len(g)==0: + continue + if len(g)==len(previous) & g.size()==previous.size(): + deg_seq=sorted(g.degree().values()) + previous_deg_seq=sorted(previous.degree().values()) + assert_true(previous_deg_seq < deg_seq) + previous=g.copy() + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_bipartite.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_bipartite.py new file mode 100644 index 0000000..63d336e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_bipartite.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python + +from nose.tools import * +from networkx import * +from networkx.generators.bipartite import * + +"""Generators - Bipartite +---------------------- +""" + +class TestGeneratorsBipartite(): + def test_configuration_model(self): + aseq=[3,3,3,3] + bseq=[2,2,2,2,2] + assert_raises(networkx.exception.NetworkXError, + bipartite_configuration_model, aseq, bseq) + + aseq=[3,3,3,3] + bseq=[2,2,2,2,2,2] + G=bipartite_configuration_model(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,2,2,2] + bseq=[3,3,3,3] + G=bipartite_configuration_model(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,1,1,1] + bseq=[3,3,3] + G=bipartite_configuration_model(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [1, 1, 1, 2, 2, 2, 3, 3, 3]) + + GU=project(Graph(G),range(len(aseq))) + assert_equal(GU.number_of_nodes(), 6) + + GD=project(Graph(G),range(len(aseq),len(aseq)+len(bseq))) + assert_equal(GD.number_of_nodes(), 3) + + assert_raises(networkx.exception.NetworkXError, + bipartite_configuration_model, aseq, bseq, + create_using=DiGraph()) + + def test_havel_hakimi_graph(self): + aseq=[3,3,3,3] + bseq=[2,2,2,2,2] + assert_raises(networkx.exception.NetworkXError, + bipartite_havel_hakimi_graph, aseq, bseq) + + bseq=[2,2,2,2,2,2] + G=bipartite_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,2,2,2] + bseq=[3,3,3,3] + G=bipartite_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + GU=project(Graph(G),range(len(aseq))) + assert_equal(GU.number_of_nodes(), 6) + + GD=project(Graph(G),range(len(aseq),len(aseq)+len(bseq))) + assert_equal(GD.number_of_nodes(), 4) + assert_raises(networkx.exception.NetworkXError, + bipartite_havel_hakimi_graph, aseq, bseq, + create_using=DiGraph()) + + def test_reverse_havel_hakimi_graph(self): + aseq=[3,3,3,3] + bseq=[2,2,2,2,2] + assert_raises(networkx.exception.NetworkXError, + bipartite_reverse_havel_hakimi_graph, aseq, bseq) + + bseq=[2,2,2,2,2,2] + G=bipartite_reverse_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,2,2,2] + bseq=[3,3,3,3] + G=bipartite_reverse_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,1,1,1] + bseq=[3,3,3] + G=bipartite_reverse_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [1, 1, 1, 2, 2, 2, 3, 3, 3]) + + GU=project(Graph(G),range(len(aseq))) + assert_equal(GU.number_of_nodes(), 6) + + GD=project(Graph(G),range(len(aseq),len(aseq)+len(bseq))) + assert_equal(GD.number_of_nodes(), 3) + assert_raises(networkx.exception.NetworkXError, + bipartite_reverse_havel_hakimi_graph, aseq, bseq, + create_using=DiGraph()) + + def test_alternating_havel_hakimi_graph(self): + aseq=[3,3,3,3] + bseq=[2,2,2,2,2] + assert_raises(networkx.exception.NetworkXError, + bipartite_alternating_havel_hakimi_graph, aseq, bseq) + + bseq=[2,2,2,2,2,2] + G=bipartite_alternating_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,2,2,2] + bseq=[3,3,3,3] + G=bipartite_alternating_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]) + + aseq=[2,2,2,1,1,1] + bseq=[3,3,3] + G=bipartite_alternating_havel_hakimi_graph(aseq,bseq) + assert_equal(sorted(G.degree().values()), + [1, 1, 1, 2, 2, 2, 3, 3, 3]) + + GU=project(Graph(G),range(len(aseq))) + assert_equal(GU.number_of_nodes(), 6) + + GD=project(Graph(G),range(len(aseq),len(aseq)+len(bseq))) + assert_equal(GD.number_of_nodes(), 3) + + assert_raises(networkx.exception.NetworkXError, + bipartite_alternating_havel_hakimi_graph, aseq, bseq, + create_using=DiGraph()) + + def test_preferential_attachment(self): + aseq=[3,2,1,1] + G=bipartite_preferential_attachment_graph(aseq,0.5) + assert_raises(networkx.exception.NetworkXError, + bipartite_preferential_attachment_graph, aseq, 0.5, + create_using=DiGraph()) + + def test_bipartite_random_graph(self): + n=10 + m=20 + G=bipartite_random_graph(n,m,0.9) + assert_equal(len(G),30) + assert_true(is_bipartite(G)) + X,Y=nx.algorithms.bipartite.sets(G) + assert_equal(set(range(n)),X) + assert_equal(set(range(n,n+m)),Y) + + def test_directed_bipartite_random_graph(self): + n=10 + m=20 + G=bipartite_random_graph(n,m,0.9,directed=True) + assert_equal(len(G),30) + assert_true(is_bipartite(G)) + X,Y=nx.algorithms.bipartite.sets(G) + assert_equal(set(range(n)),X) + assert_equal(set(range(n,n+m)),Y) + + def test_bipartite_gnmk_random_graph(self): + n = 10 + m = 20 + edges = 100 + G = bipartite_gnmk_random_graph(n, m, edges) + assert_equal(len(G),30) + assert_true(is_bipartite(G)) + X,Y=nx.algorithms.bipartite.sets(G) + print(X) + assert_equal(set(range(n)),X) + assert_equal(set(range(n,n+m)),Y) + assert_equal(edges, len(G.edges())) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_classic.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_classic.py new file mode 100644 index 0000000..96ca367 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_classic.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python +""" +==================== +Generators - Classic +==================== + +Unit tests for various classic graph generators in generators/classic.py +""" +from nose.tools import * +from networkx import * +from networkx.algorithms.isomorphism.isomorph import graph_could_be_isomorphic +is_isomorphic=graph_could_be_isomorphic + +class TestGeneratorClassic(): + def test_balanced_tree(self): + # balanced_tree(r,h) is a tree with (r**(h+1)-1)/(r-1) edges + for r,h in [(2,2),(3,3),(6,2)]: + t=balanced_tree(r,h) + order=t.order() + assert_true(order==(r**(h+1)-1)/(r-1)) + assert_true(is_connected(t)) + assert_true(t.size()==order-1) + dh = degree_histogram(t) + assert_equal(dh[0],0) # no nodes of 0 + assert_equal(dh[1],r**h) # nodes of degree 1 are leaves + assert_equal(dh[r],1) # root is degree r + assert_equal(dh[r+1],order-r**h-1)# everyone else is degree r+1 + assert_equal(len(dh),r+2) + + def test_balanced_tree_star(self): + # balanced_tree(r,1) is the r-star + t=balanced_tree(r=2,h=1) + assert_true(is_isomorphic(t,star_graph(2))) + t=balanced_tree(r=5,h=1) + assert_true(is_isomorphic(t,star_graph(5))) + t=balanced_tree(r=10,h=1) + assert_true(is_isomorphic(t,star_graph(10))) + + def test_full_rary_tree(self): + r=2 + n=9 + t=full_rary_tree(r,n) + assert_equal(t.order(),n) + assert_true(is_connected(t)) + dh = degree_histogram(t) + assert_equal(dh[0],0) # no nodes of 0 + assert_equal(dh[1],5) # nodes of degree 1 are leaves + assert_equal(dh[r],1) # root is degree r + assert_equal(dh[r+1],9-5-1) # everyone else is degree r+1 + assert_equal(len(dh),r+2) + + def test_full_rary_tree_balanced(self): + t=full_rary_tree(2,15) + th=balanced_tree(2,3) + assert_true(is_isomorphic(t,th)) + + def test_full_rary_tree_path(self): + t=full_rary_tree(1,10) + assert_true(is_isomorphic(t,path_graph(10))) + + def test_full_rary_tree_empty(self): + t=full_rary_tree(0,10) + assert_true(is_isomorphic(t,empty_graph(10))) + t=full_rary_tree(3,0) + assert_true(is_isomorphic(t,empty_graph(0))) + + def test_full_rary_tree_3_20(self): + t=full_rary_tree(3,20) + assert_equal(t.order(),20) + + def test_barbell_graph(self): + # number of nodes = 2*m1 + m2 (2 m1-complete graphs + m2-path + 2 edges) + # number of edges = 2*(number_of_edges(m1-complete graph) + m2 + 1 + m1=3; m2=5 + b=barbell_graph(m1,m2) + assert_true(number_of_nodes(b)==2*m1+m2) + assert_true(number_of_edges(b)==m1*(m1-1) + m2 + 1) + assert_equal(b.name, 'barbell_graph(3,5)') + + m1=4; m2=10 + b=barbell_graph(m1,m2) + assert_true(number_of_nodes(b)==2*m1+m2) + assert_true(number_of_edges(b)==m1*(m1-1) + m2 + 1) + assert_equal(b.name, 'barbell_graph(4,10)') + + m1=3; m2=20 + b=barbell_graph(m1,m2) + assert_true(number_of_nodes(b)==2*m1+m2) + assert_true(number_of_edges(b)==m1*(m1-1) + m2 + 1) + assert_equal(b.name, 'barbell_graph(3,20)') + + # Raise NetworkXError if m1<2 + m1=1; m2=20 + assert_raises(networkx.exception.NetworkXError, barbell_graph, m1, m2) + + # Raise NetworkXError if m2<0 + m1=5; m2=-2 + assert_raises(networkx.exception.NetworkXError, barbell_graph, m1, m2) + + # barbell_graph(2,m) = path_graph(m+4) + m1=2; m2=5 + b=barbell_graph(m1,m2) + assert_true(is_isomorphic(b, path_graph(m2+4))) + + m1=2; m2=10 + b=barbell_graph(m1,m2) + assert_true(is_isomorphic(b, path_graph(m2+4))) + + m1=2; m2=20 + b=barbell_graph(m1,m2) + assert_true(is_isomorphic(b, path_graph(m2+4))) + + assert_raises(networkx.exception.NetworkXError, barbell_graph, m1, m2, + create_using=DiGraph()) + + mb=barbell_graph(m1, m2, create_using=MultiGraph()) + assert_true(mb.edges()==b.edges()) + + def test_complete_graph(self): + # complete_graph(m) is a connected graph with + # m nodes and m*(m+1)/2 edges + for m in [0, 1, 3, 5]: + g = complete_graph(m) + assert_true(number_of_nodes(g) == m) + assert_true(number_of_edges(g) == m * (m - 1) // 2) + + + mg=complete_graph(m, create_using=MultiGraph()) + assert_true(mg.edges()==g.edges()) + + def test_complete_digraph(self): + # complete_graph(m) is a connected graph with + # m nodes and m*(m+1)/2 edges + for m in [0, 1, 3, 5]: + g = complete_graph(m,create_using=nx.DiGraph()) + assert_true(number_of_nodes(g) == m) + assert_true(number_of_edges(g) == m * (m - 1)) + + def test_complete_bipartite_graph(self): + G=complete_bipartite_graph(0,0) + assert_true(is_isomorphic( G, null_graph() )) + + for i in [1, 5]: + G=complete_bipartite_graph(i,0) + assert_true(is_isomorphic( G, empty_graph(i) )) + G=complete_bipartite_graph(0,i) + assert_true(is_isomorphic( G, empty_graph(i) )) + + G=complete_bipartite_graph(2,2) + assert_true(is_isomorphic( G, cycle_graph(4) )) + + G=complete_bipartite_graph(1,5) + assert_true(is_isomorphic( G, star_graph(5) )) + + G=complete_bipartite_graph(5,1) + assert_true(is_isomorphic( G, star_graph(5) )) + + # complete_bipartite_graph(m1,m2) is a connected graph with + # m1+m2 nodes and m1*m2 edges + for m1, m2 in [(5, 11), (7, 3)]: + G=complete_bipartite_graph(m1,m2) + assert_equal(number_of_nodes(G), m1 + m2) + assert_equal(number_of_edges(G), m1 * m2) + + assert_raises(networkx.exception.NetworkXError, + complete_bipartite_graph, 7, 3, create_using=DiGraph()) + + mG=complete_bipartite_graph(7, 3, create_using=MultiGraph()) + assert_equal(mG.edges(), G.edges()) + + def test_circular_ladder_graph(self): + G=circular_ladder_graph(5) + assert_raises(networkx.exception.NetworkXError, circular_ladder_graph, + 5, create_using=DiGraph()) + mG=circular_ladder_graph(5, create_using=MultiGraph()) + assert_equal(mG.edges(), G.edges()) + + def test_cycle_graph(self): + G=cycle_graph(4) + assert_equal(sorted(G.edges()), [(0, 1), (0, 3), (1, 2), (2, 3)]) + mG=cycle_graph(4, create_using=MultiGraph()) + assert_equal(sorted(mG.edges()), [(0, 1), (0, 3), (1, 2), (2, 3)]) + G=cycle_graph(4, create_using=DiGraph()) + assert_false(G.has_edge(2,1)) + assert_true(G.has_edge(1,2)) + + def test_dorogovtsev_goltsev_mendes_graph(self): + G=dorogovtsev_goltsev_mendes_graph(0) + assert_equal(G.edges(), [(0, 1)]) + assert_equal(G.nodes(), [0, 1]) + G=dorogovtsev_goltsev_mendes_graph(1) + assert_equal(G.edges(), [(0, 1), (0, 2), (1, 2)]) + assert_equal(average_clustering(G), 1.0) + assert_equal(list(triangles(G).values()), [1, 1, 1]) + G=dorogovtsev_goltsev_mendes_graph(10) + assert_equal(number_of_nodes(G), 29526) + assert_equal(number_of_edges(G), 59049) + assert_equal(G.degree(0), 1024) + assert_equal(G.degree(1), 1024) + assert_equal(G.degree(2), 1024) + + assert_raises(networkx.exception.NetworkXError, + dorogovtsev_goltsev_mendes_graph, 7, + create_using=DiGraph()) + assert_raises(networkx.exception.NetworkXError, + dorogovtsev_goltsev_mendes_graph, 7, + create_using=MultiGraph()) + + def test_empty_graph(self): + G=empty_graph() + assert_equal(number_of_nodes(G), 0) + G=empty_graph(42) + assert_equal(number_of_nodes(G), 42) + assert_equal(number_of_edges(G), 0) + assert_equal(G.name, 'empty_graph(42)') + + # create empty digraph + G=empty_graph(42,create_using=DiGraph(name="duh")) + assert_equal(number_of_nodes(G), 42) + assert_equal(number_of_edges(G), 0) + assert_equal(G.name, 'empty_graph(42)') + assert_true(isinstance(G,DiGraph)) + + # create empty multigraph + G=empty_graph(42,create_using=MultiGraph(name="duh")) + assert_equal(number_of_nodes(G), 42) + assert_equal(number_of_edges(G), 0) + assert_equal(G.name, 'empty_graph(42)') + assert_true(isinstance(G,MultiGraph)) + + # create empty graph from another + pete=petersen_graph() + G=empty_graph(42,create_using=pete) + assert_equal(number_of_nodes(G), 42) + assert_equal(number_of_edges(G), 0) + assert_equal(G.name, 'empty_graph(42)') + assert_true(isinstance(G,Graph)) + + def test_grid_2d_graph(self): + n=5;m=6 + G=grid_2d_graph(n,m) + assert_equal(number_of_nodes(G), n*m) + assert_equal(degree_histogram(G), [0,0,4,2*(n+m)-8,(n-2)*(m-2)]) + DG=grid_2d_graph(n,m, create_using=DiGraph()) + assert_equal(DG.succ, G.adj) + assert_equal(DG.pred, G.adj) + MG=grid_2d_graph(n,m, create_using=MultiGraph()) + assert_equal(MG.edges(), G.edges()) + + def test_grid_graph(self): + """grid_graph([n,m]) is a connected simple graph with the + following properties: + number_of_nodes=n*m + degree_histogram=[0,0,4,2*(n+m)-8,(n-2)*(m-2)] + """ + for n, m in [(3, 5), (5, 3), (4, 5), (5, 4)]: + dim=[n,m] + g=grid_graph(dim) + assert_equal(number_of_nodes(g), n*m) + assert_equal(degree_histogram(g), [0,0,4,2*(n+m)-8,(n-2)*(m-2)]) + assert_equal(dim,[n,m]) + + for n, m in [(1, 5), (5, 1)]: + dim=[n,m] + g=grid_graph(dim) + assert_equal(number_of_nodes(g), n*m) + assert_true(is_isomorphic(g,path_graph(5))) + assert_equal(dim,[n,m]) + +# mg=grid_graph([n,m], create_using=MultiGraph()) +# assert_equal(mg.edges(), g.edges()) + + def test_hypercube_graph(self): + for n, G in [(0, null_graph()), (1, path_graph(2)), + (2, cycle_graph(4)), (3, cubical_graph())]: + g=hypercube_graph(n) + assert_true(is_isomorphic(g, G)) + + g=hypercube_graph(4) + assert_equal(degree_histogram(g), [0, 0, 0, 0, 16]) + g=hypercube_graph(5) + assert_equal(degree_histogram(g), [0, 0, 0, 0, 0, 32]) + g=hypercube_graph(6) + assert_equal(degree_histogram(g), [0, 0, 0, 0, 0, 0, 64]) + +# mg=hypercube_graph(6, create_using=MultiGraph()) +# assert_equal(mg.edges(), g.edges()) + + def test_ladder_graph(self): + for i, G in [(0, empty_graph(0)), (1, path_graph(2)), + (2, hypercube_graph(2)), (10, grid_graph([2,10]))]: + assert_true(is_isomorphic(ladder_graph(i), G)) + + assert_raises(networkx.exception.NetworkXError, + ladder_graph, 2, create_using=DiGraph()) + + g = ladder_graph(2) + mg=ladder_graph(2, create_using=MultiGraph()) + assert_equal(mg.edges(), g.edges()) + + def test_lollipop_graph(self): + # number of nodes = m1 + m2 + # number of edges = number_of_edges(complete_graph(m1)) + m2 + for m1, m2 in [(3, 5), (4, 10), (3, 20)]: + b=lollipop_graph(m1,m2) + assert_equal(number_of_nodes(b), m1+m2) + assert_equal(number_of_edges(b), m1*(m1-1)/2 + m2) + assert_equal(b.name, + 'lollipop_graph(' + str(m1) + ',' + str(m2) + ')') + + # Raise NetworkXError if m<2 + assert_raises(networkx.exception.NetworkXError, + lollipop_graph, 1, 20) + + # Raise NetworkXError if n<0 + assert_raises(networkx.exception.NetworkXError, + lollipop_graph, 5, -2) + + # lollipop_graph(2,m) = path_graph(m+2) + for m1, m2 in [(2, 5), (2, 10), (2, 20)]: + b=lollipop_graph(m1,m2) + assert_true(is_isomorphic(b, path_graph(m2+2))) + + assert_raises(networkx.exception.NetworkXError, + lollipop_graph, m1, m2, create_using=DiGraph()) + + mb=lollipop_graph(m1, m2, create_using=MultiGraph()) + assert_true(mb.edges(), b.edges()) + + def test_null_graph(self): + assert_equal(number_of_nodes(null_graph()), 0) + + def test_path_graph(self): + p=path_graph(0) + assert_true(is_isomorphic(p, null_graph())) + assert_equal(p.name, 'path_graph(0)') + + p=path_graph(1) + assert_true(is_isomorphic( p, empty_graph(1))) + assert_equal(p.name, 'path_graph(1)') + + p=path_graph(10) + assert_true(is_connected(p)) + assert_equal(sorted(list(p.degree().values())), + [1, 1, 2, 2, 2, 2, 2, 2, 2, 2]) + assert_equal(p.order()-1, p.size()) + + dp=path_graph(3, create_using=DiGraph()) + assert_true(dp.has_edge(0,1)) + assert_false(dp.has_edge(1,0)) + + mp=path_graph(10, create_using=MultiGraph()) + assert_true(mp.edges()==p.edges()) + + def test_periodic_grid_2d_graph(self): + g=grid_2d_graph(0,0, periodic=True) + assert_equal(g.degree(), {}) + + for m, n, G in [(2, 2, cycle_graph(4)), (1, 7, cycle_graph(7)), + (7, 1, cycle_graph(7)), (2, 5, circular_ladder_graph(5)), + (5, 2, circular_ladder_graph(5)), (2, 4, cubical_graph()), + (4, 2, cubical_graph())]: + g=grid_2d_graph(m,n, periodic=True) + assert_true(is_isomorphic(g, G)) + + DG=grid_2d_graph(4, 2, periodic=True, create_using=DiGraph()) + assert_equal(DG.succ,g.adj) + assert_equal(DG.pred,g.adj) + MG=grid_2d_graph(4, 2, periodic=True, create_using=MultiGraph()) + assert_equal(MG.edges(),g.edges()) + + def test_star_graph(self): + assert_true(is_isomorphic(star_graph(0), empty_graph(1))) + assert_true(is_isomorphic(star_graph(1), path_graph(2))) + assert_true(is_isomorphic(star_graph(2), path_graph(3))) + + s=star_graph(10) + assert_equal(sorted(list(s.degree().values())), + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10]) + + assert_raises(networkx.exception.NetworkXError, + star_graph, 10, create_using=DiGraph()) + + ms=star_graph(10, create_using=MultiGraph()) + assert_true(ms.edges()==s.edges()) + + def test_trivial_graph(self): + assert_equal(number_of_nodes(trivial_graph()), 1) + + def test_wheel_graph(self): + for n, G in [(0, null_graph()), (1, empty_graph(1)), + (2, path_graph(2)), (3, complete_graph(3)), + (4, complete_graph(4))]: + g=wheel_graph(n) + assert_true(is_isomorphic( g, G)) + + assert_equal(g.name, 'wheel_graph(4)') + + g=wheel_graph(10) + assert_equal(sorted(list(g.degree().values())), + [3, 3, 3, 3, 3, 3, 3, 3, 3, 9]) + + assert_raises(networkx.exception.NetworkXError, + wheel_graph, 10, create_using=DiGraph()) + + mg=wheel_graph(10, create_using=MultiGraph()) + assert_equal(mg.edges(), g.edges()) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_degree_seq.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_degree_seq.py new file mode 100644 index 0000000..0d09855 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_degree_seq.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx +from networkx import * +from networkx.generators.degree_seq import * +from networkx.utils import uniform_sequence,powerlaw_sequence + +def test_configuration_model_empty(): + # empty graph has empty degree sequence + deg_seq=[] + G=configuration_model(deg_seq) + assert_equal(G.degree(), {}) + +def test_configuration_model(): + deg_seq=[5,3,3,3,3,2,2,2,1,1,1] + G=configuration_model(deg_seq,seed=12345678) + assert_equal(sorted(G.degree().values(),reverse=True), + [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]) + assert_equal(sorted(G.degree(range(len(deg_seq))).values(), + reverse=True), + [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]) + + # test that fixed seed delivers the same graph + deg_seq=[3,3,3,3,3,3,3,3,3,3,3,3] + G1=configuration_model(deg_seq,seed=1000) + G2=configuration_model(deg_seq,seed=1000) + assert_true(is_isomorphic(G1,G2)) + G1=configuration_model(deg_seq,seed=10) + G2=configuration_model(deg_seq,seed=10) + assert_true(is_isomorphic(G1,G2)) + +@raises(NetworkXError) +def test_configuation_raise(): + z=[5,3,3,3,3,2,2,2,1,1,1] + G = configuration_model(z, create_using=DiGraph()) + +@raises(NetworkXError) +def test_configuation_raise_odd(): + z=[5,3,3,3,3,2,2,2,1,1] + G = configuration_model(z, create_using=DiGraph()) + +@raises(NetworkXError) +def test_directed_configuation_raise_unequal(): + zin = [5,3,3,3,3,2,2,2,1,1] + zout = [5,3,3,3,3,2,2,2,1,2] + G = directed_configuration_model(zin, zout) + +def test_directed_configuation_mode(): + G = directed_configuration_model([],[],seed=0) + assert_equal(len(G),0) + + +def test_expected_degree_graph_empty(): + # empty graph has empty degree sequence + deg_seq=[] + G=expected_degree_graph(deg_seq) + assert_equal(G.degree(), {}) + + +def test_expected_degree_graph(): + # test that fixed seed delivers the same graph + deg_seq=[3,3,3,3,3,3,3,3,3,3,3,3] + G1=expected_degree_graph(deg_seq,seed=1000) + G2=expected_degree_graph(deg_seq,seed=1000) + assert_true(is_isomorphic(G1,G2)) + + G1=expected_degree_graph(deg_seq,seed=10) + G2=expected_degree_graph(deg_seq,seed=10) + assert_true(is_isomorphic(G1,G2)) + + +def test_expected_degree_graph_selfloops(): + deg_seq=[3,3,3,3,3,3,3,3,3,3,3,3] + G1=expected_degree_graph(deg_seq,seed=1000, selfloops=False) + G2=expected_degree_graph(deg_seq,seed=1000, selfloops=False) + assert_true(is_isomorphic(G1,G2)) + +def test_expected_degree_graph_skew(): + deg_seq=[10,2,2,2,2] + G1=expected_degree_graph(deg_seq,seed=1000) + G2=expected_degree_graph(deg_seq,seed=1000) + assert_true(is_isomorphic(G1,G2)) + + +def test_havel_hakimi_construction(): + G = havel_hakimi_graph([]) + assert_equal(len(G),0) + + z=[1000,3,3,3,3,2,2,2,1,1,1] + assert_raises(networkx.exception.NetworkXError, + havel_hakimi_graph, z) + z=["A",3,3,3,3,2,2,2,1,1,1] + assert_raises(networkx.exception.NetworkXError, + havel_hakimi_graph, z) + + z=[5,4,3,3,3,2,2,2] + G=havel_hakimi_graph(z) + G=configuration_model(z) + z=[6,5,4,4,2,1,1,1] + assert_raises(networkx.exception.NetworkXError, + havel_hakimi_graph, z) + + z=[10,3,3,3,3,2,2,2,2,2,2] + + G=havel_hakimi_graph(z) + + assert_raises(networkx.exception.NetworkXError, + havel_hakimi_graph, z, create_using=DiGraph()) + +def test_directed_havel_hakimi(): + # Test range of valid directed degree sequences + n, r = 100, 10 + p = 1.0 / r + for i in range(r): + G1 = nx.erdos_renyi_graph(n,p*(i+1),None,True) + din = list(G1.in_degree().values()) + dout = list(G1.out_degree().values()) + G2 = nx.directed_havel_hakimi_graph(din, dout) + assert_true(din == list(G2.in_degree().values())) + assert_true(dout == list(G2.out_degree().values())) + + # Test non-graphical sequence + dout = [1000,3,3,3,3,2,2,2,1,1,1] + din=[103,102,102,102,102,102,102,102,102,102] + assert_raises(nx.exception.NetworkXError, + nx.directed_havel_hakimi_graph, din, dout) + # Test valid sequences + dout=[1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + din=[2, 2, 2, 2, 2, 2, 2, 2, 0, 2] + G2 = nx.directed_havel_hakimi_graph(din, dout) + assert_true(din == list(G2.in_degree().values())) + assert_true(dout == list(G2.out_degree().values())) + # Test unequal sums + din=[2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + assert_raises(nx.exception.NetworkXError, + nx.directed_havel_hakimi_graph, din, dout) + # Test for negative values + din=[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -2] + assert_raises(nx.exception.NetworkXError, + nx.directed_havel_hakimi_graph, din, dout) + +def test_degree_sequence_tree(): + z=[1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + G=degree_sequence_tree(z) + assert_true(len(G.nodes())==len(z)) + assert_true(len(G.edges())==sum(z)/2) + + assert_raises(networkx.exception.NetworkXError, + degree_sequence_tree, z, create_using=DiGraph()) + + z=[1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + assert_raises(networkx.exception.NetworkXError, + degree_sequence_tree, z) + +def test_random_degree_sequence_graph(): + d=[1,2,2,3] + G = nx.random_degree_sequence_graph(d) + assert_equal(d, list(G.degree().values())) + +def test_random_degree_sequence_graph_raise(): + z=[1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4] + assert_raises(networkx.exception.NetworkXUnfeasible, + random_degree_sequence_graph, z) + +def test_random_degree_sequence_large(): + G = nx.fast_gnp_random_graph(100,0.1) + d = G.degree().values() + G = nx.random_degree_sequence_graph(d, seed=0) + assert_equal(sorted(d), sorted(list(G.degree().values()))) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_directed.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_directed.py new file mode 100644 index 0000000..45b9ab1 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_directed.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +"""Generators - Directed Graphs +---------------------------- +""" + +from nose.tools import * +from networkx import * +from networkx.generators.directed import * + +class TestGeneratorsDirected(): + def test_smoke_test_random_graphs(self): + G=gn_graph(100) + G=gnr_graph(100,0.5) + G=gnc_graph(100) + G=scale_free_graph(100) + + def test_create_using_keyword_arguments(self): + assert_raises(networkx.exception.NetworkXError, + gn_graph, 100, create_using=Graph()) + assert_raises(networkx.exception.NetworkXError, + gnr_graph, 100, 0.5, create_using=Graph()) + assert_raises(networkx.exception.NetworkXError, + gnc_graph, 100, create_using=Graph()) + assert_raises(networkx.exception.NetworkXError, + scale_free_graph, 100, create_using=Graph()) + G=gn_graph(100,seed=1) + MG=gn_graph(100,create_using=MultiDiGraph(),seed=1) + assert_equal(G.edges(), MG.edges()) + G=gnr_graph(100,0.5,seed=1) + MG=gnr_graph(100,0.5,create_using=MultiDiGraph(),seed=1) + assert_equal(G.edges(), MG.edges()) + G=gnc_graph(100,seed=1) + MG=gnc_graph(100,create_using=MultiDiGraph(),seed=1) + assert_equal(G.edges(), MG.edges()) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_ego.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_ego.py new file mode 100644 index 0000000..da15b60 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_ego.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +""" +ego graph +--------- +""" + +from nose.tools import assert_true, assert_equal +import networkx as nx + +class TestGeneratorEgo(): + def test_ego(self): + G=nx.star_graph(3) + H=nx.ego_graph(G,0) + assert_true(nx.is_isomorphic(G,H)) + G.add_edge(1,11) + G.add_edge(2,22) + G.add_edge(3,33) + H=nx.ego_graph(G,0) + assert_true(nx.is_isomorphic(nx.star_graph(3),H)) + G=nx.path_graph(3) + H=nx.ego_graph(G,0) + assert_equal(H.edges(), [(0, 1)]) + H=nx.ego_graph(G,0,undirected=True) + assert_equal(H.edges(), [(0, 1)]) + H=nx.ego_graph(G,0,center=False) + assert_equal(H.edges(), []) + + + def test_ego_distance(self): + G=nx.Graph() + G.add_edge(0,1,weight=2,distance=1) + G.add_edge(1,2,weight=2,distance=2) + G.add_edge(2,3,weight=2,distance=1) + assert_equal(sorted(nx.ego_graph(G,0,radius=3).nodes()),[0,1,2,3]) + eg=nx.ego_graph(G,0,radius=3,distance='weight') + assert_equal(sorted(eg.nodes()),[0,1]) + eg=nx.ego_graph(G,0,radius=3,distance='weight',undirected=True) + assert_equal(sorted(eg.nodes()),[0,1]) + eg=nx.ego_graph(G,0,radius=3,distance='distance') + assert_equal(sorted(eg.nodes()),[0,1,2]) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_geometric.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_geometric.py new file mode 100644 index 0000000..5e29e25 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_geometric.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestGeneratorsGeometric(): + def test_random_geometric_graph(self): + G=nx.random_geometric_graph(50,0.25) + assert_equal(len(G),50) + + def test_geographical_threshold_graph(self): + G=nx.geographical_threshold_graph(50,100) + assert_equal(len(G),50) + + def test_waxman_graph(self): + G=nx.waxman_graph(50,0.5,0.1) + assert_equal(len(G),50) + G=nx.waxman_graph(50,0.5,0.1,L=1) + assert_equal(len(G),50) + + def test_naviable_small_world(self): + G = nx.navigable_small_world_graph(5,p=1,q=0) + gg = nx.grid_2d_graph(5,5).to_directed() + assert_true(nx.is_isomorphic(G,gg)) + + G = nx.navigable_small_world_graph(5,p=1,q=0,dim=3) + gg = nx.grid_graph([5,5,5]).to_directed() + assert_true(nx.is_isomorphic(G,gg)) + + G = nx.navigable_small_world_graph(5,p=1,q=0,dim=1) + gg = nx.grid_graph([5]).to_directed() + assert_true(nx.is_isomorphic(G,gg)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_hybrid.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_hybrid.py new file mode 100644 index 0000000..88c7505 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_hybrid.py @@ -0,0 +1,24 @@ +from nose.tools import * +import networkx as nx + +def test_2d_grid_graph(): + # FC article claims 2d grid graph of size n is (3,3)-connected + # and (5,9)-connected, but I don't think it is (5,9)-connected + G=nx.grid_2d_graph(8,8,periodic=True) + assert_true(nx.is_kl_connected(G,3,3)) + assert_false(nx.is_kl_connected(G,5,9)) + (H,graphOK)=nx.kl_connected_subgraph(G,5,9,same_as_graph=True) + assert_false(graphOK) + +def test_small_graph(): + G=nx.Graph() + G.add_edge(1,2) + G.add_edge(1,3) + G.add_edge(2,3) + assert_true(nx.is_kl_connected(G,2,2)) + H=nx.kl_connected_subgraph(G,2,2) + (H,graphOK)=nx.kl_connected_subgraph(G,2,2, + low_memory=True, + same_as_graph=True) + assert_true(graphOK) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_intersection.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_intersection.py new file mode 100644 index 0000000..26bbcf6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_intersection.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx + +class TestIntersectionGraph(): + def test_random_intersection_graph(self): + G=nx.uniform_random_intersection_graph(10,5,0.5) + assert_equal(len(G),10) + + def test_k_random_intersection_graph(self): + G=nx.k_random_intersection_graph(10,5,2) + assert_equal(len(G),10) + + def test_general_random_intersection_graph(self): + G=nx.general_random_intersection_graph(10,5,[0.1,0.2,0.2,0.1,0.1]) + assert_equal(len(G),10) + assert_raises(ValueError, nx.general_random_intersection_graph,10,5, + [0.1,0.2,0.2,0.1]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_line.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_line.py new file mode 100644 index 0000000..35b71be --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_line.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +"""line graph +---------- +""" + +import networkx as nx +from nose.tools import * + + +class TestGeneratorLine(): + def test_line(self): + G=nx.star_graph(5) + L=nx.line_graph(G) + assert_true(nx.is_isomorphic(L,nx.complete_graph(5))) + G=nx.path_graph(5) + L=nx.line_graph(G) + assert_true(nx.is_isomorphic(L,nx.path_graph(4))) + G=nx.cycle_graph(5) + L=nx.line_graph(G) + assert_true(nx.is_isomorphic(L,G)) + G=nx.DiGraph() + G.add_edges_from([(0,1),(0,2),(0,3)]) + L=nx.line_graph(G) + assert_equal(L.adj, {}) + G=nx.DiGraph() + G.add_edges_from([(0,1),(1,2),(2,3)]) + L=nx.line_graph(G) + assert_equal(sorted(L.edges()), [((0, 1), (1, 2)), ((1, 2), (2, 3))]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_clustered.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_clustered.py new file mode 100644 index 0000000..f052f2c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_clustered.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx + +class TestRandomClusteredGraph: + + def test_valid(self): + node=[1,1,1,2,1,2,0,0] + tri=[0,0,0,0,0,1,1,1] + joint_degree_sequence=zip(node,tri) + G = networkx.random_clustered_graph(joint_degree_sequence) + assert_equal(G.number_of_nodes(),8) + assert_equal(G.number_of_edges(),7) + + def test_valid2(self): + G = networkx.random_clustered_graph(\ + [(1,2),(2,1),(1,1),(1,1),(1,1),(2,0)]) + assert_equal(G.number_of_nodes(),6) + assert_equal(G.number_of_edges(),10) + + def test_invalid1(self): + assert_raises((TypeError,networkx.NetworkXError), + networkx.random_clustered_graph,[[1,1],[2,1],[0,1]]) + + def test_invalid2(self): + assert_raises((TypeError,networkx.NetworkXError), + networkx.random_clustered_graph,[[1,1],[1,2],[0,1]]) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_graphs.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_graphs.py new file mode 100644 index 0000000..c49d74d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_random_graphs.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +from nose.tools import * +from networkx import * +from networkx.generators.random_graphs import * + +class TestGeneratorsRandom(): + def smoke_test_random_graph(self): + seed = 42 + G=gnp_random_graph(100,0.25,seed) + G=binomial_graph(100,0.25,seed) + G=erdos_renyi_graph(100,0.25,seed) + G=fast_gnp_random_graph(100,0.25,seed) + G=gnm_random_graph(100,20,seed) + G=dense_gnm_random_graph(100,20,seed) + + G=watts_strogatz_graph(10,2,0.25,seed) + assert_equal(len(G), 10) + assert_equal(G.number_of_edges(), 10) + + G=connected_watts_strogatz_graph(10,2,0.1,seed) + assert_equal(len(G), 10) + assert_equal(G.number_of_edges(), 10) + + G=watts_strogatz_graph(10,4,0.25,seed) + assert_equal(len(G), 10) + assert_equal(G.number_of_edges(), 20) + + G=newman_watts_strogatz_graph(10,2,0.0,seed) + assert_equal(len(G), 10) + assert_equal(G.number_of_edges(), 10) + + G=newman_watts_strogatz_graph(10,4,0.25,seed) + assert_equal(len(G), 10) + assert_true(G.number_of_edges() >= 20) + + G=barabasi_albert_graph(100,1,seed) + G=barabasi_albert_graph(100,3,seed) + assert_equal(G.number_of_edges(),(97*3)) + + G=powerlaw_cluster_graph(100,1,1.0,seed) + G=powerlaw_cluster_graph(100,3,0.0,seed) + assert_equal(G.number_of_edges(),(97*3)) + + G=random_regular_graph(10,20,seed) + + assert_raises(networkx.exception.NetworkXError, + random_regular_graph, 3, 21) + + constructor=[(10,20,0.8),(20,40,0.8)] + G=random_shell_graph(constructor,seed) + + G=nx.random_lobster(10,0.1,0.5,seed) + + def test_gnp(self): + G=gnp_random_graph(10,0.1) + assert_equal(len(G),10) + + G=gnp_random_graph(10,0.1,seed=42) + assert_equal(len(G),10) + + G=gnp_random_graph(10,1.1) + assert_equal(len(G),10) + assert_equal(len(G.edges()),45) + + G=gnp_random_graph(10,1.1,directed=True) + assert_equal(len(G),10) + assert_equal(len(G.edges()),90) + + G=gnp_random_graph(10,-1.1) + assert_equal(len(G),10) + assert_equal(len(G.edges()),0) + + G=binomial_graph(10,0.1) + assert_equal(len(G),10) + + G=erdos_renyi_graph(10,0.1) + assert_equal(len(G),10) + + + def test_fast_gnp(self): + G=fast_gnp_random_graph(10,0.1) + assert_equal(len(G),10) + + G=fast_gnp_random_graph(10,0.1,seed=42) + assert_equal(len(G),10) + + G=fast_gnp_random_graph(10,1.1) + assert_equal(len(G),10) + assert_equal(len(G.edges()),45) + + G=fast_gnp_random_graph(10,-1.1) + assert_equal(len(G),10) + assert_equal(len(G.edges()),0) + + G=fast_gnp_random_graph(10,0.1,directed=True) + assert_true(G.is_directed()) + assert_equal(len(G),10) + + + def test_gnm(self): + G=gnm_random_graph(10,3) + assert_equal(len(G),10) + assert_equal(len(G.edges()),3) + + G=gnm_random_graph(10,3,seed=42) + assert_equal(len(G),10) + assert_equal(len(G.edges()),3) + + G=gnm_random_graph(10,100) + assert_equal(len(G),10) + assert_equal(len(G.edges()),45) + + G=gnm_random_graph(10,100,directed=True) + assert_equal(len(G),10) + assert_equal(len(G.edges()),90) + + G=gnm_random_graph(10,-1.1) + assert_equal(len(G),10) + assert_equal(len(G.edges()),0) + + def test_watts_strogatz_big_k(self): + assert_raises(networkx.exception.NetworkXError, + watts_strogatz_graph, 10, 10, 0.25) + assert_raises(networkx.exception.NetworkXError, + newman_watts_strogatz_graph, 10, 10, 0.25) + # could create an infinite loop, now doesn't + # infinite loop used to occur when a node has degree n-1 and needs to rewire + watts_strogatz_graph(10, 9, 0.25, seed=0) + newman_watts_strogatz_graph(10, 9, 0.5, seed=0) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_small.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_small.py new file mode 100644 index 0000000..5228d3a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_small.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python + +from nose.tools import * +from networkx import * +from networkx.algorithms.isomorphism.isomorph import graph_could_be_isomorphic +is_isomorphic=graph_could_be_isomorphic + +"""Generators - Small +===================== + +Some small graphs +""" + +null=null_graph() + +class TestGeneratorsSmall(): + def test_make_small_graph(self): + d=["adjacencylist","Bull Graph",5,[[2,3],[1,3,4],[1,2,5],[2],[3]]] + G=make_small_graph(d) + assert_true(is_isomorphic(G, bull_graph())) + + def test__LCF_graph(self): + # If n<=0, then return the null_graph + G=LCF_graph(-10,[1,2],100) + assert_true(is_isomorphic(G,null)) + G=LCF_graph(0,[1,2],3) + assert_true(is_isomorphic(G,null)) + G=LCF_graph(0,[1,2],10) + assert_true(is_isomorphic(G,null)) + + # Test that LCF(n,[],0) == cycle_graph(n) + for a, b, c in [(5, [], 0), (10, [], 0), (5, [], 1), (10, [], 10)]: + G=LCF_graph(a, b, c) + assert_true(is_isomorphic(G,cycle_graph(a))) + + # Generate the utility graph K_{3,3} + G=LCF_graph(6,[3,-3],3) + utility_graph=complete_bipartite_graph(3,3) + assert_true(is_isomorphic(G, utility_graph)) + + def test_properties_named_small_graphs(self): + G=bull_graph() + assert_equal(G.number_of_nodes(), 5) + assert_equal(G.number_of_edges(), 5) + assert_equal(sorted(G.degree().values()), [1, 1, 2, 3, 3]) + assert_equal(diameter(G), 3) + assert_equal(radius(G), 2) + + G=chvatal_graph() + assert_equal(G.number_of_nodes(), 12) + assert_equal(G.number_of_edges(), 24) + assert_equal(list(G.degree().values()), 12 * [4]) + assert_equal(diameter(G), 2) + assert_equal(radius(G), 2) + + G=cubical_graph() + assert_equal(G.number_of_nodes(), 8) + assert_equal(G.number_of_edges(), 12) + assert_equal(list(G.degree().values()), 8*[3]) + assert_equal(diameter(G), 3) + assert_equal(radius(G), 3) + + G=desargues_graph() + assert_equal(G.number_of_nodes(), 20) + assert_equal(G.number_of_edges(), 30) + assert_equal(list(G.degree().values()), 20*[3]) + + G=diamond_graph() + assert_equal(G.number_of_nodes(), 4) + assert_equal(sorted(G.degree().values()), [2, 2, 3, 3]) + assert_equal(diameter(G), 2) + assert_equal(radius(G), 1) + + G=dodecahedral_graph() + assert_equal(G.number_of_nodes(), 20) + assert_equal(G.number_of_edges(), 30) + assert_equal(list(G.degree().values()), 20*[3]) + assert_equal(diameter(G), 5) + assert_equal(radius(G), 5) + + G=frucht_graph() + assert_equal(G.number_of_nodes(), 12) + assert_equal(G.number_of_edges(), 18) + assert_equal(list(G.degree().values()), 12*[3]) + assert_equal(diameter(G), 4) + assert_equal(radius(G), 3) + + G=heawood_graph() + assert_equal(G.number_of_nodes(), 14) + assert_equal(G.number_of_edges(), 21) + assert_equal(list(G.degree().values()), 14*[3]) + assert_equal(diameter(G), 3) + assert_equal(radius(G), 3) + + G=house_graph() + assert_equal(G.number_of_nodes(), 5) + assert_equal(G.number_of_edges(), 6) + assert_equal(sorted(G.degree().values()), [2, 2, 2, 3, 3]) + assert_equal(diameter(G), 2) + assert_equal(radius(G), 2) + + G=house_x_graph() + assert_equal(G.number_of_nodes(), 5) + assert_equal(G.number_of_edges(), 8) + assert_equal(sorted(G.degree().values()), [2, 3, 3, 4, 4]) + assert_equal(diameter(G), 2) + assert_equal(radius(G), 1) + + G=icosahedral_graph() + assert_equal(G.number_of_nodes(), 12) + assert_equal(G.number_of_edges(), 30) + assert_equal(list(G.degree().values()), + [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]) + assert_equal(diameter(G), 3) + assert_equal(radius(G), 3) + + G=krackhardt_kite_graph() + assert_equal(G.number_of_nodes(), 10) + assert_equal(G.number_of_edges(), 18) + assert_equal(sorted(G.degree().values()), + [1, 2, 3, 3, 3, 4, 4, 5, 5, 6]) + + G=moebius_kantor_graph() + assert_equal(G.number_of_nodes(), 16) + assert_equal(G.number_of_edges(), 24) + assert_equal(list(G.degree().values()), 16*[3]) + assert_equal(diameter(G), 4) + + G=octahedral_graph() + assert_equal(G.number_of_nodes(), 6) + assert_equal(G.number_of_edges(), 12) + assert_equal(list(G.degree().values()), 6*[4]) + assert_equal(diameter(G), 2) + assert_equal(radius(G), 2) + + G=pappus_graph() + assert_equal(G.number_of_nodes(), 18) + assert_equal(G.number_of_edges(), 27) + assert_equal(list(G.degree().values()), 18*[3]) + assert_equal(diameter(G), 4) + + G=petersen_graph() + assert_equal(G.number_of_nodes(), 10) + assert_equal(G.number_of_edges(), 15) + assert_equal(list(G.degree().values()), 10*[3]) + assert_equal(diameter(G), 2) + assert_equal(radius(G), 2) + + G=sedgewick_maze_graph() + assert_equal(G.number_of_nodes(), 8) + assert_equal(G.number_of_edges(), 10) + assert_equal(sorted(G.degree().values()), [1, 2, 2, 2, 3, 3, 3, 4]) + + G=tetrahedral_graph() + assert_equal(G.number_of_nodes(), 4) + assert_equal(G.number_of_edges(), 6) + assert_equal(list(G.degree().values()), [3, 3, 3, 3]) + assert_equal(diameter(G), 1) + assert_equal(radius(G), 1) + + G=truncated_cube_graph() + assert_equal(G.number_of_nodes(), 24) + assert_equal(G.number_of_edges(), 36) + assert_equal(list(G.degree().values()), 24*[3]) + + G=truncated_tetrahedron_graph() + assert_equal(G.number_of_nodes(), 12) + assert_equal(G.number_of_edges(), 18) + assert_equal(list(G.degree().values()), 12*[3]) + + G=tutte_graph() + assert_equal(G.number_of_nodes(), 46) + assert_equal(G.number_of_edges(), 69) + assert_equal(list(G.degree().values()), 46*[3]) + + # Test create_using with directed or multigraphs on small graphs + assert_raises(networkx.exception.NetworkXError, tutte_graph, + create_using=DiGraph()) + MG=tutte_graph(create_using=MultiGraph()) + assert_equal(MG.edges(), G.edges()) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_stochastic.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_stochastic.py new file mode 100644 index 0000000..15c6fef --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_stochastic.py @@ -0,0 +1,33 @@ +from nose.tools import assert_true, assert_equal, raises +import networkx as nx + +def test_stochastic(): + G=nx.DiGraph() + G.add_edge(0,1) + G.add_edge(0,2) + S=nx.stochastic_graph(G) + assert_true(nx.is_isomorphic(G,S)) + assert_equal(sorted(S.edges(data=True)), + [(0, 1, {'weight': 0.5}), + (0, 2, {'weight': 0.5})]) + S=nx.stochastic_graph(G,copy=True) + assert_equal(sorted(S.edges(data=True)), + [(0, 1, {'weight': 0.5}), + (0, 2, {'weight': 0.5})]) + +def test_stochastic_ints(): + G=nx.DiGraph() + G.add_edge(0,1,weight=1) + G.add_edge(0,2,weight=1) + S=nx.stochastic_graph(G) + assert_equal(sorted(S.edges(data=True)), + [(0, 1, {'weight': 0.5}), + (0, 2, {'weight': 0.5})]) + +@raises(nx.NetworkXError) +def test_stochastic_graph_input(): + S = nx.stochastic_graph(nx.Graph()) + +@raises(nx.NetworkXError) +def test_stochastic_multigraph_input(): + S = nx.stochastic_graph(nx.MultiGraph()) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_threshold.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_threshold.py new file mode 100644 index 0000000..198d7cc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/tests/test_threshold.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python + +"""Threshold Graphs +================ +""" + +from nose.tools import * +from nose import SkipTest +from nose.plugins.attrib import attr +import networkx as nx +import networkx.generators.threshold as nxt +from networkx.algorithms.isomorphism.isomorph import graph_could_be_isomorphic + +cnlti = nx.convert_node_labels_to_integers + + +class TestGeneratorThreshold(): + def test_threshold_sequence_graph_test(self): + G=nx.star_graph(10) + assert_true(nxt.is_threshold_graph(G)) + assert_true(nxt.is_threshold_sequence(list(G.degree().values()))) + + G=nx.complete_graph(10) + assert_true(nxt.is_threshold_graph(G)) + assert_true(nxt.is_threshold_sequence(list(G.degree().values()))) + + deg=[3,2,2,1,1,1] + assert_false(nxt.is_threshold_sequence(deg)) + + deg=[3,2,2,1] + assert_true(nxt.is_threshold_sequence(deg)) + + G=nx.generators.havel_hakimi_graph(deg) + assert_true(nxt.is_threshold_graph(G)) + + def test_creation_sequences(self): + deg=[3,2,2,1] + G=nx.generators.havel_hakimi_graph(deg) + cs0=nxt.creation_sequence(deg) + H0=nxt.threshold_graph(cs0) + assert_equal(''.join(cs0), 'ddid') + + cs1=nxt.creation_sequence(deg, with_labels=True) + H1=nxt.threshold_graph(cs1) + assert_equal(cs1, [(1, 'd'), (2, 'd'), (3, 'i'), (0, 'd')]) + + cs2=nxt.creation_sequence(deg, compact=True) + H2=nxt.threshold_graph(cs2) + assert_equal(cs2, [2, 1, 1]) + assert_equal(''.join(nxt.uncompact(cs2)), 'ddid') + assert_true(graph_could_be_isomorphic(H0,G)) + assert_true(graph_could_be_isomorphic(H0,H1)) + assert_true(graph_could_be_isomorphic(H0,H2)) + + def test_shortest_path(self): + deg=[3,2,2,1] + G=nx.generators.havel_hakimi_graph(deg) + cs1=nxt.creation_sequence(deg, with_labels=True) + for n, m in [(3, 0), (0, 3), (0, 2), (0, 1), (1, 3), + (3, 1), (1, 2), (2, 3)]: + assert_equal(nxt.shortest_path(cs1,n,m), + nx.shortest_path(G, n, m)) + + spl=nxt.shortest_path_length(cs1,3) + spl2=nxt.shortest_path_length([ t for v,t in cs1],2) + assert_equal(spl, spl2) + + spld={} + for j,pl in enumerate(spl): + n=cs1[j][0] + spld[n]=pl + assert_equal(spld, nx.single_source_shortest_path_length(G, 3)) + + def test_weights_thresholds(self): + wseq=[3,4,3,3,5,6,5,4,5,6] + cs=nxt.weights_to_creation_sequence(wseq,threshold=10) + wseq=nxt.creation_sequence_to_weights(cs) + cs2=nxt.weights_to_creation_sequence(wseq) + assert_equal(cs, cs2) + + wseq=nxt.creation_sequence_to_weights(nxt.uncompact([3,1,2,3,3,2,3])) + assert_equal(wseq, + [s*0.125 for s in [4,4,4,3,5,5,2,2,2,6,6,6,1,1,7,7,7]]) + + wseq=nxt.creation_sequence_to_weights([3,1,2,3,3,2,3]) + assert_equal(wseq, + [s*0.125 for s in [4,4,4,3,5,5,2,2,2,6,6,6,1,1,7,7,7]]) + + wseq=nxt.creation_sequence_to_weights(list(enumerate('ddidiiidididi'))) + assert_equal(wseq, + [s*0.1 for s in [5,5,4,6,3,3,3,7,2,8,1,9,0]]) + + wseq=nxt.creation_sequence_to_weights('ddidiiidididi') + assert_equal(wseq, + [s*0.1 for s in [5,5,4,6,3,3,3,7,2,8,1,9,0]]) + + wseq=nxt.creation_sequence_to_weights('ddidiiidididid') + ws=[s/float(12) for s in [6,6,5,7,4,4,4,8,3,9,2,10,1,11]] + assert_true(sum([abs(c-d) for c,d in zip(wseq,ws)]) < 1e-14) + + def test_finding_routines(self): + G=nx.Graph({1:[2],2:[3],3:[4],4:[5],5:[6]}) + G.add_edge(2,4) + G.add_edge(2,5) + G.add_edge(2,7) + G.add_edge(3,6) + G.add_edge(4,6) + + # Alternating 4 cycle + assert_equal(nxt.find_alternating_4_cycle(G), [1, 2, 3, 6]) + + # Threshold graph + TG=nxt.find_threshold_graph(G) + assert_true(nxt.is_threshold_graph(TG)) + assert_equal(sorted(TG.nodes()), [1, 2, 3, 4, 5, 7]) + + cs=nxt.creation_sequence(TG.degree(),with_labels=True) + assert_equal(nxt.find_creation_sequence(G), cs) + + def test_fast_versions_properties_threshold_graphs(self): + cs='ddiiddid' + G=nxt.threshold_graph(cs) + assert_equal(nxt.density('ddiiddid'), nx.density(G)) + assert_equal(sorted(nxt.degree_sequence(cs)), + sorted(G.degree().values())) + + ts=nxt.triangle_sequence(cs) + assert_equal(ts, list(nx.triangles(G).values())) + assert_equal(sum(ts) // 3, nxt.triangles(cs)) + + c1=nxt.cluster_sequence(cs) + c2=list(nx.clustering(G).values()) + assert_almost_equal(sum([abs(c-d) for c,d in zip(c1,c2)]), 0) + + b1=nx.betweenness_centrality(G).values() + b2=nxt.betweenness_sequence(cs) + assert_true(sum([abs(c-d) for c,d in zip(b1,b2)]) < 1e-14) + + assert_equal(nxt.eigenvalues(cs), [0, 1, 3, 3, 5, 7, 7, 8]) + + # Degree Correlation + assert_true(abs(nxt.degree_correlation(cs)+0.593038821954) < 1e-12) + assert_equal(nxt.degree_correlation('diiiddi'), -0.8) + assert_equal(nxt.degree_correlation('did'), -1.0) + assert_equal(nxt.degree_correlation('ddd'), 1.0) + assert_equal(nxt.eigenvalues('dddiii'), [0, 0, 0, 0, 3, 3]) + assert_equal(nxt.eigenvalues('dddiiid'), [0, 1, 1, 1, 4, 4, 7]) + + def test_tg_creation_routines(self): + s=nxt.left_d_threshold_sequence(5,7) + s=nxt.right_d_threshold_sequence(5,7) + s1=nxt.swap_d(s,1.0,1.0) + + + @attr('numpy') + def test_eigenvectors(self): + try: + import numpy as N + eigenval=N.linalg.eigvals + except ImportError: + raise SkipTest('NumPy not available.') + + cs='ddiiddid' + G=nxt.threshold_graph(cs) + (tgeval,tgevec)=nxt.eigenvectors(cs) + dot=N.dot + assert_equal([ abs(dot(lv,lv)-1.0)<1e-9 for lv in tgevec ], [True]*8) + lapl=nx.laplacian_matrix(G) +# tgev=[ dot(lv,dot(lapl,lv)) for lv in tgevec ] +# assert_true(sum([abs(c-d) for c,d in zip(tgev,tgeval)]) < 1e-9) +# tgev.sort() +# lev=list(eigenval(lapl)) +# lev.sort() +# assert_true(sum([abs(c-d) for c,d in zip(tgev,lev)]) < 1e-9) + + def test_create_using(self): + cs='ddiiddid' + G=nxt.threshold_graph(cs) + assert_raises(nx.exception.NetworkXError, + nxt.threshold_graph, cs, create_using=nx.DiGraph()) + MG=nxt.threshold_graph(cs,create_using=nx.MultiGraph()) + assert_equal(MG.edges(), G.edges()) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/generators/threshold.py b/lib/python2.7/site-packages/setoolsgui/networkx/generators/threshold.py new file mode 100644 index 0000000..68a565e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/generators/threshold.py @@ -0,0 +1,906 @@ +""" +Threshold Graphs - Creation, manipulation and identification. +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nPieter Swart (swart@lanl.gov)\nDan Schult (dschult@colgate.edu)""" +# Copyright (C) 2004-2008 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# + +__all__=[] + +import random # for swap_d +from math import sqrt +import networkx + +def is_threshold_graph(G): + """ + Returns True if G is a threshold graph. + """ + return is_threshold_sequence(list(G.degree().values())) + +def is_threshold_sequence(degree_sequence): + """ + Returns True if the sequence is a threshold degree seqeunce. + + Uses the property that a threshold graph must be constructed by + adding either dominating or isolated nodes. Thus, it can be + deconstructed iteratively by removing a node of degree zero or a + node that connects to the remaining nodes. If this deconstruction + failes then the sequence is not a threshold sequence. + """ + ds=degree_sequence[:] # get a copy so we don't destroy original + ds.sort() + while ds: + if ds[0]==0: # if isolated node + ds.pop(0) # remove it + continue + if ds[-1]!=len(ds)-1: # is the largest degree node dominating? + return False # no, not a threshold degree sequence + ds.pop() # yes, largest is the dominating node + ds=[ d-1 for d in ds ] # remove it and decrement all degrees + return True + + +def creation_sequence(degree_sequence,with_labels=False,compact=False): + """ + Determines the creation sequence for the given threshold degree sequence. + + The creation sequence is a list of single characters 'd' + or 'i': 'd' for dominating or 'i' for isolated vertices. + Dominating vertices are connected to all vertices present when it + is added. The first node added is by convention 'd'. + This list can be converted to a string if desired using "".join(cs) + + If with_labels==True: + Returns a list of 2-tuples containing the vertex number + and a character 'd' or 'i' which describes the type of vertex. + + If compact==True: + Returns the creation sequence in a compact form that is the number + of 'i's and 'd's alternating. + Examples: + [1,2,2,3] represents d,i,i,d,d,i,i,i + [3,1,2] represents d,d,d,i,d,d + + Notice that the first number is the first vertex to be used for + construction and so is always 'd'. + + with_labels and compact cannot both be True. + + Returns None if the sequence is not a threshold sequence + """ + if with_labels and compact: + raise ValueError("compact sequences cannot be labeled") + + # make an indexed copy + if isinstance(degree_sequence,dict): # labeled degree seqeunce + ds = [ [degree,label] for (label,degree) in degree_sequence.items() ] + else: + ds=[ [d,i] for i,d in enumerate(degree_sequence) ] + ds.sort() + cs=[] # creation sequence + while ds: + if ds[0][0]==0: # isolated node + (d,v)=ds.pop(0) + if len(ds)>0: # make sure we start with a d + cs.insert(0,(v,'i')) + else: + cs.insert(0,(v,'d')) + continue + if ds[-1][0]!=len(ds)-1: # Not dominating node + return None # not a threshold degree sequence + (d,v)=ds.pop() + cs.insert(0,(v,'d')) + ds=[ [d[0]-1,d[1]] for d in ds ] # decrement due to removing node + + if with_labels: return cs + if compact: return make_compact(cs) + return [ v[1] for v in cs ] # not labeled + +def make_compact(creation_sequence): + """ + Returns the creation sequence in a compact form + that is the number of 'i's and 'd's alternating. + Examples: + [1,2,2,3] represents d,i,i,d,d,i,i,i. + [3,1,2] represents d,d,d,i,d,d. + Notice that the first number is the first vertex + to be used for construction and so is always 'd'. + + Labeled creation sequences lose their labels in the + compact representation. + """ + first=creation_sequence[0] + if isinstance(first,str): # creation sequence + cs = creation_sequence[:] + elif isinstance(first,tuple): # labeled creation sequence + cs = [ s[1] for s in creation_sequence ] + elif isinstance(first,int): # compact creation sequence + return creation_sequence + else: + raise TypeError("Not a valid creation sequence type") + + ccs=[] + count=1 # count the run lengths of d's or i's. + for i in range(1,len(cs)): + if cs[i]==cs[i-1]: + count+=1 + else: + ccs.append(count) + count=1 + ccs.append(count) # don't forget the last one + return ccs + +def uncompact(creation_sequence): + """ + Converts a compact creation sequence for a threshold + graph to a standard creation sequence (unlabeled). + If the creation_sequence is already standard, return it. + See creation_sequence. + """ + first=creation_sequence[0] + if isinstance(first,str): # creation sequence + return creation_sequence + elif isinstance(first,tuple): # labeled creation sequence + return creation_sequence + elif isinstance(first,int): # compact creation sequence + ccscopy=creation_sequence[:] + else: + raise TypeError("Not a valid creation sequence type") + cs = [] + while ccscopy: + cs.extend(ccscopy.pop(0)*['d']) + if ccscopy: + cs.extend(ccscopy.pop(0)*['i']) + return cs + +def creation_sequence_to_weights(creation_sequence): + """ + Returns a list of node weights which create the threshold + graph designated by the creation sequence. The weights + are scaled so that the threshold is 1.0. The order of the + nodes is the same as that in the creation sequence. + """ + # Turn input sequence into a labeled creation sequence + first=creation_sequence[0] + if isinstance(first,str): # creation sequence + if isinstance(creation_sequence,list): + wseq = creation_sequence[:] + else: + wseq = list(creation_sequence) # string like 'ddidid' + elif isinstance(first,tuple): # labeled creation sequence + wseq = [ v[1] for v in creation_sequence] + elif isinstance(first,int): # compact creation sequence + wseq = uncompact(creation_sequence) + else: + raise TypeError("Not a valid creation sequence type") + # pass through twice--first backwards + wseq.reverse() + w=0 + prev='i' + for j,s in enumerate(wseq): + if s=='i': + wseq[j]=w + prev=s + elif prev=='i': + prev=s + w+=1 + wseq.reverse() # now pass through forwards + for j,s in enumerate(wseq): + if s=='d': + wseq[j]=w + prev=s + elif prev=='d': + prev=s + w+=1 + # Now scale weights + if prev=='d': w+=1 + wscale=1./float(w) + return [ ww*wscale for ww in wseq] + #return wseq + +def weights_to_creation_sequence(weights,threshold=1,with_labels=False,compact=False): + """ + Returns a creation sequence for a threshold graph + determined by the weights and threshold given as input. + If the sum of two node weights is greater than the + threshold value, an edge is created between these nodes. + + The creation sequence is a list of single characters 'd' + or 'i': 'd' for dominating or 'i' for isolated vertices. + Dominating vertices are connected to all vertices present + when it is added. The first node added is by convention 'd'. + + If with_labels==True: + Returns a list of 2-tuples containing the vertex number + and a character 'd' or 'i' which describes the type of vertex. + + If compact==True: + Returns the creation sequence in a compact form that is the number + of 'i's and 'd's alternating. + Examples: + [1,2,2,3] represents d,i,i,d,d,i,i,i + [3,1,2] represents d,d,d,i,d,d + + Notice that the first number is the first vertex to be used for + construction and so is always 'd'. + + with_labels and compact cannot both be True. + """ + if with_labels and compact: + raise ValueError("compact sequences cannot be labeled") + + # make an indexed copy + if isinstance(weights,dict): # labeled weights + wseq = [ [w,label] for (label,w) in weights.items() ] + else: + wseq = [ [w,i] for i,w in enumerate(weights) ] + wseq.sort() + cs=[] # creation sequence + cutoff=threshold-wseq[-1][0] + while wseq: + if wseq[0][0]0: + # get new degree sequence on subgraph + dsdict=H.degree() + ds=[ [d,v] for v,d in dsdict.items() ] + ds.sort() + # Update threshold graph nodes + if ds[-1][0]==0: # all are isolated + cs.extend( zip( dsdict, ['i']*(len(ds)-1)+['d']) ) + break # Done! + # pull off isolated nodes + while ds[0][0]==0: + (d,iso)=ds.pop(0) + cs.append((iso,'i')) + # find new biggest node + (d,bigv)=ds.pop() + # add edges of star to t_g + cs.append((bigv,'d')) + # form subgraph of neighbors of big node + H=H.subgraph(H.neighbors(bigv)) + cs.reverse() + return cs + + + +### Properties of Threshold Graphs +def triangles(creation_sequence): + """ + Compute number of triangles in the threshold graph with the + given creation sequence. + """ + # shortcut algoritm that doesn't require computing number + # of triangles at each node. + cs=creation_sequence # alias + dr=cs.count("d") # number of d's in sequence + ntri=dr*(dr-1)*(dr-2)/6 # number of triangles in clique of nd d's + # now add dr choose 2 triangles for every 'i' in sequence where + # dr is the number of d's to the right of the current i + for i,typ in enumerate(cs): + if typ=="i": + ntri+=dr*(dr-1)/2 + else: + dr-=1 + return ntri + + +def triangle_sequence(creation_sequence): + """ + Return triangle sequence for the given threshold graph creation sequence. + + """ + cs=creation_sequence + seq=[] + dr=cs.count("d") # number of d's to the right of the current pos + dcur=(dr-1)*(dr-2) // 2 # number of triangles through a node of clique dr + irun=0 # number of i's in the last run + drun=0 # number of d's in the last run + for i,sym in enumerate(cs): + if sym=="d": + drun+=1 + tri=dcur+(dr-1)*irun # new triangles at this d + else: # cs[i]="i": + if prevsym=="d": # new string of i's + dcur+=(dr-1)*irun # accumulate shared shortest paths + irun=0 # reset i run counter + dr-=drun # reduce number of d's to right + drun=0 # reset d run counter + irun+=1 + tri=dr*(dr-1) // 2 # new triangles at this i + seq.append(tri) + prevsym=sym + return seq + +def cluster_sequence(creation_sequence): + """ + Return cluster sequence for the given threshold graph creation sequence. + """ + triseq=triangle_sequence(creation_sequence) + degseq=degree_sequence(creation_sequence) + cseq=[] + for i,deg in enumerate(degseq): + tri=triseq[i] + if deg <= 1: # isolated vertex or single pair gets cc 0 + cseq.append(0) + continue + max_size=(deg*(deg-1)) // 2 + cseq.append(float(tri)/float(max_size)) + return cseq + + +def degree_sequence(creation_sequence): + """ + Return degree sequence for the threshold graph with the given + creation sequence + """ + cs=creation_sequence # alias + seq=[] + rd=cs.count("d") # number of d to the right + for i,sym in enumerate(cs): + if sym=="d": + rd-=1 + seq.append(rd+i) + else: + seq.append(rd) + return seq + +def density(creation_sequence): + """ + Return the density of the graph with this creation_sequence. + The density is the fraction of possible edges present. + """ + N=len(creation_sequence) + two_size=sum(degree_sequence(creation_sequence)) + two_possible=N*(N-1) + den=two_size/float(two_possible) + return den + +def degree_correlation(creation_sequence): + """ + Return the degree-degree correlation over all edges. + """ + cs=creation_sequence + s1=0 # deg_i*deg_j + s2=0 # deg_i^2+deg_j^2 + s3=0 # deg_i+deg_j + m=0 # number of edges + rd=cs.count("d") # number of d nodes to the right + rdi=[ i for i,sym in enumerate(cs) if sym=="d"] # index of "d"s + ds=degree_sequence(cs) + for i,sym in enumerate(cs): + if sym=="d": + if i!=rdi[0]: + print("Logic error in degree_correlation",i,rdi) + raise ValueError + rdi.pop(0) + degi=ds[i] + for dj in rdi: + degj=ds[dj] + s1+=degj*degi + s2+=degi**2+degj**2 + s3+=degi+degj + m+=1 + denom=(2*m*s2-s3*s3) + numer=(4*m*s1-s3*s3) + if denom==0: + if numer==0: + return 1 + raise ValueError("Zero Denominator but Numerator is %s"%numer) + return numer/float(denom) + + +def shortest_path(creation_sequence,u,v): + """ + Find the shortest path between u and v in a + threshold graph G with the given creation_sequence. + + For an unlabeled creation_sequence, the vertices + u and v must be integers in (0,len(sequence)) refering + to the position of the desired vertices in the sequence. + + For a labeled creation_sequence, u and v are labels of veritices. + + Use cs=creation_sequence(degree_sequence,with_labels=True) + to convert a degree sequence to a creation sequence. + + Returns a list of vertices from u to v. + Example: if they are neighbors, it returns [u,v] + """ + # Turn input sequence into a labeled creation sequence + first=creation_sequence[0] + if isinstance(first,str): # creation sequence + cs = [(i,creation_sequence[i]) for i in range(len(creation_sequence))] + elif isinstance(first,tuple): # labeled creation sequence + cs = creation_sequence[:] + elif isinstance(first,int): # compact creation sequence + ci = uncompact(creation_sequence) + cs = [(i,ci[i]) for i in range(len(ci))] + else: + raise TypeError("Not a valid creation sequence type") + + verts=[ s[0] for s in cs ] + if v not in verts: + raise ValueError("Vertex %s not in graph from creation_sequence"%v) + if u not in verts: + raise ValueError("Vertex %s not in graph from creation_sequence"%u) + # Done checking + if u==v: return [u] + + uindex=verts.index(u) + vindex=verts.index(v) + bigind=max(uindex,vindex) + if cs[bigind][1]=='d': + return [u,v] + # must be that cs[bigind][1]=='i' + cs=cs[bigind:] + while cs: + vert=cs.pop() + if vert[1]=='d': + return [u,vert[0],v] + # All after u are type 'i' so no connection + return -1 + +def shortest_path_length(creation_sequence,i): + """ + Return the shortest path length from indicated node to + every other node for the threshold graph with the given + creation sequence. + Node is indicated by index i in creation_sequence unless + creation_sequence is labeled in which case, i is taken to + be the label of the node. + + Paths lengths in threshold graphs are at most 2. + Length to unreachable nodes is set to -1. + """ + # Turn input sequence into a labeled creation sequence + first=creation_sequence[0] + if isinstance(first,str): # creation sequence + if isinstance(creation_sequence,list): + cs = creation_sequence[:] + else: + cs = list(creation_sequence) + elif isinstance(first,tuple): # labeled creation sequence + cs = [ v[1] for v in creation_sequence] + i = [v[0] for v in creation_sequence].index(i) + elif isinstance(first,int): # compact creation sequence + cs = uncompact(creation_sequence) + else: + raise TypeError("Not a valid creation sequence type") + + # Compute + N=len(cs) + spl=[2]*N # length 2 to every node + spl[i]=0 # except self which is 0 + # 1 for all d's to the right + for j in range(i+1,N): + if cs[j]=="d": + spl[j]=1 + if cs[i]=='d': # 1 for all nodes to the left + for j in range(i): + spl[j]=1 + # and -1 for any trailing i to indicate unreachable + for j in range(N-1,0,-1): + if cs[j]=="d": + break + spl[j]=-1 + return spl + + +def betweenness_sequence(creation_sequence,normalized=True): + """ + Return betweenness for the threshold graph with the given creation + sequence. The result is unscaled. To scale the values + to the iterval [0,1] divide by (n-1)*(n-2). + """ + cs=creation_sequence + seq=[] # betweenness + lastchar='d' # first node is always a 'd' + dr=float(cs.count("d")) # number of d's to the right of curren pos + irun=0 # number of i's in the last run + drun=0 # number of d's in the last run + dlast=0.0 # betweenness of last d + for i,c in enumerate(cs): + if c=='d': #cs[i]=="d": + # betweennees = amt shared with eariler d's and i's + # + new isolated nodes covered + # + new paths to all previous nodes + b=dlast + (irun-1)*irun/dr + 2*irun*(i-drun-irun)/dr + drun+=1 # update counter + else: # cs[i]="i": + if lastchar=='d': # if this is a new run of i's + dlast=b # accumulate betweenness + dr-=drun # update number of d's to the right + drun=0 # reset d counter + irun=0 # reset i counter + b=0 # isolated nodes have zero betweenness + irun+=1 # add another i to the run + seq.append(float(b)) + lastchar=c + + # normalize by the number of possible shortest paths + if normalized: + order=len(cs) + scale=1.0/((order-1)*(order-2)) + seq=[ s*scale for s in seq ] + + return seq + + +def eigenvectors(creation_sequence): + """ + Return a 2-tuple of Laplacian eigenvalues and eigenvectors + for the threshold network with creation_sequence. + The first value is a list of eigenvalues. + The second value is a list of eigenvectors. + The lists are in the same order so corresponding eigenvectors + and eigenvalues are in the same position in the two lists. + + Notice that the order of the eigenvalues returned by eigenvalues(cs) + may not correspond to the order of these eigenvectors. + """ + ccs=make_compact(creation_sequence) + N=sum(ccs) + vec=[0]*N + val=vec[:] + # get number of type d nodes to the right (all for first node) + dr=sum(ccs[::2]) + + nn=ccs[0] + vec[0]=[1./sqrt(N)]*N + val[0]=0 + e=dr + dr-=nn + type_d=True + i=1 + dd=1 + while dd=0): + raise ValueError("p must be in [0,1]") + + cs=['d'] # threshold sequences always start with a d + for i in range(1,n): + if random.random() < p: + cs.append('d') + else: + cs.append('i') + return cs + + + + + +# maybe *_d_threshold_sequence routines should +# be (or be called from) a single routine with a more descriptive name +# and a keyword parameter? +def right_d_threshold_sequence(n,m): + """ + Create a skewed threshold graph with a given number + of vertices (n) and a given number of edges (m). + + The routine returns an unlabeled creation sequence + for the threshold graph. + + FIXME: describe algorithm + + """ + cs=['d']+['i']*(n-1) # create sequence with n insolated nodes + + # m n*(n-1)/2: + raise ValueError("Too many edges for this many nodes.") + + # connected case m >n-1 + ind=n-1 + sum=n-1 + while sum n*(n-1)/2: + raise ValueError("Too many edges for this many nodes.") + + # Connected case when M>N-1 + cs[n-1]='d' + sum=n-1 + ind=1 + while summ: # be sure not to change the first vertex + cs[sum-m]='i' + return cs + +def swap_d(cs,p_split=1.0,p_combine=1.0,seed=None): + """ + Perform a "swap" operation on a threshold sequence. + + The swap preserves the number of nodes and edges + in the graph for the given sequence. + The resulting sequence is still a threshold sequence. + + Perform one split and one combine operation on the + 'd's of a creation sequence for a threshold graph. + This operation maintains the number of nodes and edges + in the graph, but shifts the edges from node to node + maintaining the threshold quality of the graph. + """ + if not seed is None: + random.seed(seed) + + # preprocess the creation sequence + dlist= [ i for (i,node_type) in enumerate(cs[1:-1]) if node_type=='d' ] + # split + if random.random()>sys.stderr,"split at %s to %s and %s"%(choice,split_to,flip_side) + # combine + if random.random()= len(cs) or cs[target]=='d' or first_choice==second_choice: + return cs + # OK to combine + cs[first_choice]='i' + cs[second_choice]='i' + cs[target]='d' +# print >>sys.stderr,"combine %s and %s to make %s."%(first_choice,second_choice,target) + + return cs + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/__init__.py new file mode 100644 index 0000000..9f63c89 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/__init__.py @@ -0,0 +1,9 @@ +from networkx.linalg.attrmatrix import * +import networkx.linalg.attrmatrix +from networkx.linalg.spectrum import * +import networkx.linalg.spectrum +from networkx.linalg.graphmatrix import * +import networkx.linalg.graphmatrix +from networkx.linalg.laplacianmatrix import * +import networkx.linalg.laplacianmatrix + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/attrmatrix.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/attrmatrix.py new file mode 100644 index 0000000..df193bb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/attrmatrix.py @@ -0,0 +1,458 @@ +""" + Functions for constructing matrix-like objects from graph attributes. +""" + +__all__ = ['attr_matrix', 'attr_sparse_matrix'] + +import networkx as nx + +def _node_value(G, node_attr): + """Returns a function that returns a value from G.node[u]. + + We return a function expecting a node as its sole argument. Then, in the + simplest scenario, the returned function will return G.node[u][node_attr]. + However, we also handle the case when `node_attr` is None or when it is a + function itself. + + Parameters + ---------- + G : graph + A NetworkX graph + + node_attr : {None, str, callable} + Specification of how the value of the node attribute should be obtained + from the node attribute dictionary. + + Returns + ------- + value : function + A function expecting a node as its sole argument. The function will + returns a value from G.node[u] that depends on `edge_attr`. + + """ + if node_attr is None: + value = lambda u: u + elif not hasattr(node_attr, '__call__'): + # assume it is a key for the node attribute dictionary + value = lambda u: G.node[u][node_attr] + else: + # Advanced: Allow users to specify something else. + # + # For example, + # node_attr = lambda u: G.node[u].get('size', .5) * 3 + # + value = node_attr + + return value + +def _edge_value(G, edge_attr): + """Returns a function that returns a value from G[u][v]. + + Suppose there exists an edge between u and v. Then we return a function + expecting u and v as arguments. For Graph and DiGraph, G[u][v] is + the edge attribute dictionary, and the function (essentially) returns + G[u][v][edge_attr]. However, we also handle cases when `edge_attr` is None + and when it is a function itself. For MultiGraph and MultiDiGraph, G[u][v] + is a dictionary of all edges between u and v. In this case, the returned + function sums the value of `edge_attr` for every edge between u and v. + + Parameters + ---------- + G : graph + A NetworkX graph + + edge_attr : {None, str, callable} + Specification of how the value of the edge attribute should be obtained + from the edge attribute dictionary, G[u][v]. For multigraphs, G[u][v] + is a dictionary of all the edges between u and v. This allows for + special treatment of multiedges. + + Returns + ------- + value : function + A function expecting two nodes as parameters. The nodes should + represent the from- and to- node of an edge. The function will + return a value from G[u][v] that depends on `edge_attr`. + + """ + + if edge_attr is None: + # topological count of edges + + if G.is_multigraph(): + value = lambda u,v: len(G[u][v]) + else: + value = lambda u,v: 1 + + elif not hasattr(edge_attr, '__call__'): + # assume it is a key for the edge attribute dictionary + + if edge_attr == 'weight': + # provide a default value + if G.is_multigraph(): + value = lambda u,v: sum([d.get(edge_attr, 1) for d in G[u][v].values()]) + else: + value = lambda u,v: G[u][v].get(edge_attr, 1) + else: + # otherwise, the edge attribute MUST exist for each edge + if G.is_multigraph(): + value = lambda u,v: sum([d[edge_attr] for d in G[u][v].values()]) + else: + value = lambda u,v: G[u][v][edge_attr] + + else: + # Advanced: Allow users to specify something else. + # + # Alternative default value: + # edge_attr = lambda u,v: G[u][v].get('thickness', .5) + # + # Function on an attribute: + # edge_attr = lambda u,v: abs(G[u][v]['weight']) + # + # Handle Multi(Di)Graphs differently: + # edge_attr = lambda u,v: numpy.prod([d['size'] for d in G[u][v].values()]) + # + # Ignore multiple edges + # edge_attr = lambda u,v: 1 if len(G[u][v]) else 0 + # + value = edge_attr + + return value + +def attr_matrix(G, edge_attr=None, node_attr=None, normalized=False, + rc_order=None, dtype=None, order=None): + """Returns a NumPy matrix using attributes from G. + + If only `G` is passed in, then the adjacency matrix is constructed. + + Let A be a discrete set of values for the node attribute `node_attr`. Then + the elements of A represent the rows and columns of the constructed matrix. + Now, iterate through every edge e=(u,v) in `G` and consider the value + of the edge attribute `edge_attr`. If ua and va are the values of the + node attribute `node_attr` for u and v, respectively, then the value of + the edge attribute is added to the matrix element at (ua, va). + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy matrix. + + edge_attr : str, optional + Each element of the matrix represents a running total of the + specified edge attribute for edges whose node attributes correspond + to the rows/cols of the matirx. The attribute must be present for + all edges in the graph. If no attribute is specified, then we + just count the number of edges whose node attributes correspond + to the matrix element. + + node_attr : str, optional + Each row and column in the matrix represents a particular value + of the node attribute. The attribute must be present for all nodes + in the graph. Note, the values of this attribute should be reliably + hashable. So, float values are not recommended. If no attribute is + specified, then the rows and columns will be the nodes of the graph. + + normalized : bool, optional + If True, then each row is normalized by the summation of its values. + + rc_order : list, optional + A list of the node attribute values. This list specifies the ordering + of rows and columns of the array. If no ordering is provided, then + the ordering will be random (and also, a return value). + + Other Parameters + ---------------- + dtype : NumPy data-type, optional + A valid NumPy dtype used to initialize the array. Keep in mind certain + dtypes can yield unexpected results if the array is to be normalized. + The parameter is passed to numpy.zeros(). If unspecified, the NumPy + default is used. + + order : {'C', 'F'}, optional + Whether to store multidimensional data in C- or Fortran-contiguous + (row- or column-wise) order in memory. This parameter is passed to + numpy.zeros(). If unspecified, the NumPy default is used. + + Returns + ------- + M : NumPy matrix + The attribute matrix. + + ordering : list + If `rc_order` was specified, then only the matrix is returned. + However, if `rc_order` was None, then the ordering used to construct + the matrix is returned as well. + + Examples + -------- + Construct an adjacency matrix: + + >>> G = nx.Graph() + >>> G.add_edge(0,1,thickness=1,weight=3) + >>> G.add_edge(0,2,thickness=2) + >>> G.add_edge(1,2,thickness=3) + >>> nx.attr_matrix(G, rc_order=[0,1,2]) + matrix([[ 0., 1., 1.], + [ 1., 0., 1.], + [ 1., 1., 0.]]) + + Alternatively, we can obtain the matrix describing edge thickness. + + >>> nx.attr_matrix(G, edge_attr='thickness', rc_order=[0,1,2]) + matrix([[ 0., 1., 2.], + [ 1., 0., 3.], + [ 2., 3., 0.]]) + + We can also color the nodes and ask for the probability distribution over + all edges (u,v) describing: + + Pr(v has color Y | u has color X) + + >>> G.node[0]['color'] = 'red' + >>> G.node[1]['color'] = 'red' + >>> G.node[2]['color'] = 'blue' + >>> rc = ['red', 'blue'] + >>> nx.attr_matrix(G, node_attr='color', normalized=True, rc_order=rc) + matrix([[ 0.33333333, 0.66666667], + [ 1. , 0. ]]) + + For example, the above tells us that for all edges (u,v): + + Pr( v is red | u is red) = 1/3 + Pr( v is blue | u is red) = 2/3 + + Pr( v is red | u is blue) = 1 + Pr( v is blue | u is blue) = 0 + + Finally, we can obtain the total weights listed by the node colors. + + >>> nx.attr_matrix(G, edge_attr='weight', node_attr='color', rc_order=rc) + matrix([[ 3., 2.], + [ 2., 0.]]) + + Thus, the total weight over all edges (u,v) with u and v having colors: + + (red, red) is 3 # the sole contribution is from edge (0,1) + (red, blue) is 2 # contributions from edges (0,2) and (1,2) + (blue, red) is 2 # same as (red, blue) since graph is undirected + (blue, blue) is 0 # there are no edges with blue endpoints + + """ + try: + import numpy as np + except ImportError: + raise ImportError( + "attr_matrix() requires numpy: http://scipy.org/ ") + + edge_value = _edge_value(G, edge_attr) + node_value = _node_value(G, node_attr) + + if rc_order is None: + ordering = list(set([node_value(n) for n in G])) + else: + ordering = rc_order + + N = len(ordering) + undirected = not G.is_directed() + index = dict(zip(ordering, range(N))) + M = np.zeros((N,N), dtype=dtype, order=order) + + seen = set([]) + for u,nbrdict in G.adjacency_iter(): + for v in nbrdict: + # Obtain the node attribute values. + i, j = index[node_value(u)], index[node_value(v)] + if v not in seen: + M[i,j] += edge_value(u,v) + if undirected: + M[j,i] = M[i,j] + + if undirected: + seen.add(u) + + if normalized: + M /= M.sum(axis=1).reshape((N,1)) + + M = np.asmatrix(M) + + if rc_order is None: + return M, ordering + else: + return M + +def attr_sparse_matrix(G, edge_attr=None, node_attr=None, + normalized=False, rc_order=None, dtype=None): + """Returns a SciPy sparse matrix using attributes from G. + + If only `G` is passed in, then the adjacency matrix is constructed. + + Let A be a discrete set of values for the node attribute `node_attr`. Then + the elements of A represent the rows and columns of the constructed matrix. + Now, iterate through every edge e=(u,v) in `G` and consider the value + of the edge attribute `edge_attr`. If ua and va are the values of the + node attribute `node_attr` for u and v, respectively, then the value of + the edge attribute is added to the matrix element at (ua, va). + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy matrix. + + edge_attr : str, optional + Each element of the matrix represents a running total of the + specified edge attribute for edges whose node attributes correspond + to the rows/cols of the matirx. The attribute must be present for + all edges in the graph. If no attribute is specified, then we + just count the number of edges whose node attributes correspond + to the matrix element. + + node_attr : str, optional + Each row and column in the matrix represents a particular value + of the node attribute. The attribute must be present for all nodes + in the graph. Note, the values of this attribute should be reliably + hashable. So, float values are not recommended. If no attribute is + specified, then the rows and columns will be the nodes of the graph. + + normalized : bool, optional + If True, then each row is normalized by the summation of its values. + + rc_order : list, optional + A list of the node attribute values. This list specifies the ordering + of rows and columns of the array. If no ordering is provided, then + the ordering will be random (and also, a return value). + + Other Parameters + ---------------- + dtype : NumPy data-type, optional + A valid NumPy dtype used to initialize the array. Keep in mind certain + dtypes can yield unexpected results if the array is to be normalized. + The parameter is passed to numpy.zeros(). If unspecified, the NumPy + default is used. + + Returns + ------- + M : SciPy sparse matrix + The attribute matrix. + + ordering : list + If `rc_order` was specified, then only the matrix is returned. + However, if `rc_order` was None, then the ordering used to construct + the matrix is returned as well. + + Examples + -------- + Construct an adjacency matrix: + + >>> G = nx.Graph() + >>> G.add_edge(0,1,thickness=1,weight=3) + >>> G.add_edge(0,2,thickness=2) + >>> G.add_edge(1,2,thickness=3) + >>> M = nx.attr_sparse_matrix(G, rc_order=[0,1,2]) + >>> M.todense() + matrix([[ 0., 1., 1.], + [ 1., 0., 1.], + [ 1., 1., 0.]]) + + Alternatively, we can obtain the matrix describing edge thickness. + + >>> M = nx.attr_sparse_matrix(G, edge_attr='thickness', rc_order=[0,1,2]) + >>> M.todense() + matrix([[ 0., 1., 2.], + [ 1., 0., 3.], + [ 2., 3., 0.]]) + + We can also color the nodes and ask for the probability distribution over + all edges (u,v) describing: + + Pr(v has color Y | u has color X) + + >>> G.node[0]['color'] = 'red' + >>> G.node[1]['color'] = 'red' + >>> G.node[2]['color'] = 'blue' + >>> rc = ['red', 'blue'] + >>> M = nx.attr_sparse_matrix(G, node_attr='color', \ + normalized=True, rc_order=rc) + >>> M.todense() + matrix([[ 0.33333333, 0.66666667], + [ 1. , 0. ]]) + + For example, the above tells us that for all edges (u,v): + + Pr( v is red | u is red) = 1/3 + Pr( v is blue | u is red) = 2/3 + + Pr( v is red | u is blue) = 1 + Pr( v is blue | u is blue) = 0 + + Finally, we can obtain the total weights listed by the node colors. + + >>> M = nx.attr_sparse_matrix(G, edge_attr='weight',\ + node_attr='color', rc_order=rc) + >>> M.todense() + matrix([[ 3., 2.], + [ 2., 0.]]) + + Thus, the total weight over all edges (u,v) with u and v having colors: + + (red, red) is 3 # the sole contribution is from edge (0,1) + (red, blue) is 2 # contributions from edges (0,2) and (1,2) + (blue, red) is 2 # same as (red, blue) since graph is undirected + (blue, blue) is 0 # there are no edges with blue endpoints + + """ + try: + import numpy as np + from scipy import sparse + except ImportError: + raise ImportError( + "attr_sparse_matrix() requires scipy: http://scipy.org/ ") + + edge_value = _edge_value(G, edge_attr) + node_value = _node_value(G, node_attr) + + if rc_order is None: + ordering = list(set([node_value(n) for n in G])) + else: + ordering = rc_order + + N = len(ordering) + undirected = not G.is_directed() + index = dict(zip(ordering, range(N))) + M = sparse.lil_matrix((N,N), dtype=dtype) + + seen = set([]) + for u,nbrdict in G.adjacency_iter(): + for v in nbrdict: + # Obtain the node attribute values. + i, j = index[node_value(u)], index[node_value(v)] + if v not in seen: + M[i,j] += edge_value(u,v) + if undirected: + M[j,i] = M[i,j] + + if undirected: + seen.add(u) + + if normalized: + norms = np.asarray(M.sum(axis=1)).ravel() + for i,norm in enumerate(norms): + M[i,:] /= norm + + if rc_order is None: + return M, ordering + else: + return M + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") + try: + import scipy + except: + raise SkipTest("SciPy not available") + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/graphmatrix.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/graphmatrix.py new file mode 100644 index 0000000..c677619 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/graphmatrix.py @@ -0,0 +1,156 @@ +""" +Adjacency matrix and incidence matrix of graphs. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) + +__all__ = ['incidence_matrix', + 'adj_matrix', 'adjacency_matrix', + ] + + +def incidence_matrix(G, nodelist=None, edgelist=None, + oriented=False, weight=None): + """Return incidence matrix of G. + + The incidence matrix assigns each row to a node and each column to an edge. + For a standard incidence matrix a 1 appears wherever a row's node is + incident on the column's edge. For an oriented incidence matrix each + edge is assigned an orientation (arbitrarily for undirected and aligning to + direction for directed). A -1 appears for the tail of an edge and 1 + for the head of the edge. The elements are zero otherwise. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list, optional (default= all nodes in G) + The rows are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + edgelist : list, optional (default= all edges in G) + The columns are ordered according to the edges in edgelist. + If edgelist is None, then the ordering is produced by G.edges(). + + oriented: bool, optional (default=False) + If True, matrix elements are +1 or -1 for the head or tail node + respectively of each edge. If False, +1 occurs at both nodes. + + weight : string or None, optional (default=None) + The edge data key used to provide each value in the matrix. + If None, then each edge has weight 1. Edge weights, if used, + should be positive so that the orientation can provide the sign. + + Returns + ------- + A : NumPy matrix + The incidence matrix of G. + + Notes + ----- + For MultiGraph/MultiDiGraph, the edges in edgelist should be + (u,v,key) 3-tuples. + + "Networks are the best discrete model for so many problems in + applied mathematics" [1]_. + + References + ---------- + .. [1] Gil Strang, Network applications: A = incidence matrix, + http://academicearth.org/lectures/network-applications-incidence-matrix + """ + try: + import numpy as np + except ImportError: + raise ImportError( + "incidence_matrix() requires numpy: http://scipy.org/ ") + if nodelist is None: + nodelist = G.nodes() + if edgelist is None: + if G.is_multigraph(): + edgelist = G.edges(keys=True) + else: + edgelist = G.edges() + A = np.zeros((len(nodelist),len(edgelist))) + node_index = dict( (node,i) for i,node in enumerate(nodelist) ) + for ei,e in enumerate(edgelist): + (u,v) = e[:2] + if u == v: continue # self loops give zero column + try: + ui = node_index[u] + vi = node_index[v] + except KeyError: + raise NetworkXError('node %s or %s in edgelist ' + 'but not in nodelist"%(u,v)') + if weight is None: + wt = 1 + else: + if G.is_multigraph(): + ekey = e[2] + wt = G[u][v][ekey].get(weight,1) + else: + wt = G[u][v].get(weight,1) + if oriented: + A[ui,ei] = -wt + A[vi,ei] = wt + else: + A[ui,ei] = wt + A[vi,ei] = wt + return np.asmatrix(A) + +def adjacency_matrix(G, nodelist=None, weight='weight'): + """Return adjacency matrix of G. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list, optional + The rows and columns are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + weight : string or None, optional (default='weight') + The edge data key used to provide each value in the matrix. + If None, then each edge has weight 1. + + Returns + ------- + A : numpy matrix + Adjacency matrix representation of G. + + Notes + ----- + If you want a pure Python adjacency matrix representation try + networkx.convert.to_dict_of_dicts which will return a + dictionary-of-dictionaries format that can be addressed as a + sparse matrix. + + For MultiGraph/MultiDiGraph, the edges weights are summed. + See to_numpy_matrix for other options. + + See Also + -------- + to_numpy_matrix + to_dict_of_dicts + """ + return nx.to_numpy_matrix(G,nodelist=nodelist,weight=weight) + +adj_matrix=adjacency_matrix + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/laplacianmatrix.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/laplacianmatrix.py new file mode 100644 index 0000000..ffb256c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/laplacianmatrix.py @@ -0,0 +1,277 @@ +""" +Laplacian matrix of graphs. +""" +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import require, not_implemented_for + +__author__ = "\n".join(['Aric Hagberg ', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult (dschult@colgate.edu)', + 'Alejandro Weinstein ']) + +__all__ = ['laplacian_matrix', + 'normalized_laplacian_matrix', + 'directed_laplacian_matrix'] + +@require('numpy') +@not_implemented_for('directed') +def laplacian_matrix(G, nodelist=None, weight='weight'): + """Return the Laplacian matrix of G. + + The graph Laplacian is the matrix L = D - A, where + A is the adjacency matrix and D is the diagonal matrix of node degrees. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list, optional + The rows and columns are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + weight : string or None, optional (default='weight') + The edge data key used to compute each value in the matrix. + If None, then each edge has weight 1. + + Returns + ------- + L : NumPy matrix + The Laplacian matrix of G. + + Notes + ----- + For MultiGraph/MultiDiGraph, the edges weights are summed. + See to_numpy_matrix for other options. + + See Also + -------- + to_numpy_matrix + normalized_laplacian_matrix + """ + import numpy as np + if nodelist is None: + nodelist = G.nodes() + if G.is_multigraph(): + # this isn't the fastest way to do this... + A = np.asarray(nx.to_numpy_matrix(G,nodelist=nodelist,weight=weight)) + I = np.identity(A.shape[0]) + D = I*np.sum(A,axis=1) + L = D - A + else: + # Graph or DiGraph, this is faster than above + n = len(nodelist) + index = dict( (n,i) for i,n in enumerate(nodelist) ) + L = np.zeros((n,n)) + for ui,u in enumerate(nodelist): + totalwt = 0.0 + for v,d in G[u].items(): + try: + vi = index[v] + except KeyError: + continue + wt = d.get(weight,1) + L[ui,vi] = -wt + totalwt += wt + L[ui,ui] = totalwt + return np.asmatrix(L) + +@require('numpy') +@not_implemented_for('directed') +def normalized_laplacian_matrix(G, nodelist=None, weight='weight'): + r"""Return the normalized Laplacian matrix of G. + + The normalized graph Laplacian is the matrix + + .. math:: + + NL = D^{-1/2} L D^{-1/2} + + where `L` is the graph Laplacian and `D` is the diagonal matrix of + node degrees. + + Parameters + ---------- + G : graph + A NetworkX graph + + nodelist : list, optional + The rows and columns are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + weight : string or None, optional (default='weight') + The edge data key used to compute each value in the matrix. + If None, then each edge has weight 1. + + Returns + ------- + L : NumPy matrix + The normalized Laplacian matrix of G. + + Notes + ----- + For MultiGraph/MultiDiGraph, the edges weights are summed. + See to_numpy_matrix for other options. + + If the Graph contains selfloops, D is defined as diag(sum(A,1)), where A is + the adjencency matrix [2]_. + + See Also + -------- + laplacian_matrix + + References + ---------- + .. [1] Fan Chung-Graham, Spectral Graph Theory, + CBMS Regional Conference Series in Mathematics, Number 92, 1997. + .. [2] Steve Butler, Interlacing For Weighted Graphs Using The Normalized + Laplacian, Electronic Journal of Linear Algebra, Volume 16, pp. 90-98, + March 2007. + """ + import numpy as np + if G.is_multigraph(): + L = laplacian_matrix(G, nodelist=nodelist, weight=weight) + D = np.diag(L) + elif G.number_of_selfloops() == 0: + L = laplacian_matrix(G, nodelist=nodelist, weight=weight) + D = np.diag(L) + else: + A = np.array(nx.adj_matrix(G)) + D = np.sum(A, 1) + L = np.diag(D) - A + + # Handle div by 0. It happens if there are unconnected nodes + with np.errstate(divide='ignore'): + Disqrt = np.diag(1 / np.sqrt(D)) + Disqrt[np.isinf(Disqrt)] = 0 + Ln = np.dot(Disqrt, np.dot(L,Disqrt)) + return Ln + +############################################################################### +# Code based on +# https://bitbucket.org/bedwards/networkx-community/src/370bd69fc02f/networkx/algorithms/community/ + +@require('numpy') +@not_implemented_for('undirected') +@not_implemented_for('multigraph') +def directed_laplacian_matrix(G, nodelist=None, weight='weight', + walk_type=None, alpha=0.95): + r"""Return the directed Laplacian matrix of G. + + The graph directed Laplacian is the matrix + + .. math:: + + L = I - (\Phi^{1/2} P \Phi^{-1/2} + \Phi^{-1/2} P^T \Phi^{1/2} ) / 2 + + where `I` is the identity matrix, `P` is the transition matrix of the + graph, and `\Phi` a matrix with the Perron vector of `P` in the diagonal and + zeros elsewhere. + + Depending on the value of walk_type, `P` can be the transition matrix + induced by a random walk, a lazy random walk, or a random walk with + teleportation (PageRank). + + Parameters + ---------- + G : DiGraph + A NetworkX graph + + nodelist : list, optional + The rows and columns are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + weight : string or None, optional (default='weight') + The edge data key used to compute each value in the matrix. + If None, then each edge has weight 1. + + walk_type : string or None, optional (default=None) + If None, `P` is selected depending on the properties of the + graph. Otherwise is one of 'random', 'lazy', or 'pagerank' + + alpha : real + (1 - alpha) is the teleportation probability used with pagerank + + Returns + ------- + L : NumPy array + Normalized Laplacian of G. + + Raises + ------ + NetworkXError + If NumPy cannot be imported + + NetworkXNotImplemnted + If G is not a DiGraph + + Notes + ----- + Only implemented for DiGraphs + + See Also + -------- + laplacian_matrix + + References + ---------- + .. [1] Fan Chung (2005). + Laplacians and the Cheeger inequality for directed graphs. + Annals of Combinatorics, 9(1), 2005 + """ + import numpy as np + if walk_type is None: + if nx.is_strongly_connected(G): + if nx.is_aperiodic(G): + walk_type = "random" + else: + walk_type = "lazy" + else: + walk_type = "pagerank" + + M = nx.to_numpy_matrix(G, nodelist=nodelist, weight=weight) + n, m = M.shape + if walk_type in ["random", "lazy"]: + DI = np.diagflat(1.0 / np.sum(M, axis=1)) + if walk_type == "random": + P = DI * M + else: + I = np.identity(n) + P = (I + DI * M) / 2.0 + elif walk_type == "pagerank": + if not (0 < alpha < 1): + raise nx.NetworkXError('alpha must be between 0 and 1') + # add constant to dangling nodes' row + dangling = np.where(M.sum(axis=1) == 0) + for d in dangling[0]: + M[d] = 1.0 / n + # normalize + M = M / M.sum(axis=1) + P = alpha * M + (1 - alpha) / n + else: + raise nx.NetworkXError("walk_type must be random, lazy, or pagerank") + + evals, evecs = np.linalg.eig(P.T) + index = evals.argsort()[-1] # index of largest eval,evec + # eigenvector of largest eigenvalue at ind[-1] + v = np.array(evecs[:,index]).flatten().real + p = v / v.sum() + sp = np.sqrt(p) + Q = np.diag(sp) * P * np.diag(1.0/sp) + I = np.identity(len(G)) + + return I - (Q + Q.T) /2.0 + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/spectrum.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/spectrum.py new file mode 100644 index 0000000..bca7288 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/spectrum.py @@ -0,0 +1,90 @@ +""" +Eigenvalue spectrum of graphs. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = "\n".join(['Aric Hagberg (hagberg@lanl.gov)', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)']) + +__all__ = ['laplacian_spectrum', 'adjacency_spectrum'] + + +def laplacian_spectrum(G, weight='weight'): + """Return eigenvalues of the Laplacian of G + + Parameters + ---------- + G : graph + A NetworkX graph + + weight : string or None, optional (default='weight') + The edge data key used to compute each value in the matrix. + If None, then each edge has weight 1. + + Returns + ------- + evals : NumPy array + Eigenvalues + + Notes + ----- + For MultiGraph/MultiDiGraph, the edges weights are summed. + See to_numpy_matrix for other options. + + See Also + -------- + laplacian_matrix + """ + try: + import numpy as np + except ImportError: + raise ImportError( + "laplacian_spectrum() requires NumPy: http://scipy.org/ ") + return np.linalg.eigvals(nx.laplacian_matrix(G,weight=weight)) + +def adjacency_spectrum(G, weight='weight'): + """Return eigenvalues of the adjacency matrix of G. + + Parameters + ---------- + G : graph + A NetworkX graph + + weight : string or None, optional (default='weight') + The edge data key used to compute each value in the matrix. + If None, then each edge has weight 1. + + Returns + ------- + evals : NumPy array + Eigenvalues + + Notes + ----- + For MultiGraph/MultiDiGraph, the edges weights are summed. + See to_numpy_matrix for other options. + + See Also + -------- + adjacency_matrix + """ + try: + import numpy as np + except ImportError: + raise ImportError( + "adjacency_spectrum() requires NumPy: http://scipy.org/ ") + return np.linalg.eigvals(nx.adjacency_matrix(G,weight=weight)) + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import numpy + except: + raise SkipTest("NumPy not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_graphmatrix.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_graphmatrix.py new file mode 100644 index 0000000..bba234e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_graphmatrix.py @@ -0,0 +1,89 @@ +from nose import SkipTest + +import networkx as nx +from networkx.generators.degree_seq import havel_hakimi_graph + +class TestGraphMatrix(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global numpy + global assert_equal + global assert_almost_equal + try: + import numpy + from numpy.testing import assert_equal,assert_almost_equal + except ImportError: + raise SkipTest('NumPy not available.') + + def setUp(self): + deg=[3,2,2,1,0] + self.G=havel_hakimi_graph(deg) + self.OI=numpy.array([[-1, -1, -1, 0], + [1, 0, 0, -1], + [0, 1, 0, 1], + [0, 0, 1, 0], + [0, 0, 0, 0]]) + self.A=numpy.array([[0, 1, 1, 1, 0], + [1, 0, 1, 0, 0], + [1, 1, 0, 0, 0], + [1, 0, 0, 0, 0], + [0, 0, 0, 0, 0]]) + self.WG=nx.Graph( (u,v,{'weight':0.5,'other':0.3}) + for (u,v) in self.G.edges_iter() ) + self.WG.add_node(4) + self.WA=numpy.array([[0 , 0.5, 0.5, 0.5, 0], + [0.5, 0 , 0.5, 0 , 0], + [0.5, 0.5, 0 , 0 , 0], + [0.5, 0 , 0 , 0 , 0], + [0 , 0 , 0 , 0 , 0]]) + self.MG=nx.MultiGraph(self.G) + self.MG2=self.MG.copy() + self.MG2.add_edge(0,1) + self.MG2A=numpy.array([[0, 2, 1, 1, 0], + [2, 0, 1, 0, 0], + [1, 1, 0, 0, 0], + [1, 0, 0, 0, 0], + [0, 0, 0, 0, 0]]) + self.MGOI=numpy.array([[-1, -1, -1, -1, 0], + [1, 1, 0, 0, -1], + [0, 0, 1, 0, 1], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 0]]) + + def test_incidence_matrix(self): + "Conversion to incidence matrix" + assert_equal(nx.incidence_matrix(self.G,oriented=True),self.OI) + assert_equal(nx.incidence_matrix(self.G),numpy.abs(self.OI)) + assert_equal(nx.incidence_matrix(self.MG,oriented=True),self.OI) + assert_equal(nx.incidence_matrix(self.MG),numpy.abs(self.OI)) + assert_equal(nx.incidence_matrix(self.MG2,oriented=True),self.MGOI) + assert_equal(nx.incidence_matrix(self.MG2),numpy.abs(self.MGOI)) + assert_equal(nx.incidence_matrix(self.WG,oriented=True),self.OI) + assert_equal(nx.incidence_matrix(self.WG),numpy.abs(self.OI)) + assert_equal(nx.incidence_matrix(self.WG,oriented=True, + weight='weight'),0.5*self.OI) + assert_equal(nx.incidence_matrix(self.WG,weight='weight'), + numpy.abs(0.5*self.OI)) + assert_equal(nx.incidence_matrix(self.WG,oriented=True,weight='other'), + 0.3*self.OI) + WMG=nx.MultiGraph(self.WG) + WMG.add_edge(0,1,attr_dict={'weight':0.5,'other':0.3}) + assert_equal(nx.incidence_matrix(WMG,weight='weight'), + numpy.abs(0.5*self.MGOI)) + assert_equal(nx.incidence_matrix(WMG,weight='weight',oriented=True), + 0.5*self.MGOI) + assert_equal(nx.incidence_matrix(WMG,weight='other',oriented=True), + 0.3*self.MGOI) + + def test_adjacency_matrix(self): + "Conversion to adjacency matrix" + assert_equal(nx.adj_matrix(self.G),self.A) + assert_equal(nx.adj_matrix(self.MG),self.A) + assert_equal(nx.adj_matrix(self.MG2),self.MG2A) + assert_equal(nx.adj_matrix(self.G,nodelist=[0,1]),self.A[:2,:2]) + assert_equal(nx.adj_matrix(self.WG),self.WA) + assert_equal(nx.adj_matrix(self.WG,weight=None),self.A) + assert_equal(nx.adj_matrix(self.MG2,weight=None),self.MG2A) + assert_equal(nx.adj_matrix(self.WG,weight='other'),0.6*self.WA) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_laplacian.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_laplacian.py new file mode 100644 index 0000000..87725fe --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_laplacian.py @@ -0,0 +1,101 @@ +from nose import SkipTest + +import networkx as nx +from networkx.generators.degree_seq import havel_hakimi_graph + +class TestLaplacian(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global numpy + global assert_equal + global assert_almost_equal + try: + import numpy + from numpy.testing import assert_equal,assert_almost_equal + except ImportError: + raise SkipTest('NumPy not available.') + + def setUp(self): + deg=[3,2,2,1,0] + self.G=havel_hakimi_graph(deg) + self.WG=nx.Graph( (u,v,{'weight':0.5,'other':0.3}) + for (u,v) in self.G.edges_iter() ) + self.WG.add_node(4) + self.MG=nx.MultiGraph(self.G) + + # Graph with selfloops + self.Gsl = self.G.copy() + for node in self.Gsl.nodes(): + self.Gsl.add_edge(node, node) + + + def test_laplacian(self): + "Graph Laplacian" + NL=numpy.array([[ 3, -1, -1, -1, 0], + [-1, 2, -1, 0, 0], + [-1, -1, 2, 0, 0], + [-1, 0, 0, 1, 0], + [ 0, 0, 0, 0, 0]]) + WL=0.5*NL + OL=0.3*NL + assert_equal(nx.laplacian_matrix(self.G),NL) + assert_equal(nx.laplacian_matrix(self.MG),NL) + assert_equal(nx.laplacian_matrix(self.G,nodelist=[0,1]), + numpy.array([[ 1, -1],[-1, 1]])) + assert_equal(nx.laplacian_matrix(self.WG),WL) + assert_equal(nx.laplacian_matrix(self.WG,weight=None),NL) + assert_equal(nx.laplacian_matrix(self.WG,weight='other'),OL) + + def test_normalized_laplacian(self): + "Generalized Graph Laplacian" + GL=numpy.array([[ 1.00, -0.408, -0.408, -0.577, 0.00], + [-0.408, 1.00, -0.50, 0.00 , 0.00], + [-0.408, -0.50, 1.00, 0.00, 0.00], + [-0.577, 0.00, 0.00, 1.00, 0.00], + [ 0.00, 0.00, 0.00, 0.00, 0.00]]) + Lsl = numpy.array([[ 0.75 , -0.2887, -0.2887, -0.3536, 0.], + [-0.2887, 0.6667, -0.3333, 0. , 0.], + [-0.2887, -0.3333, 0.6667, 0. , 0.], + [-0.3536, 0. , 0. , 0.5 , 0.], + [ 0. , 0. , 0. , 0. , 0.]]) + + assert_almost_equal(nx.normalized_laplacian_matrix(self.G),GL,decimal=3) + assert_almost_equal(nx.normalized_laplacian_matrix(self.MG),GL,decimal=3) + assert_almost_equal(nx.normalized_laplacian_matrix(self.WG),GL,decimal=3) + assert_almost_equal(nx.normalized_laplacian_matrix(self.WG,weight='other'),GL,decimal=3) + assert_almost_equal(nx.normalized_laplacian_matrix(self.Gsl), Lsl, decimal=3) + + def test_directed_laplacian(self): + "Directed Laplacian" + # Graph used as an example in Sec. 4.1 of Langville and Meyer, + # "Google's PageRank and Beyond". The graph contains dangling nodes, so + # the pagerank random walk is selected by directed_laplacian + G = nx.DiGraph() + G.add_edges_from(((1,2), (1,3), (3,1), (3,2), (3,5), (4,5), (4,6), + (5,4), (5,6), (6,4))) + GL = numpy.array([[ 0.9833, -0.2941, -0.3882, -0.0291, -0.0231, -0.0261], + [-0.2941, 0.8333, -0.2339, -0.0536, -0.0589, -0.0554], + [-0.3882, -0.2339, 0.9833, -0.0278, -0.0896, -0.0251], + [-0.0291, -0.0536, -0.0278, 0.9833, -0.4878, -0.6675], + [-0.0231, -0.0589, -0.0896, -0.4878, 0.9833, -0.2078], + [-0.0261, -0.0554, -0.0251, -0.6675, -0.2078, 0.9833]]) + assert_almost_equal(nx.directed_laplacian_matrix(G, alpha=0.9), GL, decimal=3) + + # Make the graph strongly connected, so we can use a random and lazy walk + G.add_edges_from((((2,5), (6,1)))) + GL = numpy.array([[ 1. , -0.3062, -0.4714, 0. , 0. , -0.3227], + [-0.3062, 1. , -0.1443, 0. , -0.3162, 0. ], + [-0.4714, -0.1443, 1. , 0. , -0.0913, 0. ], + [ 0. , 0. , 0. , 1. , -0.5 , -0.5 ], + [ 0. , -0.3162, -0.0913, -0.5 , 1. , -0.25 ], + [-0.3227, 0. , 0. , -0.5 , -0.25 , 1. ]]) + assert_almost_equal(nx.directed_laplacian_matrix(G, walk_type='random'), GL, decimal=3) + + GL = numpy.array([[ 0.5 , -0.1531, -0.2357, 0. , 0. , -0.1614], + [-0.1531, 0.5 , -0.0722, 0. , -0.1581, 0. ], + [-0.2357, -0.0722, 0.5 , 0. , -0.0456, 0. ], + [ 0. , 0. , 0. , 0.5 , -0.25 , -0.25 ], + [ 0. , -0.1581, -0.0456, -0.25 , 0.5 , -0.125 ], + [-0.1614, 0. , 0. , -0.25 , -0.125 , 0.5 ]]) + assert_almost_equal(nx.directed_laplacian_matrix(G, walk_type='lazy'), GL, decimal=3) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_spectrum.py b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_spectrum.py new file mode 100644 index 0000000..a2961cb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/linalg/tests/test_spectrum.py @@ -0,0 +1,44 @@ +from nose import SkipTest + +import networkx as nx +from networkx.generators.degree_seq import havel_hakimi_graph + +class TestSpectrum(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global numpy + global assert_equal + global assert_almost_equal + try: + import numpy + from numpy.testing import assert_equal,assert_almost_equal + except ImportError: + raise SkipTest('NumPy not available.') + + def setUp(self): + deg=[3,2,2,1,0] + self.G=havel_hakimi_graph(deg) + self.P=nx.path_graph(3) + self.WG=nx.Graph( (u,v,{'weight':0.5,'other':0.3}) + for (u,v) in self.G.edges_iter() ) + self.WG.add_node(4) + + def test_laplacian_spectrum(self): + "Laplacian eigenvalues" + evals=numpy.array([0, 0, 1, 3, 4]) + e=sorted(nx.laplacian_spectrum(self.G)) + assert_almost_equal(e,evals) + e=sorted(nx.laplacian_spectrum(self.WG,weight=None)) + assert_almost_equal(e,evals) + e=sorted(nx.laplacian_spectrum(self.WG)) + assert_almost_equal(e,0.5*evals) + e=sorted(nx.laplacian_spectrum(self.WG,weight='other')) + assert_almost_equal(e,0.3*evals) + + def test_adjacency_spectrum(self): + "Adjacency eigenvalues" + evals=numpy.array([-numpy.sqrt(2), 0, numpy.sqrt(2)]) + e=sorted(nx.adjacency_spectrum(self.P)) + assert_almost_equal(e,evals) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/__init__.py new file mode 100644 index 0000000..c806cd0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/__init__.py @@ -0,0 +1,16 @@ +""" +A package for reading and writing graphs in various formats. + +""" +from networkx.readwrite.adjlist import * +from networkx.readwrite.multiline_adjlist import * +from networkx.readwrite.edgelist import * +from networkx.readwrite.gpickle import * +from networkx.readwrite.pajek import * +from networkx.readwrite.leda import * +from networkx.readwrite.sparsegraph6 import * +from networkx.readwrite.nx_yaml import * +from networkx.readwrite.gml import * +from networkx.readwrite.graphml import * +from networkx.readwrite.gexf import * +from networkx.readwrite.nx_shp import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/adjlist.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/adjlist.py new file mode 100644 index 0000000..57f1e24 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/adjlist.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +""" +************** +Adjacency List +************** +Read and write NetworkX graphs as adjacency lists. + +Adjacency list format is useful for graphs without data associated +with nodes or edges and for nodes that can be meaningfully represented +as strings. + +Format +------ +The adjacency list format consists of lines with node labels. The +first label in a line is the source node. Further labels in the line +are considered target nodes and are added to the graph along with an edge +between the source node and target node. + +The graph with edges a-b, a-c, d-e can be represented as the following +adjacency list (anything following the # in a line is a comment):: + + a b c # source target target + d e +""" +__author__ = '\n'.join(['Aric Hagberg ', + 'Dan Schult ', + 'Loïc Séguin-C. ']) +# Copyright (C) 2004-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['generate_adjlist', + 'write_adjlist', + 'parse_adjlist', + 'read_adjlist'] + +from networkx.utils import make_str, open_file +import networkx as nx + + +def generate_adjlist(G, delimiter = ' '): + """Generate a single line of the graph G in adjacency list format. + + Parameters + ---------- + G : NetworkX graph + + delimiter : string, optional + Separator for node labels + + Returns + ------- + lines : string + Lines of data in adjlist format. + + Examples + -------- + >>> G = nx.lollipop_graph(4, 3) + >>> for line in nx.generate_adjlist(G): + ... print(line) + 0 1 2 3 + 1 2 3 + 2 3 + 3 4 + 4 5 + 5 6 + 6 + + See Also + -------- + write_adjlist, read_adjlist + + """ + directed=G.is_directed() + seen=set() + for s,nbrs in G.adjacency_iter(): + line = make_str(s)+delimiter + for t,data in nbrs.items(): + if not directed and t in seen: + continue + if G.is_multigraph(): + for d in data.values(): + line += make_str(t) + delimiter + else: + line += make_str(t) + delimiter + if not directed: + seen.add(s) + yield line[:-len(delimiter)] + +@open_file(1,mode='wb') +def write_adjlist(G, path, comments="#", delimiter=' ', encoding = 'utf-8'): + """Write graph G in single-line adjacency-list format to path. + + + Parameters + ---------- + G : NetworkX graph + + path : string or file + Filename or file handle for data output. + Filenames ending in .gz or .bz2 will be compressed. + + comments : string, optional + Marker for comment lines + + delimiter : string, optional + Separator for node labels + + encoding : string, optional + Text encoding. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_adjlist(G,"test.adjlist") + + The path can be a filehandle or a string with the name of the file. If a + filehandle is provided, it has to be opened in 'wb' mode. + + >>> fh=open("test.adjlist",'wb') + >>> nx.write_adjlist(G, fh) + + Notes + ----- + This format does not store graph, node, or edge data. + + See Also + -------- + read_adjlist, generate_adjlist + """ + import sys + import time + pargs=comments + " ".join(sys.argv) + '\n' + header = (pargs + + comments + " GMT %s\n" % (time.asctime(time.gmtime())) + + comments + " %s\n" % (G.name)) + path.write(header.encode(encoding)) + + for line in generate_adjlist(G, delimiter): + line+='\n' + path.write(line.encode(encoding)) + + +def parse_adjlist(lines, comments = '#', delimiter = None, + create_using = None, nodetype = None): + """Parse lines of a graph adjacency list representation. + + Parameters + ---------- + lines : list or iterator of strings + Input data in adjlist format + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + nodetype : Python type, optional + Convert nodes to this type. + + comments : string, optional + Marker for comment lines + + delimiter : string, optional + Separator for node labels. The default is whitespace. + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + + Returns + ------- + G: NetworkX graph + The graph corresponding to the lines in adjacency list format. + + Examples + -------- + >>> lines = ['1 2 5', + ... '2 3 4', + ... '3 5', + ... '4', + ... '5'] + >>> G = nx.parse_adjlist(lines, nodetype = int) + >>> G.nodes() + [1, 2, 3, 4, 5] + >>> G.edges() + [(1, 2), (1, 5), (2, 3), (2, 4), (3, 5)] + + See Also + -------- + read_adjlist + + """ + if create_using is None: + G=nx.Graph() + else: + try: + G=create_using + G.clear() + except: + raise TypeError("Input graph is not a NetworkX graph type") + + for line in lines: + p=line.find(comments) + if p>=0: + line = line[:p] + if not len(line): + continue + vlist=line.strip().split(delimiter) + u=vlist.pop(0) + # convert types + if nodetype is not None: + try: + u=nodetype(u) + except: + raise TypeError("Failed to convert node (%s) to type %s"\ + %(u,nodetype)) + G.add_node(u) + if nodetype is not None: + try: + vlist=map(nodetype,vlist) + except: + raise TypeError("Failed to convert nodes (%s) to type %s"\ + %(','.join(vlist),nodetype)) + G.add_edges_from([(u, v) for v in vlist]) + return G + +@open_file(0,mode='rb') +def read_adjlist(path, comments="#", delimiter=None, create_using=None, + nodetype=None, encoding = 'utf-8'): + """Read graph in adjacency list format from path. + + Parameters + ---------- + path : string or file + Filename or file handle to read. + Filenames ending in .gz or .bz2 will be uncompressed. + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + nodetype : Python type, optional + Convert nodes to this type. + + comments : string, optional + Marker for comment lines + + delimiter : string, optional + Separator for node labels. The default is whitespace. + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + + Returns + ------- + G: NetworkX graph + The graph corresponding to the lines in adjacency list format. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_adjlist(G, "test.adjlist") + >>> G=nx.read_adjlist("test.adjlist") + + The path can be a filehandle or a string with the name of the file. If a + filehandle is provided, it has to be opened in 'rb' mode. + + >>> fh=open("test.adjlist", 'rb') + >>> G=nx.read_adjlist(fh) + + Filenames ending in .gz or .bz2 will be compressed. + + >>> nx.write_adjlist(G,"test.adjlist.gz") + >>> G=nx.read_adjlist("test.adjlist.gz") + + The optional nodetype is a function to convert node strings to nodetype. + + For example + + >>> G=nx.read_adjlist("test.adjlist", nodetype=int) + + will attempt to convert all nodes to integer type. + + Since nodes must be hashable, the function nodetype must return hashable + types (e.g. int, float, str, frozenset - or tuples of those, etc.) + + The optional create_using parameter is a NetworkX graph container. + The default is Graph(), an undirected graph. To read the data as + a directed graph use + + >>> G=nx.read_adjlist("test.adjlist", create_using=nx.DiGraph()) + + Notes + ----- + This format does not store graph or node data. + + See Also + -------- + write_adjlist + """ + lines = (line.decode(encoding) for line in path) + return parse_adjlist(lines, + comments = comments, + delimiter = delimiter, + create_using = create_using, + nodetype = nodetype) + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.adjlist') + os.unlink('test.adjlist.gz') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/edgelist.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/edgelist.py new file mode 100644 index 0000000..4a1aea9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/edgelist.py @@ -0,0 +1,464 @@ +""" +********** +Edge Lists +********** +Read and write NetworkX graphs as edge lists. + +The multi-line adjacency list format is useful for graphs with nodes +that can be meaningfully represented as strings. With the edgelist +format simple edge data can be stored but node or graph data is not. +There is no way of representing isolated nodes unless the node has a +self-loop edge. + +Format +------ +You can read or write three formats of edge lists with these functions. + +Node pairs with no data:: + + 1 2 + +Python dictionary as data:: + + 1 2 {'weight':7, 'color':'green'} + +Arbitrary data:: + + 1 2 7 green +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nDan Schult (dschult@colgate.edu)""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['generate_edgelist', + 'write_edgelist', + 'parse_edgelist', + 'read_edgelist', + 'read_weighted_edgelist', + 'write_weighted_edgelist'] + +from networkx.utils import open_file, make_str +import networkx as nx + +def generate_edgelist(G, delimiter=' ', data=True): + """Generate a single line of the graph G in edge list format. + + Parameters + ---------- + G : NetworkX graph + + delimiter : string, optional + Separator for node labels + + data : bool or list of keys + If False generate no edge data. If True use a dictionary + representation of edge data. If a list of keys use a list of data + values corresponding to the keys. + + Returns + ------- + lines : string + Lines of data in adjlist format. + + Examples + -------- + >>> G = nx.lollipop_graph(4, 3) + >>> G[1][2]['weight'] = 3 + >>> G[3][4]['capacity'] = 12 + >>> for line in nx.generate_edgelist(G, data=False): + ... print(line) + 0 1 + 0 2 + 0 3 + 1 2 + 1 3 + 2 3 + 3 4 + 4 5 + 5 6 + + >>> for line in nx.generate_edgelist(G): + ... print(line) + 0 1 {} + 0 2 {} + 0 3 {} + 1 2 {'weight': 3} + 1 3 {} + 2 3 {} + 3 4 {'capacity': 12} + 4 5 {} + 5 6 {} + + >>> for line in nx.generate_edgelist(G,data=['weight']): + ... print(line) + 0 1 + 0 2 + 0 3 + 1 2 3 + 1 3 + 2 3 + 3 4 + 4 5 + 5 6 + + See Also + -------- + write_adjlist, read_adjlist + """ + if data is True or data is False: + for e in G.edges(data=data): + yield delimiter.join(map(make_str,e)) + else: + for u,v,d in G.edges(data=True): + e=[u,v] + try: + e.extend(d[k] for k in data) + except KeyError: + pass # missing data for this edge, should warn? + yield delimiter.join(map(make_str,e)) + +@open_file(1,mode='wb') +def write_edgelist(G, path, comments="#", delimiter=' ', data=True, + encoding = 'utf-8'): + """Write graph as a list of edges. + + Parameters + ---------- + G : graph + A NetworkX graph + path : file or string + File or filename to write. If a file is provided, it must be + opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed. + comments : string, optional + The character used to indicate the start of a comment + delimiter : string, optional + The string used to separate values. The default is whitespace. + data : bool or list, optional + If False write no edge data. + If True write a string representation of the edge data dictionary.. + If a list (or other iterable) is provided, write the keys specified + in the list. + encoding: string, optional + Specify which encoding to use when writing file. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_edgelist(G, "test.edgelist") + >>> G=nx.path_graph(4) + >>> fh=open("test.edgelist",'wb') + >>> nx.write_edgelist(G, fh) + >>> nx.write_edgelist(G, "test.edgelist.gz") + >>> nx.write_edgelist(G, "test.edgelist.gz", data=False) + + >>> G=nx.Graph() + >>> G.add_edge(1,2,weight=7,color='red') + >>> nx.write_edgelist(G,'test.edgelist',data=False) + >>> nx.write_edgelist(G,'test.edgelist',data=['color']) + >>> nx.write_edgelist(G,'test.edgelist',data=['color','weight']) + + See Also + -------- + write_edgelist() + write_weighted_edgelist() + """ + + for line in generate_edgelist(G, delimiter, data): + line+='\n' + path.write(line.encode(encoding)) + +def parse_edgelist(lines, comments='#', delimiter=None, + create_using=None, nodetype=None, data=True): + """Parse lines of an edge list representation of a graph. + + + Returns + ------- + G: NetworkX Graph + The graph corresponding to lines + data : bool or list of (label,type) tuples + If False generate no edge data or if True use a dictionary + representation of edge data or a list tuples specifying dictionary + key names and types for edge data. + create_using: NetworkX graph container, optional + Use given NetworkX graph for holding nodes or edges. + nodetype : Python type, optional + Convert nodes to this type. + comments : string, optional + Marker for comment lines + delimiter : string, optional + Separator for node labels + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + Examples + -------- + Edgelist with no data: + + >>> lines = ["1 2", + ... "2 3", + ... "3 4"] + >>> G = nx.parse_edgelist(lines, nodetype = int) + >>> G.nodes() + [1, 2, 3, 4] + >>> G.edges() + [(1, 2), (2, 3), (3, 4)] + + Edgelist with data in Python dictionary representation: + + >>> lines = ["1 2 {'weight':3}", + ... "2 3 {'weight':27}", + ... "3 4 {'weight':3.0}"] + >>> G = nx.parse_edgelist(lines, nodetype = int) + >>> G.nodes() + [1, 2, 3, 4] + >>> G.edges(data = True) + [(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})] + + Edgelist with data in a list: + + >>> lines = ["1 2 3", + ... "2 3 27", + ... "3 4 3.0"] + >>> G = nx.parse_edgelist(lines, nodetype = int, data=(('weight',float),)) + >>> G.nodes() + [1, 2, 3, 4] + >>> G.edges(data = True) + [(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})] + + See Also + -------- + read_weighted_edgelist + + """ + from ast import literal_eval + if create_using is None: + G=nx.Graph() + else: + try: + G=create_using + G.clear() + except: + raise TypeError("create_using input is not a NetworkX graph type") + + for line in lines: + p=line.find(comments) + if p>=0: + line = line[:p] + if not len(line): + continue + # split line, should have 2 or more + s=line.strip().split(delimiter) + if len(s)<2: + continue + u=s.pop(0) + v=s.pop(0) + d=s + if nodetype is not None: + try: + u=nodetype(u) + v=nodetype(v) + except: + raise TypeError("Failed to convert nodes %s,%s to type %s." + %(u,v,nodetype)) + + if len(d)==0 or data is False: + # no data or data type specified + edgedata={} + elif data is True: + # no edge types specified + try: # try to evaluate as dictionary + edgedata=dict(literal_eval(' '.join(d))) + except: + raise TypeError( + "Failed to convert edge data (%s) to dictionary."%(d)) + else: + # convert edge data to dictionary with specified keys and type + if len(d)!=len(data): + raise IndexError( + "Edge data %s and data_keys %s are not the same length"% + (d, data)) + edgedata={} + for (edge_key,edge_type),edge_value in zip(data,d): + try: + edge_value=edge_type(edge_value) + except: + raise TypeError( + "Failed to convert %s data %s to type %s." + %(edge_key, edge_value, edge_type)) + edgedata.update({edge_key:edge_value}) + G.add_edge(u, v, attr_dict=edgedata) + return G + +@open_file(0,mode='rb') +def read_edgelist(path, comments="#", delimiter=None, create_using=None, + nodetype=None, data=True, edgetype=None, encoding='utf-8'): + """Read a graph from a list of edges. + + Parameters + ---------- + path : file or string + File or filename to write. If a file is provided, it must be + opened in 'rb' mode. + Filenames ending in .gz or .bz2 will be uncompressed. + comments : string, optional + The character used to indicate the start of a comment. + delimiter : string, optional + The string used to separate values. The default is whitespace. + create_using : Graph container, optional, + Use specified container to build graph. The default is networkx.Graph, + an undirected graph. + nodetype : int, float, str, Python type, optional + Convert node data from strings to specified type + data : bool or list of (label,type) tuples + Tuples specifying dictionary key names and types for edge data + edgetype : int, float, str, Python type, optional OBSOLETE + Convert edge data from strings to specified type and use as 'weight' + encoding: string, optional + Specify which encoding to use when reading file. + + Returns + ------- + G : graph + A networkx Graph or other type specified with create_using + + Examples + -------- + >>> nx.write_edgelist(nx.path_graph(4), "test.edgelist") + >>> G=nx.read_edgelist("test.edgelist") + + >>> fh=open("test.edgelist", 'rb') + >>> G=nx.read_edgelist(fh) + >>> fh.close() + + >>> G=nx.read_edgelist("test.edgelist", nodetype=int) + >>> G=nx.read_edgelist("test.edgelist",create_using=nx.DiGraph()) + + Edgelist with data in a list: + + >>> textline = '1 2 3' + >>> fh = open('test.edgelist','w') + >>> d = fh.write(textline) + >>> fh.close() + >>> G = nx.read_edgelist('test.edgelist', nodetype=int, data=(('weight',float),)) + >>> G.nodes() + [1, 2] + >>> G.edges(data = True) + [(1, 2, {'weight': 3.0})] + + See parse_edgelist() for more examples of formatting. + + See Also + -------- + parse_edgelist + + Notes + ----- + Since nodes must be hashable, the function nodetype must return hashable + types (e.g. int, float, str, frozenset - or tuples of those, etc.) + """ + lines = (line.decode(encoding) for line in path) + return parse_edgelist(lines,comments=comments, delimiter=delimiter, + create_using=create_using, nodetype=nodetype, + data=data) + + +def write_weighted_edgelist(G, path, comments="#", + delimiter=' ', encoding='utf-8'): + """Write graph G as a list of edges with numeric weights. + + Parameters + ---------- + G : graph + A NetworkX graph + path : file or string + File or filename to write. If a file is provided, it must be + opened in 'wb' mode. + Filenames ending in .gz or .bz2 will be compressed. + comments : string, optional + The character used to indicate the start of a comment + delimiter : string, optional + The string used to separate values. The default is whitespace. + encoding: string, optional + Specify which encoding to use when writing file. + + Examples + -------- + >>> G=nx.Graph() + >>> G.add_edge(1,2,weight=7) + >>> nx.write_weighted_edgelist(G, 'test.weighted.edgelist') + + See Also + -------- + read_edgelist() + write_edgelist() + write_weighted_edgelist() + + """ + write_edgelist(G,path, comments=comments, delimiter=delimiter, + data=('weight',), encoding = encoding) + +def read_weighted_edgelist(path, comments="#", delimiter=None, + create_using=None, nodetype=None, encoding='utf-8'): + + """Read a graph as list of edges with numeric weights. + + Parameters + ---------- + path : file or string + File or filename to write. If a file is provided, it must be + opened in 'rb' mode. + Filenames ending in .gz or .bz2 will be uncompressed. + comments : string, optional + The character used to indicate the start of a comment. + delimiter : string, optional + The string used to separate values. The default is whitespace. + create_using : Graph container, optional, + Use specified container to build graph. The default is networkx.Graph, + an undirected graph. + nodetype : int, float, str, Python type, optional + Convert node data from strings to specified type + encoding: string, optional + Specify which encoding to use when reading file. + + Returns + ------- + G : graph + A networkx Graph or other type specified with create_using + + Notes + ----- + Since nodes must be hashable, the function nodetype must return hashable + types (e.g. int, float, str, frozenset - or tuples of those, etc.) + + Example edgelist file format. + + With numeric edge data:: + + # read with + # >>> G=nx.read_weighted_edgelist(fh) + # source target data + a b 1 + a c 3.14159 + d e 42 + """ + return read_edgelist(path, + comments=comments, + delimiter=delimiter, + create_using=create_using, + nodetype=nodetype, + data=(('weight',float),), + encoding = encoding + ) + + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.edgelist') + os.unlink('test.edgelist.gz') + os.unlink('test.weighted.edgelist') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gexf.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gexf.py new file mode 100644 index 0000000..88503ac --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gexf.py @@ -0,0 +1,926 @@ +""" +**** +GEXF +**** +Read and write graphs in GEXF format. + +GEXF (Graph Exchange XML Format) is a language for describing complex +network structures, their associated data and dynamics. + +This implementation does not support mixed graphs (directed and +undirected edges together). + +Format +------ +GEXF is an XML format. See http://gexf.net/format/schema.html for the +specification and http://gexf.net/format/basic.html for examples. +""" +# Copyright (C) 2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +# Based on GraphML NetworkX GraphML reader +__author__ = """\n""".join(['Aric Hagberg ']) +__all__ = ['write_gexf', 'read_gexf', 'relabel_gexf_graph', 'generate_gexf'] +import itertools +import networkx as nx +from networkx.utils import open_file, make_str +try: + from xml.etree.cElementTree import Element, ElementTree, tostring +except ImportError: + try: + from xml.etree.ElementTree import Element, ElementTree, tostring + except ImportError: + pass + +@open_file(1,mode='wb') +def write_gexf(G, path, encoding='utf-8',prettyprint=True,version='1.1draft'): + """Write G in GEXF format to path. + + "GEXF (Graph Exchange XML Format) is a language for describing + complex networks structures, their associated data and dynamics" [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph + path : file or string + File or file name to write. + File names ending in .gz or .bz2 will be compressed. + encoding : string (optional) + Encoding for text data. + prettyprint : bool (optional) + If True use line breaks and indenting in output XML. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_gexf(G, "test.gexf") + + Notes + ----- + This implementation does not support mixed graphs (directed and undirected + edges together). + + The node id attribute is set to be the string of the node label. + If you want to specify an id use set it as node data, e.g. + node['a']['id']=1 to set the id of node 'a' to 1. + + References + ---------- + .. [1] GEXF graph format, http://gexf.net/format/ + """ + writer = GEXFWriter(encoding=encoding,prettyprint=prettyprint, + version=version) + writer.add_graph(G) + writer.write(path) + +def generate_gexf(G, encoding='utf-8',prettyprint=True,version='1.1draft'): + """Generate lines of GEXF format representation of G" + + "GEXF (Graph Exchange XML Format) is a language for describing + complex networks structures, their associated data and dynamics" [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph + encoding : string (optional) + Encoding for text data. + prettyprint : bool (optional) + If True use line breaks and indenting in output XML. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> linefeed=chr(10) # linefeed=\n + >>> s=linefeed.join(nx.generate_gexf(G)) # doctest: +SKIP + >>> for line in nx.generate_gexf(G): # doctest: +SKIP + ... print line + + Notes + ----- + This implementation does not support mixed graphs (directed and undirected + edges together). + + The node id attribute is set to be the string of the node label. + If you want to specify an id use set it as node data, e.g. + node['a']['id']=1 to set the id of node 'a' to 1. + + References + ---------- + .. [1] GEXF graph format, http://gexf.net/format/ + """ + writer = GEXFWriter(encoding=encoding,prettyprint=prettyprint, + version=version) + writer.add_graph(G) + for line in str(writer).splitlines(): + yield line + +@open_file(0,mode='rb') +def read_gexf(path,node_type=None,relabel=False,version='1.1draft'): + """Read graph in GEXF format from path. + + "GEXF (Graph Exchange XML Format) is a language for describing + complex networks structures, their associated data and dynamics" [1]_. + + Parameters + ---------- + path : file or string + File or file name to write. + File names ending in .gz or .bz2 will be compressed. + + node_type: Python type (default: None) + Convert node ids to this type if not None. + + relabel : bool (default: False) + If True relabel the nodes to use the GEXF node "label" attribute + instead of the node "id" attribute as the NetworkX node label. + + Returns + ------- + graph: NetworkX graph + If no parallel edges are found a Graph or DiGraph is returned. + Otherwise a MultiGraph or MultiDiGraph is returned. + + Notes + ----- + This implementation does not support mixed graphs (directed and undirected + edges together). + + References + ---------- + .. [1] GEXF graph format, http://gexf.net/format/ + """ + reader = GEXFReader(node_type=node_type,version=version) + if relabel: + G=relabel_gexf_graph(reader(path)) + else: + G=reader(path) + return G + +class GEXF(object): +# global register_namespace + + versions={} + d={'NS_GEXF':"http://www.gexf.net/1.1draft", + 'NS_VIZ':"http://www.gexf.net/1.1draft/viz", + 'NS_XSI':"http://www.w3.org/2001/XMLSchema-instance", + 'SCHEMALOCATION':' '.join(['http://www.gexf.net/1.1draft', + 'http://www.gexf.net/1.1draft/gexf.xsd' + ]), + 'VERSION':'1.1' + } + versions['1.1draft']=d + d={'NS_GEXF':"http://www.gexf.net/1.2draft", + 'NS_VIZ':"http://www.gexf.net/1.2draft/viz", + 'NS_XSI':"http://www.w3.org/2001/XMLSchema-instance", + 'SCHEMALOCATION':' '.join(['http://www.gexf.net/1.2draft', + 'http://www.gexf.net/1.2draft/gexf.xsd' + ]), + 'VERSION':'1.2' + } + versions['1.2draft']=d + + + types=[(int,"integer"), + (float,"float"), + (float,"double"), + (bool,"boolean"), + (list,"string"), + (dict,"string"), + ] + + try: # Python 3.x + blurb = chr(1245) # just to trigger the exception + types.extend([ + (str,"liststring"), + (str,"anyURI"), + (str,"string")]) + except ValueError: # Python 2.6+ + types.extend([ + (str,"liststring"), + (str,"anyURI"), + (str,"string"), + (unicode,"liststring"), + (unicode,"anyURI"), + (unicode,"string")]) + + xml_type = dict(types) + python_type = dict(reversed(a) for a in types) + convert_bool={'true':True,'false':False} + +# try: +# register_namespace = ET.register_namespace +# except AttributeError: +# def register_namespace(prefix, uri): +# ET._namespace_map[uri] = prefix + + + def set_version(self,version): + d=self.versions.get(version) + if d is None: + raise nx.NetworkXError('Unknown GEXF version %s'%version) + self.NS_GEXF = d['NS_GEXF'] + self.NS_VIZ = d['NS_VIZ'] + self.NS_XSI = d['NS_XSI'] + self.SCHEMALOCATION = d['NS_XSI'] + self.VERSION=d['VERSION'] + self.version=version + +# register_namespace('viz', d['NS_VIZ']) + + +class GEXFWriter(GEXF): + # class for writing GEXF format files + # use write_gexf() function + def __init__(self, graph=None, encoding="utf-8", + mode='static',prettyprint=True, + version='1.1draft'): + try: + import xml.etree.ElementTree + except ImportError: + raise ImportError('GEXF writer requires ' + 'xml.elementtree.ElementTree') + self.prettyprint=prettyprint + self.mode=mode + self.encoding = encoding + self.set_version(version) + self.xml = Element("gexf", + {'xmlns':self.NS_GEXF, + 'xmlns:xsi':self.NS_XSI, + 'xmlns:viz':self.NS_VIZ, + 'xsi:schemaLocation':self.SCHEMALOCATION, + 'version':self.VERSION}) + + # counters for edge and attribute identifiers + self.edge_id=itertools.count() + self.attr_id=itertools.count() + # default attributes are stored in dictionaries + self.attr={} + self.attr['node']={} + self.attr['edge']={} + self.attr['node']['dynamic']={} + self.attr['node']['static']={} + self.attr['edge']['dynamic']={} + self.attr['edge']['static']={} + + if graph is not None: + self.add_graph(graph) + + def __str__(self): + if self.prettyprint: + self.indent(self.xml) + s=tostring(self.xml).decode(self.encoding) + return s + + def add_graph(self, G): + # Add a graph element to the XML + if G.is_directed(): + default='directed' + else: + default='undirected' + graph_element = Element("graph",defaultedgetype=default,mode=self.mode) + self.graph_element=graph_element + self.add_nodes(G,graph_element) + self.add_edges(G,graph_element) + self.xml.append(graph_element) + + + def add_nodes(self, G, graph_element): + nodes_element = Element('nodes') + for node,data in G.nodes_iter(data=True): + node_data=data.copy() + node_id = make_str(node_data.pop('id', node)) + kw={'id':node_id} + label = make_str(node_data.pop('label', node)) + kw['label']=label + try: + pid=node_data.pop('pid') + kw['pid'] = make_str(pid) + except KeyError: + pass + + # add node element with attributes + node_element = Element("node", **kw) + + # add node element and attr subelements + default=G.graph.get('node_default',{}) + node_data=self.add_parents(node_element, node_data) + if self.version=='1.1': + node_data=self.add_slices(node_element, node_data) + else: + node_data=self.add_spells(node_element, node_data) + node_data=self.add_viz(node_element,node_data) + node_data=self.add_attributes("node", node_element, + node_data, default) + nodes_element.append(node_element) + graph_element.append(nodes_element) + + + def add_edges(self, G, graph_element): + def edge_key_data(G): + # helper function to unify multigraph and graph edge iterator + if G.is_multigraph(): + for u,v,key,data in G.edges_iter(data=True,keys=True): + edge_data=data.copy() + edge_data.update(key=key) + edge_id=edge_data.pop('id',None) + if edge_id is None: + edge_id=next(self.edge_id) + yield u,v,edge_id,edge_data + else: + for u,v,data in G.edges_iter(data=True): + edge_data=data.copy() + edge_id=edge_data.pop('id',None) + if edge_id is None: + edge_id=next(self.edge_id) + yield u,v,edge_id,edge_data + + edges_element = Element('edges') + for u,v,key,edge_data in edge_key_data(G): + kw={'id':make_str(key)} + try: + edge_weight=edge_data.pop('weight') + kw['weight']=make_str(edge_weight) + except KeyError: + pass + try: + edge_type=edge_data.pop('type') + kw['type']=make_str(edge_type) + except KeyError: + pass + source_id = make_str(G.node[u].get('id', u)) + target_id = make_str(G.node[v].get('id', v)) + edge_element = Element("edge", + source=source_id,target=target_id, + **kw) + default=G.graph.get('edge_default',{}) + edge_data=self.add_viz(edge_element,edge_data) + edge_data=self.add_attributes("edge", edge_element, + edge_data, default) + edges_element.append(edge_element) + graph_element.append(edges_element) + + + def add_attributes(self, node_or_edge, xml_obj, data, default): + # Add attrvalues to node or edge + attvalues=Element('attvalues') + if len(data)==0: + return data + if 'start' in data or 'end' in data: + mode='dynamic' + else: + mode='static' + for k,v in data.items(): + # rename generic multigraph key to avoid any name conflict + if k == 'key': + k='networkx_key' + attr_id = self.get_attr_id(make_str(k), self.xml_type[type(v)], + node_or_edge, default, mode) + if type(v)==list: + # dynamic data + for val,start,end in v: + e=Element("attvalue") + e.attrib['for']=attr_id + e.attrib['value']=make_str(val) + if start is not None: + e.attrib['start']=make_str(start) + if end is not None: + e.attrib['end']=make_str(end) + attvalues.append(e) + else: + # static data + e=Element("attvalue") + e.attrib['for']=attr_id + e.attrib['value']=make_str(v) + attvalues.append(e) + xml_obj.append(attvalues) + return data + + def get_attr_id(self, title, attr_type, edge_or_node, default, mode): + # find the id of the attribute or generate a new id + try: + return self.attr[edge_or_node][mode][title] + except KeyError: + # generate new id + new_id=str(next(self.attr_id)) + self.attr[edge_or_node][mode][title] = new_id + attr_kwargs = {"id":new_id, "title":title, "type":attr_type} + attribute=Element("attribute",**attr_kwargs) + # add subelement for data default value if present + default_title=default.get(title) + if default_title is not None: + default_element=Element("default") + default_element.text=make_str(default_title) + attribute.append(default_element) + # new insert it into the XML + attributes_element=None + for a in self.graph_element.findall("attributes"): + # find existing attributes element by class and mode + a_class=a.get('class') + a_mode=a.get('mode','static') # default mode is static + if a_class==edge_or_node and a_mode==mode: + attributes_element=a + if attributes_element is None: + # create new attributes element + attr_kwargs = {"mode":mode,"class":edge_or_node} + attributes_element=Element('attributes', **attr_kwargs) + self.graph_element.insert(0,attributes_element) + attributes_element.append(attribute) + return new_id + + + def add_viz(self,element,node_data): + viz=node_data.pop('viz',False) + if viz: + color=viz.get('color') + if color is not None: + if self.VERSION=='1.1': + e=Element("{%s}color"%self.NS_VIZ, + r=str(color.get('r')), + g=str(color.get('g')), + b=str(color.get('b')), + ) + else: + e=Element("{%s}color"%self.NS_VIZ, + r=str(color.get('r')), + g=str(color.get('g')), + b=str(color.get('b')), + a=str(color.get('a')), + ) + element.append(e) + + size=viz.get('size') + if size is not None: + e=Element("{%s}size"%self.NS_VIZ,value=str(size)) + element.append(e) + + thickness=viz.get('thickness') + if thickness is not None: + e=Element("{%s}thickness"%self.NS_VIZ,value=str(thickness)) + element.append(e) + + shape=viz.get('shape') + if shape is not None: + if shape.startswith('http'): + e=Element("{%s}shape"%self.NS_VIZ, + value='image',uri=str(shape)) + else: + e=Element("{%s}shape"%self.NS_VIZ,value=str(shape)) + element.append(e) + + position=viz.get('position') + if position is not None: + e=Element("{%s}position"%self.NS_VIZ, + x=str(position.get('x')), + y=str(position.get('y')), + z=str(position.get('z')), + ) + element.append(e) + return node_data + + def add_parents(self,node_element,node_data): + parents=node_data.pop('parents',False) + if parents: + parents_element=Element('parents') + for p in parents: + e=Element('parent') + e.attrib['for']=str(p) + parents_element.append(e) + node_element.append(parents_element) + return node_data + + def add_slices(self,node_element,node_data): + slices=node_data.pop('slices',False) + if slices: + slices_element=Element('slices') + for start,end in slices: + e=Element('slice',start=str(start),end=str(end)) + slices_element.append(e) + node_element.append(slices_element) + return node_data + + + def add_spells(self,node_element,node_data): + spells=node_data.pop('spells',False) + if spells: + spells_element=Element('spells') + for start,end in spells: + e=Element('spell') + if start is not None: + e.attrib['start']=make_str(start) + if end is not None: + e.attrib['end']=make_str(end) + spells_element.append(e) + node_element.append(spells_element) + return node_data + + + def write(self, fh): + # Serialize graph G in GEXF to the open fh + if self.prettyprint: + self.indent(self.xml) + document = ElementTree(self.xml) + header=''%self.encoding + fh.write(header.encode(self.encoding)) + document.write(fh, encoding=self.encoding) + + + def indent(self, elem, level=0): + # in-place prettyprint formatter + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + self.indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +class GEXFReader(GEXF): + # Class to read GEXF format files + # use read_gexf() function + def __init__(self, node_type=None,version='1.1draft'): + try: + import xml.etree.ElementTree + except ImportError: + raise ImportError('GEXF reader requires ' + 'xml.elementtree.ElementTree') + self.node_type=node_type + # assume simple graph and test for multigraph on read + self.simple_graph=True + self.set_version(version) + + def __call__(self, stream): + self.xml = ElementTree(file=stream) + g=self.xml.find("{%s}graph" % self.NS_GEXF) + if g is not None: + return self.make_graph(g) + # try all the versions + for version in self.versions: + self.set_version(version) + g=self.xml.find("{%s}graph" % self.NS_GEXF) + if g is not None: + return self.make_graph(g) + raise nx.NetworkXError("No element in GEXF file") + + + def make_graph(self, graph_xml): + # mode is "static" or "dynamic" + graph_mode = graph_xml.get("mode", "") + self.dynamic=(graph_mode=='dynamic') + + # start with empty DiGraph or MultiDiGraph + edgedefault = graph_xml.get("defaultedgetype", None) + if edgedefault=='directed': + G=nx.MultiDiGraph() + else: + G=nx.MultiGraph() + + # graph attributes + graph_start=graph_xml.get('start') + if graph_start is not None: + G.graph['start']=graph_start + graph_end=graph_xml.get('end') + if graph_end is not None: + G.graph['end']=graph_end + + # node and edge attributes + attributes_elements=graph_xml.findall("{%s}attributes"%self.NS_GEXF) + # dictionaries to hold attributes and attribute defaults + node_attr={} + node_default={} + edge_attr={} + edge_default={} + for a in attributes_elements: + attr_class = a.get("class") + if attr_class=='node': + na,nd = self.find_gexf_attributes(a) + node_attr.update(na) + node_default.update(nd) + G.graph['node_default']=node_default + elif attr_class=='edge': + ea,ed = self.find_gexf_attributes(a) + edge_attr.update(ea) + edge_default.update(ed) + G.graph['edge_default']=edge_default + else: + raise # unknown attribute class + + # Hack to handle Gephi0.7beta bug + # add weight attribute + ea={'weight':{'type': 'double', 'mode': 'static', 'title': 'weight'}} + ed={} + edge_attr.update(ea) + edge_default.update(ed) + G.graph['edge_default']=edge_default + + # add nodes + nodes_element=graph_xml.find("{%s}nodes" % self.NS_GEXF) + if nodes_element is not None: + for node_xml in nodes_element.findall("{%s}node" % self.NS_GEXF): + self.add_node(G, node_xml, node_attr) + + # add edges + edges_element=graph_xml.find("{%s}edges" % self.NS_GEXF) + if edges_element is not None: + for edge_xml in edges_element.findall("{%s}edge" % self.NS_GEXF): + self.add_edge(G, edge_xml, edge_attr) + + # switch to Graph or DiGraph if no parallel edges were found. + if self.simple_graph: + if G.is_directed(): + G=nx.DiGraph(G) + else: + G=nx.Graph(G) + return G + + def add_node(self, G, node_xml, node_attr, node_pid=None): + # add a single node with attributes to the graph + + # get attributes and subattributues for node + data = self.decode_attr_elements(node_attr, node_xml) + data = self.add_parents(data, node_xml) # add any parents + if self.version=='1.1': + data = self.add_slices(data, node_xml) # add slices + else: + data = self.add_spells(data, node_xml) # add spells + data = self.add_viz(data, node_xml) # add viz + data = self.add_start_end(data, node_xml) # add start/end + + # find the node id and cast it to the appropriate type + node_id = node_xml.get("id") + if self.node_type is not None: + node_id=self.node_type(node_id) + + # every node should have a label + node_label = node_xml.get("label") + data['label']=node_label + + # parent node id + node_pid = node_xml.get("pid", node_pid) + if node_pid is not None: + data['pid']=node_pid + + # check for subnodes, recursive + subnodes=node_xml.find("{%s}nodes" % self.NS_GEXF) + if subnodes is not None: + for node_xml in subnodes.findall("{%s}node" % self.NS_GEXF): + self.add_node(G, node_xml, node_attr, node_pid=node_id) + + G.add_node(node_id, data) + + def add_start_end(self, data, xml): + # start and end times + node_start = xml.get("start") + if node_start is not None: + data['start']=node_start + node_end = xml.get("end") + if node_end is not None: + data['end']=node_end + return data + + + def add_viz(self, data, node_xml): + # add viz element for node + viz={} + color=node_xml.find("{%s}color"%self.NS_VIZ) + if color is not None: + if self.VERSION=='1.1': + viz['color']={'r':int(color.get('r')), + 'g':int(color.get('g')), + 'b':int(color.get('b'))} + else: + viz['color']={'r':int(color.get('r')), + 'g':int(color.get('g')), + 'b':int(color.get('b')), + 'a':float(color.get('a', 1)), + } + + size=node_xml.find("{%s}size"%self.NS_VIZ) + if size is not None: + viz['size']=float(size.get('value')) + + thickness=node_xml.find("{%s}thickness"%self.NS_VIZ) + if thickness is not None: + viz['thickness']=float(thickness.get('value')) + + shape=node_xml.find("{%s}shape"%self.NS_VIZ) + if shape is not None: + viz['shape']=shape.get('shape') + if viz['shape']=='image': + viz['shape']=shape.get('uri') + + position=node_xml.find("{%s}position"%self.NS_VIZ) + if position is not None: + viz['position']={'x':float(position.get('x',0)), + 'y':float(position.get('y',0)), + 'z':float(position.get('z',0))} + + if len(viz)>0: + data['viz']=viz + return data + + def add_parents(self, data, node_xml): + parents_element=node_xml.find("{%s}parents"%self.NS_GEXF) + if parents_element is not None: + data['parents']=[] + for p in parents_element.findall("{%s}parent"%self.NS_GEXF): + parent=p.get('for') + data['parents'].append(parent) + return data + + def add_slices(self, data, node_xml): + slices_element=node_xml.find("{%s}slices"%self.NS_GEXF) + if slices_element is not None: + data['slices']=[] + for s in slices_element.findall("{%s}slice"%self.NS_GEXF): + start=s.get('start') + end=s.get('end') + data['slices'].append((start,end)) + return data + + def add_spells(self, data, node_xml): + spells_element=node_xml.find("{%s}spells"%self.NS_GEXF) + if spells_element is not None: + data['spells']=[] + for s in spells_element.findall("{%s}spell"%self.NS_GEXF): + start=s.get('start') + end=s.get('end') + data['spells'].append((start,end)) + return data + + + def add_edge(self, G, edge_element, edge_attr): + # add an edge to the graph + + # raise error if we find mixed directed and undirected edges + edge_direction = edge_element.get("type") + if G.is_directed() and edge_direction=='undirected': + raise nx.NetworkXError(\ + "Undirected edge found in directed graph.") + if (not G.is_directed()) and edge_direction=='directed': + raise nx.NetworkXError(\ + "Directed edge found in undirected graph.") + + # Get source and target and recast type if required + source = edge_element.get("source") + target = edge_element.get("target") + if self.node_type is not None: + source=self.node_type(source) + target=self.node_type(target) + + data = self.decode_attr_elements(edge_attr, edge_element) + data = self.add_start_end(data,edge_element) + + # GEXF stores edge ids as an attribute + # NetworkX uses them as keys in multigraphs + # if networkx_key is not specified as an attribute + edge_id = edge_element.get("id") + if edge_id is not None: + data["id"] = edge_id + + # check if there is a 'multigraph_key' and use that as edge_id + multigraph_key = data.pop('networkx_key',None) + if multigraph_key is not None: + edge_id=multigraph_key + + weight = edge_element.get('weight') + if weight is not None: + data['weight']=float(weight) + + edge_label = edge_element.get("label") + if edge_label is not None: + data['label']=edge_label + + + + if G.has_edge(source,target): + # seen this edge before - this is a multigraph + self.simple_graph=False + G.add_edge(source, target, key=edge_id, **data) + if edge_direction=='mutual': + G.add_edge(target, source, key=edge_id, **data) + + def decode_attr_elements(self, gexf_keys, obj_xml): + # Use the key information to decode the attr XML + attr = {} + # look for outer "" element + attr_element=obj_xml.find("{%s}attvalues" % self.NS_GEXF) + if attr_element is not None: + # loop over elements + for a in attr_element.findall("{%s}attvalue" % self.NS_GEXF): + key = a.get('for') # for is required + try: # should be in our gexf_keys dictionary + title=gexf_keys[key]['title'] + except KeyError: + raise nx.NetworkXError("No attribute defined for=%s"%key) + atype=gexf_keys[key]['type'] + value=a.get('value') + if atype=='boolean': + value=self.convert_bool[value] + else: + value=self.python_type[atype](value) + if gexf_keys[key]['mode']=='dynamic': + # for dynamic graphs use list of three-tuples + # [(value1,start1,end1), (value2,start2,end2), etc] + start=a.get('start') + end=a.get('end') + if title in attr: + attr[title].append((value,start,end)) + else: + attr[title]=[(value,start,end)] + else: + # for static graphs just assign the value + attr[title] = value + return attr + + def find_gexf_attributes(self, attributes_element): + # Extract all the attributes and defaults + attrs = {} + defaults = {} + mode=attributes_element.get('mode') + for k in attributes_element.findall("{%s}attribute" % self.NS_GEXF): + attr_id = k.get("id") + title=k.get('title') + atype=k.get('type') + attrs[attr_id]={'title':title,'type':atype,'mode':mode} + # check for the "default" subelement of key element and add + default=k.find("{%s}default" % self.NS_GEXF) + if default is not None: + if atype=='boolean': + value=self.convert_bool[default.text] + else: + value=self.python_type[atype](default.text) + defaults[title]=value + return attrs,defaults + + +def relabel_gexf_graph(G): + """Relabel graph using "label" node keyword for node label. + + Parameters + ---------- + G : graph + A NetworkX graph read from GEXF data + + Returns + ------- + H : graph + A NetworkX graph with relabed nodes + + Notes + ----- + This function relabels the nodes in a NetworkX graph with the + "label" attribute. It also handles relabeling the specific GEXF + node attributes "parents", and "pid". + """ + # build mapping of node labels, do some error checking + try: + mapping=[(u,G.node[u]['label']) for u in G] + except KeyError: + raise nx.NetworkXError('Failed to relabel nodes: ' + 'missing node labels found. ' + 'Use relabel=False.') + x,y=zip(*mapping) + if len(set(y))!=len(G): + raise nx.NetworkXError('Failed to relabel nodes: ' + 'duplicate node labels found. ' + 'Use relabel=False.') + mapping=dict(mapping) + H=nx.relabel_nodes(G,mapping) + # relabel attributes + for n in G: + m=mapping[n] + H.node[m]['id']=n + H.node[m].pop('label') + if 'pid' in H.node[m]: + H.node[m]['pid']=mapping[G.node[n]['pid']] + if 'parents' in H.node[m]: + H.node[m]['parents']=[mapping[p] for p in G.node[n]['parents']] + return H + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import xml.etree.cElementTree + except: + raise SkipTest("xml.etree.cElementTree not available") + +# fixture for nose tests +def teardown_module(module): + import os + try: + os.unlink('test.gexf') + except: + pass diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gml.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gml.py new file mode 100644 index 0000000..d248eb4 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gml.py @@ -0,0 +1,410 @@ +""" +Read graphs in GML format. + +"GML, the G>raph Modelling Language, is our proposal for a portable +file format for graphs. GML's key features are portability, simple +syntax, extensibility and flexibility. A GML file consists of a +hierarchical key-value lists. Graphs can be annotated with arbitrary +data structures. The idea for a common file format was born at the +GD'95; this proposal is the outcome of many discussions. GML is the +standard file format in the Graphlet graph editor system. It has been +overtaken and adapted by several other systems for drawing graphs." + +See http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html + +Requires pyparsing: http://pyparsing.wikispaces.com/ + +Format +------ +See http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html +for format specification. + +Example graphs in GML format: +http://www-personal.umich.edu/~mejn/netdata/ +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +# Copyright (C) 2008-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['read_gml', 'parse_gml', 'generate_gml', 'write_gml'] + +import networkx as nx +from networkx.exception import NetworkXError +from networkx.utils import is_string_like, open_file + + +@open_file(0,mode='rb') +def read_gml(path,encoding='UTF-8',relabel=False): + """Read graph in GML format from path. + + Parameters + ---------- + path : filename or filehandle + The filename or filehandle to read from. + + encoding : string, optional + Text encoding. + + relabel : bool, optional + If True use the GML node label attribute for node names otherwise use + the node id. + + Returns + ------- + G : MultiGraph or MultiDiGraph + + Raises + ------ + ImportError + If the pyparsing module is not available. + + See Also + -------- + write_gml, parse_gml + + Notes + ----- + Requires pyparsing: http://pyparsing.wikispaces.com/ + + References + ---------- + GML specification: + http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_gml(G,'test.gml') + >>> H=nx.read_gml('test.gml') + """ + lines=(line.decode(encoding) for line in path) + G=parse_gml(lines,relabel=relabel) + return G + +def parse_gml(lines, relabel=True): + """Parse GML graph from a string or iterable. + + Parameters + ---------- + lines : string or iterable + Data in GML format. + + relabel : bool, optional + If True use the GML node label attribute for node names otherwise use + the node id. + + Returns + ------- + G : MultiGraph or MultiDiGraph + + Raises + ------ + ImportError + If the pyparsing module is not available. + + See Also + -------- + write_gml, read_gml + + Notes + ----- + This stores nested GML attributes as dictionaries in the + NetworkX graph, node, and edge attribute structures. + + Requires pyparsing: http://pyparsing.wikispaces.com/ + + References + ---------- + GML specification: + http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html + """ + try: + from pyparsing import ParseException + except ImportError: + try: + from matplotlib.pyparsing import ParseException + except: + raise ImportError('Import Error: not able to import pyparsing:', + 'http://pyparsing.wikispaces.com/') + try: + data = "".join(lines) + gml = pyparse_gml() + tokens =gml.parseString(data) + except ParseException as err: + print((err.line)) + print((" "*(err.column-1) + "^")) + print(err) + raise + + # function to recursively make dicts of key/value pairs + def wrap(tok): + listtype=type(tok) + result={} + for k,v in tok: + if type(v)==listtype: + result[str(k)]=wrap(v) + else: + result[str(k)]=v + return result + + # Set flag + multigraph=False + # but assume multigraphs to start + if tokens.directed==1: + G=nx.MultiDiGraph() + else: + G=nx.MultiGraph() + + for k,v in tokens.asList(): + if k=="node": + vdict=wrap(v) + node=vdict['id'] + G.add_node(node,attr_dict=vdict) + elif k=="edge": + vdict=wrap(v) + source=vdict.pop('source') + target=vdict.pop('target') + if G.has_edge(source,target): + multigraph=True + G.add_edge(source,target,attr_dict=vdict) + else: + G.graph[k]=v + + # switch to Graph or DiGraph if no parallel edges were found. + if not multigraph: + if G.is_directed(): + G=nx.DiGraph(G) + else: + G=nx.Graph(G) + + if relabel: + # relabel, but check for duplicate labels first + mapping=[(n,d['label']) for n,d in G.node.items()] + x,y=zip(*mapping) + if len(set(y))!=len(G): + raise NetworkXError('Failed to relabel nodes: ' + 'duplicate node labels found. ' + 'Use relabel=False.') + G=nx.relabel_nodes(G,dict(mapping)) + return G + + +def pyparse_gml(): + """A pyparsing tokenizer for GML graph format. + + This is not intended to be called directly. + + See Also + -------- + write_gml, read_gml, parse_gml + """ + try: + from pyparsing import \ + Literal, CaselessLiteral, Word, Forward,\ + ZeroOrMore, Group, Dict, Optional, Combine,\ + ParseException, restOfLine, White, alphas, alphanums, nums,\ + OneOrMore,quotedString,removeQuotes,dblQuotedString, Regex + except ImportError: + try: + from matplotlib.pyparsing import \ + Literal, CaselessLiteral, Word, Forward,\ + ZeroOrMore, Group, Dict, Optional, Combine,\ + ParseException, restOfLine, White, alphas, alphanums, nums,\ + OneOrMore,quotedString,removeQuotes,dblQuotedString, Regex + except: + raise ImportError('pyparsing not found', + 'http://pyparsing.wikispaces.com/') + + lbrack = Literal("[").suppress() + rbrack = Literal("]").suppress() + pound = ("#") + comment = pound + Optional( restOfLine ) + integer = Word(nums+'-').setParseAction(lambda s,l,t:[ int(t[0])]) + real = Regex(r"[+-]?\d+\.\d*([eE][+-]?\d+)?").setParseAction( + lambda s,l,t:[ float(t[0]) ]) + dblQuotedString.setParseAction( removeQuotes ) + key = Word(alphas,alphanums+'_') + value_atom = (real | integer | Word(alphanums) | dblQuotedString) + value = Forward() # to be defined later with << operator + keyvalue = Group(key+value) + value << (value_atom | Group( lbrack + ZeroOrMore(keyvalue) + rbrack )) + node = Group(Literal("node") + lbrack + Group(OneOrMore(keyvalue)) + rbrack) + edge = Group(Literal("edge") + lbrack + Group(OneOrMore(keyvalue)) + rbrack) + + creator = Group(Literal("Creator")+ Optional( restOfLine )) + version = Group(Literal("Version")+ Optional( restOfLine )) + graphkey = Literal("graph").suppress() + + graph = Dict (Optional(creator)+Optional(version)+\ + graphkey + lbrack + ZeroOrMore( (node|edge|keyvalue) ) + rbrack ) + graph.ignore(comment) + + return graph + +def generate_gml(G): + """Generate a single entry of the graph G in GML format. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + lines: string + Lines in GML format. + + Notes + ----- + This implementation does not support all Python data types as GML + data. Nodes, node attributes, edge attributes, and graph + attributes must be either dictionaries or single stings or + numbers. If they are not an attempt is made to represent them as + strings. For example, a list as edge data + G[1][2]['somedata']=[1,2,3], will be represented in the GML file + as:: + + edge [ + source 1 + target 2 + somedata "[1, 2, 3]" + ] + """ + # recursively make dicts into gml brackets + def listify(d,indent,indentlevel): + result='[ \n' + for k,v in d.items(): + if type(v)==dict: + v=listify(v,indent,indentlevel+1) + result += (indentlevel+1)*indent + \ + string_item(k,v,indentlevel*indent)+'\n' + return result+indentlevel*indent+"]" + + def string_item(k,v,indent): + # try to make a string of the data + if type(v)==dict: + v=listify(v,indent,2) + elif is_string_like(v): + v='"%s"'%v + elif type(v)==bool: + v=int(v) + return "%s %s"%(k,v) + + # check for attributes or assign empty dict + if hasattr(G,'graph_attr'): + graph_attr=G.graph_attr + else: + graph_attr={} + if hasattr(G,'node_attr'): + node_attr=G.node_attr + else: + node_attr={} + + indent=2*' ' + count=iter(range(len(G))) + node_id={} + + yield "graph [" + if G.is_directed(): + yield indent+"directed 1" + # write graph attributes + for k,v in G.graph.items(): + if k == 'directed': + continue + yield indent+string_item(k,v,indent) + # write nodes + for n in G: + yield indent+"node [" + # get id or assign number + nid=G.node[n].get('id',next(count)) + node_id[n]=nid + yield 2*indent+"id %s"%nid + label=G.node[n].get('label',n) + if is_string_like(label): + label='"%s"'%label + yield 2*indent+'label %s'%label + if n in G: + for k,v in G.node[n].items(): + if k=='id' or k == 'label': continue + yield 2*indent+string_item(k,v,indent) + yield indent+"]" + # write edges + for u,v,edgedata in G.edges_iter(data=True): + yield indent+"edge [" + yield 2*indent+"source %s"%node_id[u] + yield 2*indent+"target %s"%node_id[v] + for k,v in edgedata.items(): + if k=='source': continue + if k=='target': continue + yield 2*indent+string_item(k,v,indent) + yield indent+"]" + yield "]" + +@open_file(1,mode='wb') +def write_gml(G, path): + """ + Write the graph G in GML format to the file or file handle path. + + Parameters + ---------- + path : filename or filehandle + The filename or filehandle to write. Filenames ending in + .gz or .gz2 will be compressed. + + See Also + -------- + read_gml, parse_gml + + Notes + ----- + GML specifications indicate that the file should only use + 7bit ASCII text encoding.iso8859-1 (latin-1). + + This implementation does not support all Python data types as GML + data. Nodes, node attributes, edge attributes, and graph + attributes must be either dictionaries or single stings or + numbers. If they are not an attempt is made to represent them as + strings. For example, a list as edge data + G[1][2]['somedata']=[1,2,3], will be represented in the GML file + as:: + + edge [ + source 1 + target 2 + somedata "[1, 2, 3]" + ] + + + Examples + --------- + >>> G=nx.path_graph(4) + >>> nx.write_gml(G,"test.gml") + + Filenames ending in .gz or .bz2 will be compressed. + + >>> nx.write_gml(G,"test.gml.gz") + """ + for line in generate_gml(G): + line+='\n' + path.write(line.encode('latin-1')) + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import pyparsing + except: + try: + import matplotlib.pyparsing + except: + raise SkipTest("pyparsing not available") + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.gml') + os.unlink('test.gml.gz') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gpickle.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gpickle.py new file mode 100644 index 0000000..688a35c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/gpickle.py @@ -0,0 +1,100 @@ +""" +************** +Pickled Graphs +************** +Read and write NetworkX graphs as Python pickles. + +"The pickle module implements a fundamental, but powerful algorithm +for serializing and de-serializing a Python object +structure. "Pickling" is the process whereby a Python object hierarchy +is converted into a byte stream, and "unpickling" is the inverse +operation, whereby a byte stream is converted back into an object +hierarchy." + +Note that NetworkX graphs can contain any hashable Python object as +node (not just integers and strings). For arbitrary data types it may +be difficult to represent the data as text. In that case using Python +pickles to store the graph data can be used. + +Format +------ +See http://docs.python.org/library/pickle.html +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)\nDan Schult (dschult@colgate.edu)""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['read_gpickle', 'write_gpickle'] + +import networkx as nx +from networkx.utils import open_file + +try: + import cPickle as pickle +except ImportError: + import pickle + +@open_file(1,mode='wb') +def write_gpickle(G, path): + """Write graph in Python pickle format. + + Pickles are a serialized byte stream of a Python object [1]_. + This format will preserve Python objects used as nodes or edges. + + Parameters + ---------- + G : graph + A NetworkX graph + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be compressed. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_gpickle(G,"test.gpickle") + + References + ---------- + .. [1] http://docs.python.org/library/pickle.html + """ + pickle.dump(G, path, pickle.HIGHEST_PROTOCOL) + +@open_file(0,mode='rb') +def read_gpickle(path): + """Read graph object in Python pickle format. + + Pickles are a serialized byte stream of a Python object [1]_. + This format will preserve Python objects used as nodes or edges. + + Parameters + ---------- + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be uncompressed. + + Returns + ------- + G : graph + A NetworkX graph + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_gpickle(G,"test.gpickle") + >>> G=nx.read_gpickle("test.gpickle") + + References + ---------- + .. [1] http://docs.python.org/library/pickle.html + """ + return pickle.load(path) + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.gpickle') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/graphml.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/graphml.py new file mode 100644 index 0000000..7f896e0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/graphml.py @@ -0,0 +1,579 @@ +""" +******* +GraphML +******* +Read and write graphs in GraphML format. + +This implementation does not support mixed graphs (directed and unidirected +edges together), hyperedges, nested graphs, or ports. + +"GraphML is a comprehensive and easy-to-use file format for graphs. It +consists of a language core to describe the structural properties of a +graph and a flexible extension mechanism to add application-specific +data. Its main features include support of + + * directed, undirected, and mixed graphs, + * hypergraphs, + * hierarchical graphs, + * graphical representations, + * references to external data, + * application-specific attribute data, and + * light-weight parsers. + +Unlike many other file formats for graphs, GraphML does not use a +custom syntax. Instead, it is based on XML and hence ideally suited as +a common denominator for all kinds of services generating, archiving, +or processing graphs." + +http://graphml.graphdrawing.org/ + +Format +------ +GraphML is an XML format. See +http://graphml.graphdrawing.org/specification.html for the specification and +http://graphml.graphdrawing.org/primer/graphml-primer.html +for examples. +""" +__author__ = """\n""".join(['Salim Fadhley', + 'Aric Hagberg (hagberg@lanl.gov)' + ]) + +__all__ = ['write_graphml', 'read_graphml', 'generate_graphml', + 'parse_graphml', 'GraphMLWriter', 'GraphMLReader'] + +import networkx as nx +from networkx.utils import open_file, make_str +import warnings +try: + from xml.etree.cElementTree import Element, ElementTree, tostring, fromstring +except ImportError: + try: + from xml.etree.ElementTree import Element, ElementTree, tostring, fromstring + except ImportError: + pass + +@open_file(1,mode='wb') +def write_graphml(G, path, encoding='utf-8',prettyprint=True): + """Write G in GraphML XML format to path + + Parameters + ---------- + G : graph + A networkx graph + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be compressed. + encoding : string (optional) + Encoding for text data. + prettyprint : bool (optional) + If True use line breaks and indenting in output XML. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_graphml(G, "test.graphml") + + Notes + ----- + This implementation does not support mixed graphs (directed and unidirected + edges together) hyperedges, nested graphs, or ports. + """ + writer = GraphMLWriter(encoding=encoding,prettyprint=prettyprint) + writer.add_graph_element(G) + writer.dump(path) + +def generate_graphml(G, encoding='utf-8',prettyprint=True): + """Generate GraphML lines for G + + Parameters + ---------- + G : graph + A networkx graph + encoding : string (optional) + Encoding for text data. + prettyprint : bool (optional) + If True use line breaks and indenting in output XML. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> linefeed=chr(10) # linefeed=\n + >>> s=linefeed.join(nx.generate_graphml(G)) # doctest: +SKIP + >>> for line in nx.generate_graphml(G): # doctest: +SKIP + ... print(line) + + Notes + ----- + This implementation does not support mixed graphs (directed and unidirected + edges together) hyperedges, nested graphs, or ports. + """ + writer = GraphMLWriter(encoding=encoding,prettyprint=prettyprint) + writer.add_graph_element(G) + for line in str(writer).splitlines(): + yield line + +@open_file(0,mode='rb') +def read_graphml(path,node_type=str): + """Read graph in GraphML format from path. + + Parameters + ---------- + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be compressed. + + node_type: Python type (default: str) + Convert node ids to this type + + Returns + ------- + graph: NetworkX graph + If no parallel edges are found a Graph or DiGraph is returned. + Otherwise a MultiGraph or MultiDiGraph is returned. + + Notes + ----- + This implementation does not support mixed graphs (directed and unidirected + edges together), hypergraphs, nested graphs, or ports. + + For multigraphs the GraphML edge "id" will be used as the edge + key. If not specified then they "key" attribute will be used. If + there is no "key" attribute a default NetworkX multigraph edge key + will be provided. + + Files with the yEd "yfiles" extension will can be read but the graphics + information is discarded. + + yEd compressed files ("file.graphmlz" extension) can be read by renaming + the file to "file.graphml.gz". + + """ + reader = GraphMLReader(node_type=node_type) + # need to check for multiple graphs + glist=list(reader(path=path)) + return glist[0] + + +def parse_graphml(graphml_string,node_type=str): + """Read graph in GraphML format from string. + + Parameters + ---------- + graphml_string : string + String containing graphml information + (e.g., contents of a graphml file). + + node_type: Python type (default: str) + Convert node ids to this type + + Returns + ------- + graph: NetworkX graph + If no parallel edges are found a Graph or DiGraph is returned. + Otherwise a MultiGraph or MultiDiGraph is returned. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> linefeed=chr(10) # linefeed=\n + >>> s=linefeed.join(nx.generate_graphml(G)) + >>> H=nx.parse_graphml(s) + + Notes + ----- + This implementation does not support mixed graphs (directed and unidirected + edges together), hypergraphs, nested graphs, or ports. + + For multigraphs the GraphML edge "id" will be used as the edge + key. If not specified then they "key" attribute will be used. If + there is no "key" attribute a default NetworkX multigraph edge key + will be provided. + + """ + reader = GraphMLReader(node_type=node_type) + # need to check for multiple graphs + glist=list(reader(string=graphml_string)) + return glist[0] + + +class GraphML(object): + NS_GRAPHML = "http://graphml.graphdrawing.org/xmlns" + NS_XSI = "http://www.w3.org/2001/XMLSchema-instance" + #xmlns:y="http://www.yworks.com/xml/graphml" + NS_Y = "http://www.yworks.com/xml/graphml" + SCHEMALOCATION = \ + ' '.join(['http://graphml.graphdrawing.org/xmlns', + 'http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd']) + + try: + chr(12345) # Fails on Py!=3. + unicode = str # Py3k's str is our unicode type + long = int # Py3K's int is our long type + except ValueError: + # Python 2.x + pass + + types=[(int,"integer"), # for Gephi GraphML bug + (str,"yfiles"),(str,"string"), (unicode,"string"), + (int,"int"), (long,"long"), + (float,"float"), (float,"double"), + (bool, "boolean")] + + xml_type = dict(types) + python_type = dict(reversed(a) for a in types) + convert_bool={'true':True,'false':False, + 'True': True, 'False': False} + + + +class GraphMLWriter(GraphML): + def __init__(self, graph=None, encoding="utf-8",prettyprint=True): + try: + import xml.etree.ElementTree + except ImportError: + raise ImportError('GraphML writer requires ' + 'xml.elementtree.ElementTree') + self.prettyprint=prettyprint + self.encoding = encoding + self.xml = Element("graphml", + {'xmlns':self.NS_GRAPHML, + 'xmlns:xsi':self.NS_XSI, + 'xsi:schemaLocation':self.SCHEMALOCATION} + ) + self.keys={} + + if graph is not None: + self.add_graph_element(graph) + + + def __str__(self): + if self.prettyprint: + self.indent(self.xml) + s=tostring(self.xml).decode(self.encoding) + return s + + def get_key(self, name, attr_type, scope, default): + keys_key = (name, attr_type, scope) + try: + return self.keys[keys_key] + except KeyError: + new_id = "d%i" % len(list(self.keys)) + self.keys[keys_key] = new_id + key_kwargs = {"id":new_id, + "for":scope, + "attr.name":name, + "attr.type":attr_type} + key_element=Element("key",**key_kwargs) + # add subelement for data default value if present + if default is not None: + default_element=Element("default") + default_element.text=make_str(default) + key_element.append(default_element) + self.xml.insert(0,key_element) + return new_id + + + def add_data(self, name, element_type, value, + scope="all", + default=None): + """ + Make a data element for an edge or a node. Keep a log of the + type in the keys table. + """ + if element_type not in self.xml_type: + raise nx.NetworkXError('GraphML writer does not support ' + '%s as data values.'%element_type) + key_id = self.get_key(name, self.xml_type[element_type], scope, default) + data_element = Element("data", key=key_id) + data_element.text = make_str(value) + return data_element + + def add_attributes(self, scope, xml_obj, data, default): + """Appends attributes to edges or nodes. + """ + for k,v in data.items(): + default_value=default.get(k) + obj=self.add_data(make_str(k), type(v), make_str(v), + scope=scope, default=default_value) + xml_obj.append(obj) + + def add_nodes(self, G, graph_element): + for node,data in G.nodes_iter(data=True): + node_element = Element("node", id = make_str(node)) + default=G.graph.get('node_default',{}) + self.add_attributes("node", node_element, data, default) + graph_element.append(node_element) + + def add_edges(self, G, graph_element): + if G.is_multigraph(): + for u,v,key,data in G.edges_iter(data=True,keys=True): + edge_element = Element("edge",source=make_str(u), + target=make_str(v)) + default=G.graph.get('edge_default',{}) + self.add_attributes("edge", edge_element, data, default) + self.add_attributes("edge", edge_element, + {'key':key}, default) + graph_element.append(edge_element) + else: + for u,v,data in G.edges_iter(data=True): + edge_element = Element("edge",source=make_str(u), + target=make_str(v)) + default=G.graph.get('edge_default',{}) + self.add_attributes("edge", edge_element, data, default) + graph_element.append(edge_element) + + def add_graph_element(self, G): + """ + Serialize graph G in GraphML to the stream. + """ + if G.is_directed(): + default_edge_type='directed' + else: + default_edge_type='undirected' + + graphid=G.graph.pop('id',None) + if graphid is None: + graph_element = Element("graph", + edgedefault = default_edge_type) + else: + graph_element = Element("graph", + edgedefault = default_edge_type, + id=graphid) + + default={} + data=dict((k,v) for (k,v) in G.graph.items() + if k not in ['node_default','edge_default']) + self.add_attributes("graph", graph_element, data, default) + self.add_nodes(G,graph_element) + self.add_edges(G,graph_element) + self.xml.append(graph_element) + + def add_graphs(self, graph_list): + """ + Add many graphs to this GraphML document. + """ + for G in graph_list: + self.add_graph_element(G) + + def dump(self, stream): + if self.prettyprint: + self.indent(self.xml) + document = ElementTree(self.xml) + header=''%self.encoding + stream.write(header.encode(self.encoding)) + document.write(stream, encoding=self.encoding) + + def indent(self, elem, level=0): + # in-place prettyprint formatter + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + self.indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +class GraphMLReader(GraphML): + """Read a GraphML document. Produces NetworkX graph objects. + """ + def __init__(self, node_type=str): + try: + import xml.etree.ElementTree + except ImportError: + raise ImportError('GraphML reader requires ' + 'xml.elementtree.ElementTree') + self.node_type=node_type + self.multigraph=False # assume multigraph and test for parallel edges + + def __call__(self, path=None, string=None): + if path is not None: + self.xml = ElementTree(file=path) + elif string is not None: + self.xml = fromstring(string) + else: + raise ValueError("Must specify either 'path' or 'string' as kwarg.") + (keys,defaults) = self.find_graphml_keys(self.xml) + for g in self.xml.findall("{%s}graph" % self.NS_GRAPHML): + yield self.make_graph(g, keys, defaults) + + def make_graph(self, graph_xml, graphml_keys, defaults): + # set default graph type + edgedefault = graph_xml.get("edgedefault", None) + if edgedefault=='directed': + G=nx.MultiDiGraph() + else: + G=nx.MultiGraph() + # set defaults for graph attributes + G.graph['node_default']={} + G.graph['edge_default']={} + for key_id,value in defaults.items(): + key_for=graphml_keys[key_id]['for'] + name=graphml_keys[key_id]['name'] + python_type=graphml_keys[key_id]['type'] + if key_for=='node': + G.graph['node_default'].update({name:python_type(value)}) + if key_for=='edge': + G.graph['edge_default'].update({name:python_type(value)}) + # hyperedges are not supported + hyperedge=graph_xml.find("{%s}hyperedge" % self.NS_GRAPHML) + if hyperedge is not None: + raise nx.NetworkXError("GraphML reader does not support hyperedges") + # add nodes + for node_xml in graph_xml.findall("{%s}node" % self.NS_GRAPHML): + self.add_node(G, node_xml, graphml_keys) + # add edges + for edge_xml in graph_xml.findall("{%s}edge" % self.NS_GRAPHML): + self.add_edge(G, edge_xml, graphml_keys) + # add graph data + data = self.decode_data_elements(graphml_keys, graph_xml) + G.graph.update(data) + + # switch to Graph or DiGraph if no parallel edges were found. + if not self.multigraph: + if G.is_directed(): + return nx.DiGraph(G) + else: + return nx.Graph(G) + else: + return G + + def add_node(self, G, node_xml, graphml_keys): + """Add a node to the graph. + """ + # warn on finding unsupported ports tag + ports=node_xml.find("{%s}port" % self.NS_GRAPHML) + if ports is not None: + warnings.warn("GraphML port tag not supported.") + # find the node by id and cast it to the appropriate type + node_id = self.node_type(node_xml.get("id")) + # get data/attributes for node + data = self.decode_data_elements(graphml_keys, node_xml) + G.add_node(node_id, data) + + def add_edge(self, G, edge_element, graphml_keys): + """Add an edge to the graph. + """ + # warn on finding unsupported ports tag + ports=edge_element.find("{%s}port" % self.NS_GRAPHML) + if ports is not None: + warnings.warn("GraphML port tag not supported.") + + # raise error if we find mixed directed and undirected edges + directed = edge_element.get("directed") + if G.is_directed() and directed=='false': + raise nx.NetworkXError(\ + "directed=false edge found in directed graph.") + if (not G.is_directed()) and directed=='true': + raise nx.NetworkXError(\ + "directed=true edge found in undirected graph.") + + source = self.node_type(edge_element.get("source")) + target = self.node_type(edge_element.get("target")) + data = self.decode_data_elements(graphml_keys, edge_element) + # GraphML stores edge ids as an attribute + # NetworkX uses them as keys in multigraphs too if no key + # attribute is specified + edge_id = edge_element.get("id") + if edge_id: + data["id"] = edge_id + if G.has_edge(source,target): + # mark this as a multigraph + self.multigraph=True + if edge_id is None: + # no id specified, try using 'key' attribute as id + edge_id=data.pop('key',None) + G.add_edge(source, target, key=edge_id, **data) + + def decode_data_elements(self, graphml_keys, obj_xml): + """Use the key information to decode the data XML if present.""" + data = {} + for data_element in obj_xml.findall("{%s}data" % self.NS_GRAPHML): + key = data_element.get("key") + try: + data_name=graphml_keys[key]['name'] + data_type=graphml_keys[key]['type'] + except KeyError: + raise nx.NetworkXError("Bad GraphML data: no key %s"%key) + text=data_element.text + # assume anything with subelements is a yfiles extension + if text is not None and len(list(data_element))==0: + if data_type==bool: + data[data_name] = self.convert_bool[text] + else: + data[data_name] = data_type(text) + elif len(list(data_element)) > 0: + # Assume yfiles as subelements, try to extract node_label + node_label = None + for node_type in ['ShapeNode', 'SVGNode', 'ImageNode']: + geometry = data_element.find("{%s}%s/{%s}Geometry" % + (self.NS_Y, node_type, self.NS_Y)) + if geometry is not None: + data['x'] = geometry.get('x') + data['y'] = geometry.get('y') + if node_label is None: + node_label = data_element.find("{%s}%s/{%s}NodeLabel" % + (self.NS_Y, node_type, self.NS_Y)) + if node_label is not None: + data['label'] = node_label.text + + # check all the diffrent types of edges avaivable in yEd. + for e in ['PolyLineEdge', 'SplineEdge', 'QuadCurveEdge', 'BezierEdge', 'ArcEdge']: + edge_label = data_element.find("{%s}%s/{%s}EdgeLabel"% + (self.NS_Y, e, (self.NS_Y))) + if edge_label is not None: + break + + if edge_label is not None: + data['label'] = edge_label.text + return data + + def find_graphml_keys(self, graph_element): + """Extracts all the keys and key defaults from the xml. + """ + graphml_keys = {} + graphml_key_defaults = {} + for k in graph_element.findall("{%s}key" % self.NS_GRAPHML): + attr_id = k.get("id") + attr_type=k.get('attr.type') + attr_name=k.get("attr.name") + yfiles_type=k.get("yfiles.type") + if yfiles_type is not None: + attr_name = yfiles_type + attr_type = 'yfiles' + if attr_type is None: + attr_type = "string" + warnings.warn("No key type for id %s. Using string"%attr_id) + if attr_name is None: + raise nx.NetworkXError("Unknown key for id %s in file."%attr_id) + graphml_keys[attr_id] = { + "name":attr_name, + "type":self.python_type[attr_type], + "for":k.get("for")} + # check for "default" subelement of key element + default=k.find("{%s}default" % self.NS_GRAPHML) + if default is not None: + graphml_key_defaults[attr_id]=default.text + return graphml_keys,graphml_key_defaults + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import xml.etree.ElementTree + except: + raise SkipTest("xml.etree.ElementTree not available") + +# fixture for nose tests +def teardown_module(module): + import os + try: + os.unlink('test.graphml') + except: + pass diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/__init__.py new file mode 100644 index 0000000..ea3db6e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/__init__.py @@ -0,0 +1,10 @@ +""" +********* +JSON data +********* +Generate and parse JSON serializable data for NetworkX graphs. +""" +from networkx.readwrite.json_graph.node_link import * +from networkx.readwrite.json_graph.adjacency import * +from networkx.readwrite.json_graph.tree import * +from networkx.readwrite.json_graph.serialize import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/adjacency.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/adjacency.py new file mode 100644 index 0000000..d5b0df2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/adjacency.py @@ -0,0 +1,123 @@ +# Copyright (C) 2011-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from copy import deepcopy +from itertools import count,repeat +import json +import networkx as nx +__author__ = """Aric Hagberg """ +__all__ = ['adjacency_data', 'adjacency_graph'] + +def adjacency_data(G): + """Return data in adjacency format that is suitable for JSON serialization + and use in Javascript documents. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + data : dict + A dictionary with node-link formatted data. + + Examples + -------- + >>> from networkx.readwrite import json_graph + >>> G = nx.Graph([(1,2)]) + >>> data = json_graph.adjacency_data(G) + + To serialize with json + + >>> import json + >>> s = json.dumps(data) + + Notes + ----- + Graph, node, and link attributes will be written when using this format + but attribute keys must be strings if you want to serialize the resulting + data with JSON. + + See Also + -------- + adjacency_graph, node_link_data, tree_data + """ + multigraph = G.is_multigraph() + data = {} + data['directed'] = G.is_directed() + data['multigraph'] = multigraph + data['graph'] = list(G.graph.items()) + data['nodes'] = [] + data['adjacency'] = [] + for n,nbrdict in G.adjacency_iter(): + data['nodes'].append(dict(id=n, **G.node[n])) + adj = [] + if multigraph: + for nbr,key in nbrdict.items(): + for k,d in key.items(): + adj.append(dict(id=nbr, key=k, **d)) + else: + for nbr,d in nbrdict.items(): + adj.append(dict(id=nbr, **d)) + data['adjacency'].append(adj) + return data + +def adjacency_graph(data, directed=False, multigraph=True): + """Return graph from adjacency data format. + + Parameters + ---------- + data : dict + Adjacency list formatted graph data + + Returns + ------- + G : NetworkX graph + A NetworkX graph object + + directed : bool + If True, and direction not specified in data, return a directed graph. + + multigraph : bool + If True, and multigraph not specified in data, return a multigraph. + + Examples + -------- + >>> from networkx.readwrite import json_graph + >>> G = nx.Graph([(1,2)]) + >>> data = json_graph.adjacency_data(G) + >>> H = json_graph.adjacency_graph(data) + + See Also + -------- + adjacency_graph, node_link_data, tree_data + """ + multigraph = data.get('multigraph',multigraph) + directed = data.get('directed',directed) + if multigraph: + graph = nx.MultiGraph() + else: + graph = nx.Graph() + if directed: + graph = graph.to_directed() + graph.graph = dict(data.get('graph',[])) + mapping=[] + for d in data['nodes']: + node_data = d.copy() + node = node_data.pop('id') + mapping.append(node) + graph.add_node(node, attr_dict=node_data) + for i,d in enumerate(data['adjacency']): + source = mapping[i] + for tdata in d: + target_data = tdata.copy() + target = target_data.pop('id') + key = target_data.pop('key', None) + if not multigraph or key is None: + graph.add_edge(source,target,attr_dict=tdata) + else: + graph.add_edge(source,target,key=key, attr_dict=tdata) + return graph diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/node_link.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/node_link.py new file mode 100644 index 0000000..03f150f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/node_link.py @@ -0,0 +1,116 @@ +# Copyright (C) 2011-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from itertools import count,repeat +import json +import networkx as nx +__author__ = """Aric Hagberg """ +__all__ = ['node_link_data', 'node_link_graph'] + +def node_link_data(G): + """Return data in node-link format that is suitable for JSON serialization + and use in Javascript documents. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + data : dict + A dictionary with node-link formatted data. + + Examples + -------- + >>> from networkx.readwrite import json_graph + >>> G = nx.Graph([(1,2)]) + >>> data = json_graph.node_link_data(G) + + To serialize with json + + >>> import json + >>> s = json.dumps(data) + + Notes + ----- + Graph, node, and link attributes are stored in this format but keys + for attributes must be strings if you want to serialize with JSON. + + See Also + -------- + node_link_graph, adjacency_data, tree_data + """ + multigraph = G.is_multigraph() + mapping = dict(zip(G,count())) + data = {} + data['directed'] = G.is_directed() + data['multigraph'] = multigraph + data['graph'] = list(G.graph.items()) + data['nodes'] = [ dict(id=n, **G.node[n]) for n in G ] + if multigraph: + data['links'] = [ dict(source=mapping[u], target=mapping[v], key=k, **d) + for u,v,k,d in G.edges(keys=True, data=True) ] + else: + data['links'] = [ dict(source=mapping[u], target=mapping[v], **d) + for u,v,d in G.edges(data=True) ] + + return data + + +def node_link_graph(data, directed=False, multigraph=True): + """Return graph from node-link data format. + + Parameters + ---------- + data : dict + node-link formatted graph data + + directed : bool + If True, and direction not specified in data, return a directed graph. + + multigraph : bool + If True, and multigraph not specified in data, return a multigraph. + + Returns + ------- + G : NetworkX graph + A NetworkX graph object + + Examples + -------- + >>> from networkx.readwrite import json_graph + >>> G = nx.Graph([(1,2)]) + >>> data = json_graph.node_link_data(G) + >>> H = json_graph.node_link_graph(data) + + See Also + -------- + node_link_data, adjacency_data, tree_data + """ + multigraph = data.get('multigraph',multigraph) + directed = data.get('directed',directed) + if multigraph: + graph = nx.MultiGraph() + else: + graph = nx.Graph() + if directed: + graph = graph.to_directed() + mapping=[] + graph.graph = dict(data.get('graph',[])) + c = count() + for d in data['nodes']: + node = d.get('id',next(c)) + mapping.append(node) + nodedata = dict((str(k),v) for k,v in d.items() if k!='id') + graph.add_node(node, **nodedata) + for d in data['links']: + link_data = d.copy() + source = link_data.pop('source') + target = link_data.pop('target') + edgedata = dict((str(k),v) for k,v in d.items() + if k!='source' and k!='target') + graph.add_edge(mapping[source],mapping[target],**edgedata) + return graph diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/serialize.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/serialize.py new file mode 100644 index 0000000..5861836 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/serialize.py @@ -0,0 +1,31 @@ +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from functools import partial,update_wrapper +import json +from networkx.readwrite.json_graph import node_link_data,node_link_graph +__author__ = """Aric Hagberg (hagberg@lanl.gov))""" +__all__ = ['dumps','loads','dump','load'] + +class NXJSONEncoder(json.JSONEncoder): + def default(self, o): + return node_link_data(o) + + +class NXJSONDecoder(json.JSONDecoder): + def decode(self, s): + d = json.loads(s) + return node_link_graph(d) + +# modification of json functions to serialize networkx graphs +dumps = partial(json.dumps, cls=NXJSONEncoder) +update_wrapper(dumps,json.dumps) +loads = partial(json.loads, cls=NXJSONDecoder) +update_wrapper(loads,json.loads) +dump = partial(json.dump, cls=NXJSONEncoder) +update_wrapper(dump,json.dump) +load = partial(json.load, cls=NXJSONDecoder) +update_wrapper(load,json.load) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_adjacency.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_adjacency.py new file mode 100644 index 0000000..f8bf214 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_adjacency.py @@ -0,0 +1,52 @@ +import json +from nose.tools import assert_equal, assert_raises, assert_not_equal,assert_true +import networkx as nx +from networkx.readwrite.json_graph import * + +class TestAdjacency: + + def test_graph(self): + G = nx.path_graph(4) + H = adjacency_graph(adjacency_data(G)) + nx.is_isomorphic(G,H) + + def test_graph_attributes(self): + G = nx.path_graph(4) + G.add_node(1,color='red') + G.add_edge(1,2,width=7) + G.graph['foo']='bar' + G.graph[1]='one' + + H = adjacency_graph(adjacency_data(G)) + assert_equal(H.graph['foo'],'bar') + assert_equal(H.node[1]['color'],'red') + assert_equal(H[1][2]['width'],7) + + d = json.dumps(adjacency_data(G)) + H = adjacency_graph(json.loads(d)) + assert_equal(H.graph['foo'],'bar') + assert_equal(H.graph[1],'one') + assert_equal(H.node[1]['color'],'red') + assert_equal(H[1][2]['width'],7) + + def test_digraph(self): + G = nx.DiGraph() + G.add_path([1,2,3]) + H = adjacency_graph(adjacency_data(G)) + assert_true(H.is_directed()) + nx.is_isomorphic(G,H) + + def test_multidigraph(self): + G = nx.MultiDiGraph() + G.add_path([1,2,3]) + H = adjacency_graph(adjacency_data(G)) + assert_true(H.is_directed()) + assert_true(H.is_multigraph()) + + def test_multigraph(self): + G = nx.MultiGraph() + G.add_edge(1,2,key='first') + G.add_edge(1,2,key='second',color='blue') + H = adjacency_graph(adjacency_data(G)) + nx.is_isomorphic(G,H) + assert_equal(H[1][2]['second']['color'],'blue') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_node_link.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_node_link.py new file mode 100644 index 0000000..5430e0d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_node_link.py @@ -0,0 +1,44 @@ +import json +from nose.tools import assert_equal, assert_raises, assert_not_equal,assert_true +import networkx as nx +from networkx.readwrite.json_graph import * + +class TestNodeLink: + + def test_graph(self): + G = nx.path_graph(4) + H = node_link_graph(node_link_data(G)) + nx.is_isomorphic(G,H) + + def test_graph_attributes(self): + G = nx.path_graph(4) + G.add_node(1,color='red') + G.add_edge(1,2,width=7) + G.graph[1]='one' + G.graph['foo']='bar' + + H = node_link_graph(node_link_data(G)) + assert_equal(H.graph['foo'],'bar') + assert_equal(H.node[1]['color'],'red') + assert_equal(H[1][2]['width'],7) + + d=json.dumps(node_link_data(G)) + H = node_link_graph(json.loads(d)) + assert_equal(H.graph['foo'],'bar') + assert_equal(H.graph[1],'one') + assert_equal(H.node[1]['color'],'red') + assert_equal(H[1][2]['width'],7) + + def test_digraph(self): + G = nx.DiGraph() + H = node_link_graph(node_link_data(G)) + assert_true(H.is_directed()) + + + def test_multigraph(self): + G = nx.MultiGraph() + G.add_edge(1,2,key='first') + G.add_edge(1,2,key='second',color='blue') + H = node_link_graph(node_link_data(G)) + nx.is_isomorphic(G,H) + assert_equal(H[1][2]['second']['color'],'blue') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_serialize.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_serialize.py new file mode 100644 index 0000000..cc8d7ff --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_serialize.py @@ -0,0 +1,49 @@ +import json +from nose.tools import assert_equal, assert_raises, assert_not_equal,assert_true +import networkx as nx +from networkx.readwrite.json_graph import * + +class TestAdjacency: + + def test_graph(self): + G = nx.path_graph(4) + H = loads(dumps(G)) + nx.is_isomorphic(G,H) + + def test_graph_attributes(self): + G = nx.path_graph(4) + G.add_node(1,color='red') + G.add_edge(1,2,width=7) + G.graph['foo']='bar' + G.graph[1]='one' + + H = loads(dumps(G)) + assert_equal(H.graph['foo'],'bar') + assert_equal(H.graph[1],'one') + assert_equal(H.node[1]['color'],'red') + assert_equal(H[1][2]['width'],7) + + try: + from StringIO import StringIO + except: + from io import StringIO + io = StringIO() + dump(G,io) + io.seek(0) + H=load(io) + assert_equal(H.graph['foo'],'bar') + assert_equal(H.graph[1],'one') + assert_equal(H.node[1]['color'],'red') + assert_equal(H[1][2]['width'],7) + + + def test_digraph(self): + G = nx.DiGraph() + H = loads(dumps(G)) + assert_true(H.is_directed()) + + def test_multidigraph(self): + G = nx.MultiDiGraph() + H = loads(dumps(G)) + assert_true(H.is_directed()) + assert_true(H.is_multigraph()) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_tree.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_tree.py new file mode 100644 index 0000000..19ba8f2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tests/test_tree.py @@ -0,0 +1,29 @@ +import json +from nose.tools import assert_equal, assert_raises, assert_not_equal,assert_true +import networkx as nx +from networkx.readwrite.json_graph import * + +class TestTree: + + def test_graph(self): + G=nx.DiGraph() + G.add_nodes_from([1,2,3],color='red') + G.add_edge(1,2,foo=7) + G.add_edge(1,3,foo=10) + G.add_edge(3,4,foo=10) + H = tree_graph(tree_data(G,1)) + nx.is_isomorphic(G,H) + + def test_graph_attributes(self): + G=nx.DiGraph() + G.add_nodes_from([1,2,3],color='red') + G.add_edge(1,2,foo=7) + G.add_edge(1,3,foo=10) + G.add_edge(3,4,foo=10) + H = tree_graph(tree_data(G,1)) + assert_equal(H.node[1]['color'],'red') + + d = json.dumps(tree_data(G,1)) + H = tree_graph(json.loads(d)) + assert_equal(H.node[1]['color'],'red') + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tree.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tree.py new file mode 100644 index 0000000..d2229e9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/json_graph/tree.py @@ -0,0 +1,113 @@ +# Copyright (C) 2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +from itertools import count,repeat +import json +import networkx as nx +__author__ = """Aric Hagberg (hagberg@lanl.gov))""" +__all__ = ['tree_data', + 'tree_graph'] + +def tree_data(G, root): + """Return data in tree format that is suitable for JSON serialization + and use in Javascript documents. + + Parameters + ---------- + G : NetworkX graph + G must be an oriented tree + + root : node + The root of the tree + + Returns + ------- + data : dict + A dictionary with node-link formatted data. + + Examples + -------- + >>> from networkx.readwrite import json_graph + >>> G = nx.DiGraph([(1,2)]) + >>> data = json_graph.tree_data(G,root=1) + + To serialize with json + + >>> import json + >>> s = json.dumps(data) + + Notes + ----- + Node attributes are stored in this format but keys + for attributes must be strings if you want to serialize with JSON. + + Graph and edge attributes are not stored. + + See Also + -------- + tree_graph, node_link_data, node_link_data + """ + if not G.number_of_nodes()==G.number_of_edges()+1: + raise TypeError("G is not a tree.") + if not G.is_directed(): + raise TypeError("G is not directed") + def add_children(n, G): + nbrs = G[n] + if len(nbrs) == 0: + return [] + children = [] + for child in nbrs: + d = dict(id=child, **G.node[child]) + c = add_children(child,G) + if c: + d['children'] = c + children.append(d) + return children + data = dict(id=root, **G.node[root]) + data['children'] = add_children(root,G) + return data + +def tree_graph(data): + """Return graph from tree data format. + + Parameters + ---------- + data : dict + Tree formatted graph data + + Returns + ------- + G : NetworkX DiGraph + + Examples + -------- + >>> from networkx.readwrite import json_graph + >>> G = nx.DiGraph([(1,2)]) + >>> data = json_graph.tree_data(G,root=1) + >>> H = json_graph.tree_graph(data) + + See Also + -------- + tree_graph, node_link_data, adjacency_data + """ + graph = nx.DiGraph() + def add_children(parent, children): + for data in children: + child = data['id'] + graph.add_edge(parent, child) + grandchildren = data.get('children',[]) + if grandchildren: + add_children(child,grandchildren) + nodedata = dict((str(k),v) for k,v in data.items() + if k!='id' and k!='children') + graph.add_node(child,attr_dict=nodedata) + root = data['id'] + children = data.get('children',[]) + nodedata = dict((k,v) for k,v in data.items() + if k!='id' and k!='children') + graph.add_node(root, attr_dict=nodedata) + add_children(root, children) + return graph diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/leda.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/leda.py new file mode 100644 index 0000000..a4d17db --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/leda.py @@ -0,0 +1,106 @@ +""" +Read graphs in LEDA format. + +LEDA is a C++ class library for efficient data types and algorithms. + +Format +------ +See http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html + +""" +# Original author: D. Eppstein, UC Irvine, August 12, 2003. +# The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain. +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['read_leda', 'parse_leda'] + +import networkx as nx +from networkx.exception import NetworkXError +from networkx.utils import open_file, is_string_like + +@open_file(0,mode='rb') +def read_leda(path, encoding='UTF-8'): + """Read graph in LEDA format from path. + + Parameters + ---------- + path : file or string + File or filename to read. Filenames ending in .gz or .bz2 will be + uncompressed. + + Returns + ------- + G : NetworkX graph + + Examples + -------- + G=nx.read_leda('file.leda') + + References + ---------- + .. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html + """ + lines=(line.decode(encoding) for line in path) + G=parse_leda(lines) + return G + + +def parse_leda(lines): + """Read graph in LEDA format from string or iterable. + + Parameters + ---------- + lines : string or iterable + Data in LEDA format. + + Returns + ------- + G : NetworkX graph + + Examples + -------- + G=nx.parse_leda(string) + + References + ---------- + .. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html + """ + if is_string_like(lines): lines=iter(lines.split('\n')) + lines = iter([line.rstrip('\n') for line in lines \ + if not (line.startswith('#') or line.startswith('\n') or line=='')]) + for i in range(3): + next(lines) + # Graph + du = int(next(lines)) # -1=directed, -2=undirected + if du==-1: + G = nx.DiGraph() + else: + G = nx.Graph() + + # Nodes + n =int(next(lines)) # number of nodes + node={} + for i in range(1,n+1): # LEDA counts from 1 to n + symbol=next(lines).rstrip().strip('|{}| ') + if symbol=="": symbol=str(i) # use int if no label - could be trouble + node[i]=symbol + + G.add_nodes_from([s for i,s in node.items()]) + + # Edges + m = int(next(lines)) # number of edges + for i in range(m): + try: + s,t,reversal,label=next(lines).split() + except: + raise NetworkXError('Too few fields in LEDA.GRAPH edge %d'%(i+1)) + # BEWARE: no handling of reversal edges + G.add_edge(node[int(s)],node[int(t)],label=label[2:-2]) + return G + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/multiline_adjlist.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/multiline_adjlist.py new file mode 100644 index 0000000..30c3234 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/multiline_adjlist.py @@ -0,0 +1,390 @@ +# -*- coding: utf-8 -*- +""" +************************* +Multi-line Adjacency List +************************* +Read and write NetworkX graphs as multi-line adjacency lists. + +The multi-line adjacency list format is useful for graphs with +nodes that can be meaningfully represented as strings. With this format +simple edge data can be stored but node or graph data is not. + +Format +------ +The first label in a line is the source node label followed by the node degree +d. The next d lines are target node labels and optional edge data. +That pattern repeats for all nodes in the graph. + +The graph with edges a-b, a-c, d-e can be represented as the following +adjacency list (anything following the # in a line is a comment):: + + # example.multiline-adjlist + a 2 + b + c + d 1 + e +""" +__author__ = '\n'.join(['Aric Hagberg ', + 'Dan Schult ', + 'Loïc Séguin-C. ']) +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['generate_multiline_adjlist', + 'write_multiline_adjlist', + 'parse_multiline_adjlist', + 'read_multiline_adjlist'] + +from networkx.utils import make_str, open_file +import networkx as nx + +def generate_multiline_adjlist(G, delimiter = ' '): + """Generate a single line of the graph G in multiline adjacency list format. + + Parameters + ---------- + G : NetworkX graph + + delimiter : string, optional + Separator for node labels + + Returns + ------- + lines : string + Lines of data in multiline adjlist format. + + Examples + -------- + >>> G = nx.lollipop_graph(4, 3) + >>> for line in nx.generate_multiline_adjlist(G): + ... print(line) + 0 3 + 1 {} + 2 {} + 3 {} + 1 2 + 2 {} + 3 {} + 2 1 + 3 {} + 3 1 + 4 {} + 4 1 + 5 {} + 5 1 + 6 {} + 6 0 + + See Also + -------- + write_multiline_adjlist, read_multiline_adjlist + """ + if G.is_directed(): + if G.is_multigraph(): + for s,nbrs in G.adjacency_iter(): + nbr_edges=[ (u,data) + for u,datadict in nbrs.items() + for key,data in datadict.items()] + deg=len(nbr_edges) + yield make_str(s)+delimiter+"%i"%(deg) + for u,d in nbr_edges: + if d is None: + yield make_str(u) + else: + yield make_str(u)+delimiter+make_str(d) + else: # directed single edges + for s,nbrs in G.adjacency_iter(): + deg=len(nbrs) + yield make_str(s)+delimiter+"%i"%(deg) + for u,d in nbrs.items(): + if d is None: + yield make_str(u) + else: + yield make_str(u)+delimiter+make_str(d) + else: # undirected + if G.is_multigraph(): + seen=set() # helper dict used to avoid duplicate edges + for s,nbrs in G.adjacency_iter(): + nbr_edges=[ (u,data) + for u,datadict in nbrs.items() + if u not in seen + for key,data in datadict.items()] + deg=len(nbr_edges) + yield make_str(s)+delimiter+"%i"%(deg) + for u,d in nbr_edges: + if d is None: + yield make_str(u) + else: + yield make_str(u)+delimiter+make_str(d) + seen.add(s) + else: # undirected single edges + seen=set() # helper dict used to avoid duplicate edges + for s,nbrs in G.adjacency_iter(): + nbr_edges=[ (u,d) for u,d in nbrs.items() if u not in seen] + deg=len(nbr_edges) + yield make_str(s)+delimiter+"%i"%(deg) + for u,d in nbr_edges: + if d is None: + yield make_str(u) + else: + yield make_str(u)+delimiter+make_str(d) + seen.add(s) + +@open_file(1,mode='wb') +def write_multiline_adjlist(G, path, delimiter=' ', + comments='#', encoding = 'utf-8'): + """ Write the graph G in multiline adjacency list format to path + + Parameters + ---------- + G : NetworkX graph + + comments : string, optional + Marker for comment lines + + delimiter : string, optional + Separator for node labels + + encoding : string, optional + Text encoding. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_multiline_adjlist(G,"test.adjlist") + + The path can be a file handle or a string with the name of the file. If a + file handle is provided, it has to be opened in 'wb' mode. + + >>> fh=open("test.adjlist",'wb') + >>> nx.write_multiline_adjlist(G,fh) + + Filenames ending in .gz or .bz2 will be compressed. + + >>> nx.write_multiline_adjlist(G,"test.adjlist.gz") + + See Also + -------- + read_multiline_adjlist + """ + import sys + import time + + pargs=comments+" ".join(sys.argv) + header = ("%s\n" % (pargs) + + comments + " GMT %s\n" % (time.asctime(time.gmtime())) + + comments + " %s\n" % (G.name)) + path.write(header.encode(encoding)) + + for multiline in generate_multiline_adjlist(G, delimiter): + multiline+='\n' + path.write(multiline.encode(encoding)) + +def parse_multiline_adjlist(lines, comments = '#', delimiter = None, + create_using = None, nodetype = None, + edgetype = None): + """Parse lines of a multiline adjacency list representation of a graph. + + Parameters + ---------- + lines : list or iterator of strings + Input data in multiline adjlist format + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + nodetype : Python type, optional + Convert nodes to this type. + + comments : string, optional + Marker for comment lines + + delimiter : string, optional + Separator for node labels. The default is whitespace. + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + + Returns + ------- + G: NetworkX graph + The graph corresponding to the lines in multiline adjacency list format. + + Examples + -------- + >>> lines = ['1 2', + ... "2 {'weight':3, 'name': 'Frodo'}", + ... "3 {}", + ... "2 1", + ... "5 {'weight':6, 'name': 'Saruman'}"] + >>> G = nx.parse_multiline_adjlist(iter(lines), nodetype = int) + >>> G.nodes() + [1, 2, 3, 5] + """ + from ast import literal_eval + if create_using is None: + G=nx.Graph() + else: + try: + G=create_using + G.clear() + except: + raise TypeError("Input graph is not a networkx graph type") + + for line in lines: + p=line.find(comments) + if p>=0: + line = line[:p] + if not line: continue + try: + (u,deg)=line.strip().split(delimiter) + deg=int(deg) + except: + raise TypeError("Failed to read node and degree on line (%s)"%line) + if nodetype is not None: + try: + u=nodetype(u) + except: + raise TypeError("Failed to convert node (%s) to type %s"\ + %(u,nodetype)) + G.add_node(u) + for i in range(deg): + while True: + try: + line = next(lines) + except StopIteration: + msg = "Failed to find neighbor for node (%s)" % (u,) + raise TypeError(msg) + p=line.find(comments) + if p>=0: + line = line[:p] + if line: break + vlist=line.strip().split(delimiter) + numb=len(vlist) + if numb<1: + continue # isolated node + v=vlist.pop(0) + data=''.join(vlist) + if nodetype is not None: + try: + v=nodetype(v) + except: + raise TypeError( + "Failed to convert node (%s) to type %s"\ + %(v,nodetype)) + if edgetype is not None: + try: + edgedata={'weight':edgetype(data)} + except: + raise TypeError( + "Failed to convert edge data (%s) to type %s"\ + %(data, edgetype)) + else: + try: # try to evaluate + edgedata=literal_eval(data) + except: + edgedata={} + G.add_edge(u,v,attr_dict=edgedata) + + return G + +@open_file(0,mode='rb') +def read_multiline_adjlist(path, comments="#", delimiter=None, + create_using=None, + nodetype=None, edgetype=None, + encoding = 'utf-8'): + """Read graph in multi-line adjacency list format from path. + + Parameters + ---------- + path : string or file + Filename or file handle to read. + Filenames ending in .gz or .bz2 will be uncompressed. + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + nodetype : Python type, optional + Convert nodes to this type. + + edgetype : Python type, optional + Convert edge data to this type. + + comments : string, optional + Marker for comment lines + + delimiter : string, optional + Separator for node labels. The default is whitespace. + + create_using: NetworkX graph container + Use given NetworkX graph for holding nodes or edges. + + + Returns + ------- + G: NetworkX graph + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_multiline_adjlist(G,"test.adjlist") + >>> G=nx.read_multiline_adjlist("test.adjlist") + + The path can be a file or a string with the name of the file. If a + file s provided, it has to be opened in 'rb' mode. + + >>> fh=open("test.adjlist", 'rb') + >>> G=nx.read_multiline_adjlist(fh) + + Filenames ending in .gz or .bz2 will be compressed. + + >>> nx.write_multiline_adjlist(G,"test.adjlist.gz") + >>> G=nx.read_multiline_adjlist("test.adjlist.gz") + + The optional nodetype is a function to convert node strings to nodetype. + + For example + + >>> G=nx.read_multiline_adjlist("test.adjlist", nodetype=int) + + will attempt to convert all nodes to integer type. + + The optional edgetype is a function to convert edge data strings to + edgetype. + + >>> G=nx.read_multiline_adjlist("test.adjlist") + + The optional create_using parameter is a NetworkX graph container. + The default is Graph(), an undirected graph. To read the data as + a directed graph use + + >>> G=nx.read_multiline_adjlist("test.adjlist", create_using=nx.DiGraph()) + + Notes + ----- + This format does not store graph, node, or edge data. + + See Also + -------- + write_multiline_adjlist + """ + lines = (line.decode(encoding) for line in path) + return parse_multiline_adjlist(lines, + comments = comments, + delimiter = delimiter, + create_using = create_using, + nodetype = nodetype, + edgetype = edgetype) + + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.adjlist') + os.unlink('test.adjlist.gz') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_shp.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_shp.py new file mode 100644 index 0000000..d873ea6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_shp.py @@ -0,0 +1,224 @@ +""" +********* +Shapefile +********* + +Generates a networkx.DiGraph from point and line shapefiles. + +"The Esri Shapefile or simply a shapefile is a popular geospatial vector +data format for geographic information systems software. It is developed +and regulated by Esri as a (mostly) open specification for data +interoperability among Esri and other software products." +See http://en.wikipedia.org/wiki/Shapefile for additional information. +""" +# Copyright (C) 2004-2010 by +# Ben Reilly +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """Ben Reilly (benwreilly@gmail.com)""" +__all__ = ['read_shp', 'write_shp'] + + +def read_shp(path): + """Generates a networkx.DiGraph from shapefiles. Point geometries are + translated into nodes, lines into edges. Coordinate tuples are used as + keys. Attributes are preserved, line geometries are simplified into start + and end coordinates. Accepts a single shapefile or directory of many + shapefiles. + + "The Esri Shapefile or simply a shapefile is a popular geospatial vector + data format for geographic information systems software [1]_." + + Parameters + ---------- + path : file or string + File, directory, or filename to read. + + Returns + ------- + G : NetworkX graph + + Examples + -------- + >>> G=nx.read_shp('test.shp') # doctest: +SKIP + + References + ---------- + .. [1] http://en.wikipedia.org/wiki/Shapefile + """ + try: + from osgeo import ogr + except ImportError: + raise ImportError("read_shp requires OGR: http://www.gdal.org/") + + net = nx.DiGraph() + + def getfieldinfo(lyr, feature, flds): + f = feature + return [f.GetField(f.GetFieldIndex(x)) for x in flds] + + def addlyr(lyr, fields): + for findex in xrange(lyr.GetFeatureCount()): + f = lyr.GetFeature(findex) + flddata = getfieldinfo(lyr, f, fields) + g = f.geometry() + attributes = dict(zip(fields, flddata)) + attributes["ShpName"] = lyr.GetName() + if g.GetGeometryType() == 1: # point + net.add_node((g.GetPoint_2D(0)), attributes) + if g.GetGeometryType() == 2: # linestring + attributes["Wkb"] = g.ExportToWkb() + attributes["Wkt"] = g.ExportToWkt() + attributes["Json"] = g.ExportToJson() + last = g.GetPointCount() - 1 + net.add_edge(g.GetPoint_2D(0), g.GetPoint_2D(last), attributes) + + if isinstance(path, str): + shp = ogr.Open(path) + lyrcount = shp.GetLayerCount() # multiple layers indicate a directory + for lyrindex in xrange(lyrcount): + lyr = shp.GetLayerByIndex(lyrindex) + flds = [x.GetName() for x in lyr.schema] + addlyr(lyr, flds) + return net + + +def write_shp(G, outdir): + """Writes a networkx.DiGraph to two shapefiles, edges and nodes. + Nodes and edges are expected to have a Well Known Binary (Wkb) or + Well Known Text (Wkt) key in order to generate geometries. Also + acceptable are nodes with a numeric tuple key (x,y). + + "The Esri Shapefile or simply a shapefile is a popular geospatial vector + data format for geographic information systems software [1]_." + + Parameters + ---------- + outdir : directory path + Output directory for the two shapefiles. + + Returns + ------- + None + + Examples + -------- + nx.write_shp(digraph, '/shapefiles') # doctest +SKIP + + References + ---------- + .. [1] http://en.wikipedia.org/wiki/Shapefile + """ + try: + from osgeo import ogr + except ImportError: + raise ImportError("write_shp requires OGR: http://www.gdal.org/") + # easier to debug in python if ogr throws exceptions + ogr.UseExceptions() + + def netgeometry(key, data): + if 'Wkb' in data: + geom = ogr.CreateGeometryFromWkb(data['Wkb']) + elif 'Wkt' in data: + geom = ogr.CreateGeometryFromWkt(data['Wkt']) + elif type(key[0]).__name__ == 'tuple': # edge keys are packed tuples + geom = ogr.Geometry(ogr.wkbLineString) + _from, _to = key[0], key[1] + try: + geom.SetPoint(0, *_from) + geom.SetPoint(1, *_to) + except TypeError: + # assume user used tuple of int and choked ogr + _ffrom = [float(x) for x in _from] + _fto = [float(x) for x in _to] + geom.SetPoint(0, *_ffrom) + geom.SetPoint(1, *_fto) + else: + geom = ogr.Geometry(ogr.wkbPoint) + try: + geom.SetPoint(0, *key) + except TypeError: + # assume user used tuple of int and choked ogr + fkey = [float(x) for x in key] + geom.SetPoint(0, *fkey) + + return geom + + # Create_feature with new optional attributes arg (should be dict type) + def create_feature(geometry, lyr, attributes=None): + feature = ogr.Feature(lyr.GetLayerDefn()) + feature.SetGeometry(g) + if attributes != None: + # Loop through attributes, assigning data to each field + for field, data in attributes.iter(): + feature.SetField(field, data) + lyr.CreateFeature(feature) + feature.Destroy() + + drv = ogr.GetDriverByName("ESRI Shapefile") + shpdir = drv.CreateDataSource(outdir) + # delete pre-existing output first otherwise ogr chokes + try: + shpdir.DeleteLayer("nodes") + except: + pass + nodes = shpdir.CreateLayer("nodes", None, ogr.wkbPoint) + for n in G: + data = G.node[n] or {} + g = netgeometry(n, data) + create_feature(g, nodes) + try: + shpdir.DeleteLayer("edges") + except: + pass + edges = shpdir.CreateLayer("edges", None, ogr.wkbLineString) + + # New edge attribute write support merged into edge loop + fields = {} # storage for field names and their data types + attributes = {} # storage for attribute data (indexed by field names) + + # Conversion dict between python and ogr types + OGRTypes = {int: ogr.OFTInteger, str: ogr.OFTString, float: ogr.OFTReal} + + # Edge loop + for e in G.edges(data=True): + data = G.get_edge_data(*e) + g = netgeometry(e, data) + # Loop through attribute data in edges + for key, data in e[2].iter(): + # Reject spatial data not required for attribute table + if (key != 'Json' and key != 'Wkt' and key != 'Wkb' + and key != 'ShpName'): + # For all edges check/add field and data type to fields dict + if key not in fields: + # Field not in previous edges so add to dict + if type(data) in OGRTypes: + fields[key] = OGRTypes[type(data)] + else: + # Data type not supported, default to string (char 80) + fields[key] = ogr.OFTString + # Create the new field + newfield = ogr.FieldDefn(key, fields[key]) + edges.CreateField(newfield) + # Store the data from new field to dict for CreateLayer() + attributes[key] = data + else: + # Field already exists, add data to dict for CreateLayer() + attributes[key] = data + # Create the feature with, passing new attribute data + create_feature(g, edges, attributes) + + nodes, edges = None, None + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import ogr + except: + raise SkipTest("OGR not available") diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_yaml.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_yaml.py new file mode 100644 index 0000000..00c916f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/nx_yaml.py @@ -0,0 +1,109 @@ +""" +**** +YAML +**** +Read and write NetworkX graphs in YAML format. + +"YAML is a data serialization format designed for human readability +and interaction with scripting languages." +See http://www.yaml.org for documentation. + +Format +------ +http://pyyaml.org/wiki/PyYAML + +""" +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['read_yaml', 'write_yaml'] + +import networkx as nx +from networkx.utils import open_file + +@open_file(1,mode='w') +def write_yaml(G, path, encoding='UTF-8', **kwds): + """Write graph G in YAML format to path. + + YAML is a data serialization format designed for human readability + and interaction with scripting languages [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be compressed. + encoding: string, optional + Specify which encoding to use when writing file. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_yaml(G,'test.yaml') + + References + ---------- + .. [1] http://www.yaml.org + """ + try: + import yaml + except ImportError: + raise ImportError("write_yaml() requires PyYAML: http://pyyaml.org/") + yaml.dump(G, path, **kwds) + +@open_file(0,mode='r') +def read_yaml(path): + """Read graph in YAML format from path. + + YAML is a data serialization format designed for human readability + and interaction with scripting languages [1]_. + + Parameters + ---------- + path : file or string + File or filename to read. Filenames ending in .gz or .bz2 + will be uncompressed. + + Returns + ------- + G : NetworkX graph + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_yaml(G,'test.yaml') + >>> G=nx.read_yaml('test.yaml') + + References + ---------- + .. [1] http://www.yaml.org + + """ + try: + import yaml + except ImportError: + raise ImportError("read_yaml() requires PyYAML: http://pyyaml.org/") + + G=yaml.load(path) + return G + + +# fixture for nose tests +def setup_module(module): + from nose import SkipTest + try: + import yaml + except: + raise SkipTest("PyYAML not available") + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.yaml') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/p2g.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/p2g.py new file mode 100644 index 0000000..92dca9d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/p2g.py @@ -0,0 +1,107 @@ +""" +This module provides the following: read and write of p2g format +used in metabolic pathway studies. + +See http://www.cs.purdue.edu/homes/koyuturk/pathway/ for a description. + +The summary is included here: + +A file that describes a uniquely labeled graph (with extension ".gr") +format looks like the following: + + +name +3 4 +a +1 2 +b + +c +0 2 + +"name" is simply a description of what the graph corresponds to. The +second line displays the number of nodes and number of edges, +respectively. This sample graph contains three nodes labeled "a", "b", +and "c". The rest of the graph contains two lines for each node. The +first line for a node contains the node label. After the declaration +of the node label, the out-edges of that node in the graph are +provided. For instance, "a" is linked to nodes 1 and 2, which are +labeled "b" and "c", while the node labeled "b" has no outgoing +edges. Observe that node labeled "c" has an outgoing edge to +itself. Indeed, self-loops are allowed. Node index starts from 0. + +""" +# Copyright (C) 2008-2012 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx +from networkx.utils import is_string_like,open_file +__author__ = '\n'.join(['Willem Ligtenberg (w.p.a.ligtenberg@tue.nl)', + 'Aric Hagberg (aric.hagberg@gmail.com)']) + +@open_file(1,mode='w') +def write_p2g(G, path, encoding = 'utf-8'): + """Write NetworkX graph in p2g format. + + Notes + ----- + This format is meant to be used with directed graphs with + possible self loops. + """ + path.write(("%s\n"%G.name).encode(encoding)) + path.write(("%s %s\n"%(G.order(),G.size())).encode(encoding)) + nodes = G.nodes() + # make dictionary mapping nodes to integers + nodenumber=dict(zip(nodes,range(len(nodes)))) + for n in nodes: + path.write(("%s\n"%n).encode(encoding)) + for nbr in G.neighbors(n): + path.write(("%s "%nodenumber[nbr]).encode(encoding)) + path.write("\n".encode(encoding)) + +@open_file(0,mode='r') +def read_p2g(path, encoding='utf-8'): + """Read graph in p2g format from path. + + Returns + ------- + MultiDiGraph + + Notes + ----- + If you want a DiGraph (with no self loops allowed and no edge data) + use D=networkx.DiGraph(read_p2g(path)) + """ + lines = (line.decode(encoding) for line in path) + G=parse_p2g(lines) + return G + +def parse_p2g(lines): + """Parse p2g format graph from string or iterable. + + Returns + ------- + MultiDiGraph + """ + description = next(lines).strip() + # are multiedges (parallel edges) allowed? + G=networkx.MultiDiGraph(name=description,selfloops=True) + nnodes,nedges=map(int,next(lines).split()) + nodelabel={} + nbrs={} + # loop over the nodes keeping track of node labels and out neighbors + # defer adding edges until all node labels are known + for i in range(nnodes): + n=next(lines).strip() + nodelabel[i]=n + G.add_node(n) + nbrs[n]=map(int,next(lines).split()) + # now we know all of the node labels so we can add the edges + # with the correct labels + for n in G: + for nbr in nbrs[n]: + G.add_edge(n,nodelabel[nbr]) + return G diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/pajek.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/pajek.py new file mode 100644 index 0000000..ffc3641 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/pajek.py @@ -0,0 +1,231 @@ +""" +***** +Pajek +***** +Read graphs in Pajek format. + +This implementation handles directed and undirected graphs including +those with self loops and parallel edges. + +Format +------ +See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm +for format information. +""" +# Copyright (C) 2008-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +from networkx.utils import is_string_like, open_file, make_str +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +__all__ = ['read_pajek', 'parse_pajek', 'generate_pajek', 'write_pajek'] + +def generate_pajek(G): + """Generate lines in Pajek graph format. + + Parameters + ---------- + G : graph + A Networkx graph + + References + ---------- + See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm + for format information. + """ + if G.name=='': + name='NetworkX' + else: + name=G.name + # Apparently many Pajek format readers can't process this line + # So we'll leave it out for now. + # yield '*network %s'%name + + # write nodes with attributes + yield '*vertices %s'%(G.order()) + nodes = G.nodes() + # make dictionary mapping nodes to integers + nodenumber=dict(zip(nodes,range(1,len(nodes)+1))) + for n in nodes: + na=G.node.get(n,{}) + x=na.get('x',0.0) + y=na.get('y',0.0) + id=int(na.get('id',nodenumber[n])) + nodenumber[n]=id + shape=na.get('shape','ellipse') + s=' '.join(map(make_qstr,(id,n,x,y,shape))) + for k,v in na.items(): + s+=' %s %s'%(make_qstr(k),make_qstr(v)) + yield s + + # write edges with attributes + if G.is_directed(): + yield '*arcs' + else: + yield '*edges' + for u,v,edgedata in G.edges(data=True): + d=edgedata.copy() + value=d.pop('weight',1.0) # use 1 as default edge value + s=' '.join(map(make_qstr,(nodenumber[u],nodenumber[v],value))) + for k,v in d.items(): + s+=' %s %s'%(make_qstr(k),make_qstr(v)) + s+=' %s %s'%(k,v) + yield s + +@open_file(1,mode='wb') +def write_pajek(G, path, encoding='UTF-8'): + """Write graph in Pajek format to path. + + Parameters + ---------- + G : graph + A Networkx graph + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be compressed. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_pajek(G, "test.net") + + References + ---------- + See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm + for format information. + """ + for line in generate_pajek(G): + line+='\n' + path.write(line.encode(encoding)) + +@open_file(0,mode='rb') +def read_pajek(path,encoding='UTF-8'): + """Read graph in Pajek format from path. + + Parameters + ---------- + path : file or string + File or filename to write. + Filenames ending in .gz or .bz2 will be uncompressed. + + Returns + ------- + G : NetworkX MultiGraph or MultiDiGraph. + + Examples + -------- + >>> G=nx.path_graph(4) + >>> nx.write_pajek(G, "test.net") + >>> G=nx.read_pajek("test.net") + + To create a Graph instead of a MultiGraph use + + >>> G1=nx.Graph(G) + + References + ---------- + See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm + for format information. + """ + lines = (line.decode(encoding) for line in path) + return parse_pajek(lines) + +def parse_pajek(lines): + """Parse Pajek format graph from string or iterable. + + Parameters + ---------- + lines : string or iterable + Data in Pajek format. + + Returns + ------- + G : NetworkX graph + + See Also + -------- + read_pajek() + + """ + import shlex + # multigraph=False + if is_string_like(lines): lines=iter(lines.split('\n')) + lines = iter([line.rstrip('\n') for line in lines]) + G=nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes + while lines: + try: + l=next(lines) + except: #EOF + break + if l.lower().startswith("*network"): + label,name=l.split() + G.name=name + if l.lower().startswith("*vertices"): + nodelabels={} + l,nnodes=l.split() + for i in range(int(nnodes)): + splitline=shlex.split(str(next(lines))) + id,label=splitline[0:2] + G.add_node(label) + nodelabels[id]=label + G.node[label]={'id':id} + try: + x,y,shape=splitline[2:5] + G.node[label].update({'x':float(x), + 'y':float(y), + 'shape':shape}) + except: + pass + extra_attr=zip(splitline[5::2],splitline[6::2]) + G.node[label].update(extra_attr) + if l.lower().startswith("*edges") or l.lower().startswith("*arcs"): + if l.lower().startswith("*edge"): + # switch from multidigraph to multigraph + G=nx.MultiGraph(G) + if l.lower().startswith("*arcs"): + # switch to directed with multiple arcs for each existing edge + G=G.to_directed() + for l in lines: + splitline=shlex.split(str(l)) + if len(splitline)<2: + continue + ui,vi=splitline[0:2] + u=nodelabels.get(ui,ui) + v=nodelabels.get(vi,vi) + # parse the data attached to this edge and put in a dictionary + edge_data={} + try: + # there should always be a single value on the edge? + w=splitline[2:3] + edge_data.update({'weight':float(w[0])}) + except: + pass + # if there isn't, just assign a 1 +# edge_data.update({'value':1}) + extra_attr=zip(splitline[3::2],splitline[4::2]) + edge_data.update(extra_attr) + # if G.has_edge(u,v): + # multigraph=True + G.add_edge(u,v,**edge_data) + return G + + + +def make_qstr(t): + """Return the string representation of t. + Add outer double-quotes if the string has a space. + """ + if not is_string_like(t): + t = str(t) + if " " in t: + t=r'"%s"'%t + return t + + +# fixture for nose tests +def teardown_module(module): + import os + os.unlink('test.net') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/sparsegraph6.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/sparsegraph6.py new file mode 100644 index 0000000..440c92a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/sparsegraph6.py @@ -0,0 +1,169 @@ +""" +************** +SparseGraph 6 +************** +Read graphs in graph6 and sparse6 format. + +Format +------ + +"graph6 and sparse6 are formats for storing undirected graphs in a +compact manner, using only printable ASCII characters. Files in these +formats have text type and contain one line per graph." +http://cs.anu.edu.au/~bdm/data/formats.html + +See http://cs.anu.edu.au/~bdm/data/formats.txt for details. +""" +# Original author: D. Eppstein, UC Irvine, August 12, 2003. +# The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain. +__author__ = """Aric Hagberg (hagberg@lanl.gov)""" +# Copyright (C) 2004-2010 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +__all__ = ['read_graph6', 'parse_graph6', 'read_graph6_list', + 'read_sparse6', 'parse_sparse6', 'read_sparse6_list'] + +import networkx as nx +from networkx.exception import NetworkXError +from networkx.utils import open_file + +# graph6 + +def read_graph6(path): + """Read simple undirected graphs in graph6 format from path. + + Returns a single Graph. + """ + return read_graph6_list(path)[0] + +def parse_graph6(str): + """Read a simple undirected graph in graph6 format from string. + + Returns a single Graph. + """ + def bits(): + """Return sequence of individual bits from 6-bit-per-value + list of data values.""" + for d in data: + for i in [5,4,3,2,1,0]: + yield (d>>i)&1 + + if str.startswith('>>graph6<<'): + str = str[10:] + data = graph6data(str) + n, data = graph6n(data) + nd = (n*(n-1)//2 + 5) // 6 + if len(data) != nd: + raise NetworkXError(\ + 'Expected %d bits but got %d in graph6' % (n*(n-1)//2, len(data)*6)) + + G=nx.Graph() + G.add_nodes_from(range(n)) + for (i,j),b in zip([(i,j) for j in range(1,n) for i in range(j)], bits()): + if b: G.add_edge(i,j) + return G + +@open_file(0,mode='rt') +def read_graph6_list(path): + """Read simple undirected graphs in graph6 format from path. + + Returns a list of Graphs, one for each line in file. + """ + glist=[] + for line in path: + line = line.strip() + if not len(line): continue + glist.append(parse_graph6(line)) + return glist + +# sparse6 + +def read_sparse6(path): + """Read simple undirected graphs in sparse6 format from path. + + Returns a single MultiGraph.""" + return read_sparse6_list(path)[0] + +@open_file(0,mode='rt') +def read_sparse6_list(path): + """Read undirected graphs in sparse6 format from path. + + Returns a list of MultiGraphs, one for each line in file.""" + glist=[] + for line in path: + line = line.strip() + if not len(line): continue + glist.append(parse_sparse6(line)) + return glist + +def parse_sparse6(string): + """Read undirected graph in sparse6 format from string. + + Returns a MultiGraph. + """ + if string.startswith('>>sparse6<<'): + string = str[10:] + if not string.startswith(':'): + raise NetworkXError('Expected colon in sparse6') + n, data = graph6n(graph6data(string[1:])) + k = 1 + while 1<>dLen) & 1 # grab top remaining bit + + x = d & ((1<> (xLen - k)) # shift back the extra bits + dLen = xLen - k + yield b,x + + v = 0 + + G=nx.MultiGraph() + G.add_nodes_from(range(n)) + + for b,x in parseData(): + if b: v += 1 + if x >= n: break # padding with ones can cause overlarge number here + elif x > v: v = x + else: + G.add_edge(x,v) + + return G + +# helper functions + +def graph6data(str): + """Convert graph6 character sequence to 6-bit integers.""" + v = [ord(c)-63 for c in str] + if min(v) < 0 or max(v) > 63: + return None + return v + +def graph6n(data): + """Read initial one or four-unit value from graph6 sequence. + Return value, rest of seq.""" + if data[0] <= 62: + return data[0], data[1:] + return (data[1]<<12) + (data[2]<<6) + data[3], data[4:] diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_adjlist.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_adjlist.py new file mode 100644 index 0000000..9b3c0f2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_adjlist.py @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- +""" + Unit tests for adjlist. +""" +import io +from nose.tools import assert_equal, assert_raises, assert_not_equal +import os +import tempfile +import networkx as nx +from networkx.testing import * + + +class TestAdjlist(): + + def setUp(self): + self.G=nx.Graph(name="test") + e=[('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('a','f')] + self.G.add_edges_from(e) + self.G.add_node('g') + self.DG=nx.DiGraph(self.G) + self.XG=nx.MultiGraph() + self.XG.add_weighted_edges_from([(1,2,5),(1,2,5),(1,2,1),(3,3,42)]) + self. XDG=nx.MultiDiGraph(self.XG) + + def test_read_multiline_adjlist_1(self): + # Unit test for https://networkx.lanl.gov/trac/ticket/252 + s = b"""# comment line +1 2 +# comment line +2 +3 +""" + bytesIO = io.BytesIO(s) + G = nx.read_multiline_adjlist(bytesIO) + adj = {'1': {'3': {}, '2': {}}, '3': {'1': {}}, '2': {'1': {}}} + assert_equal(G.adj, adj) + + def test_unicode(self): + G = nx.Graph() + try: # Python 3.x + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + except ValueError: # Python 2.6+ + name1 = unichr(2344) + unichr(123) + unichr(6543) + name2 = unichr(5543) + unichr(1543) + unichr(324) + G.add_edge(name1, 'Radiohead', {name2: 3}) + fd, fname = tempfile.mkstemp() + nx.write_multiline_adjlist(G, fname) + H = nx.read_multiline_adjlist(fname) + assert_equal(G.adj, H.adj) + os.close(fd) + os.unlink(fname) + + def test_latin1_error(self): + G = nx.Graph() + try: # Python 3.x + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + except ValueError: # Python 2.6+ + name1 = unichr(2344) + unichr(123) + unichr(6543) + name2 = unichr(5543) + unichr(1543) + unichr(324) + G.add_edge(name1, 'Radiohead', {name2: 3}) + fd, fname = tempfile.mkstemp() + assert_raises(UnicodeEncodeError, + nx.write_multiline_adjlist, + G, fname, encoding = 'latin-1') + os.close(fd) + os.unlink(fname) + + def test_latin1(self): + G = nx.Graph() + try: # Python 3.x + blurb = chr(1245) # just to trigger the exception + name1 = 'Bj' + chr(246) + 'rk' + name2 = chr(220) + 'ber' + except ValueError: # Python 2.6+ + name1 = 'Bj' + unichr(246) + 'rk' + name2 = unichr(220) + 'ber' + G.add_edge(name1, 'Radiohead', {name2: 3}) + fd, fname = tempfile.mkstemp() + nx.write_multiline_adjlist(G, fname, encoding = 'latin-1') + H = nx.read_multiline_adjlist(fname, encoding = 'latin-1') + assert_equal(G.adj, H.adj) + os.close(fd) + os.unlink(fname) + + + + def test_adjlist_graph(self): + G=self.G + (fd,fname)=tempfile.mkstemp() + nx.write_adjlist(G,fname) + H=nx.read_adjlist(fname) + H2=nx.read_adjlist(fname) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + def test_adjlist_digraph(self): + G=self.DG + (fd,fname)=tempfile.mkstemp() + nx.write_adjlist(G,fname) + H=nx.read_adjlist(fname,create_using=nx.DiGraph()) + H2=nx.read_adjlist(fname,create_using=nx.DiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_adjlist_integers(self): + (fd,fname)=tempfile.mkstemp() + G=nx.convert_node_labels_to_integers(self.G) + nx.write_adjlist(G,fname) + H=nx.read_adjlist(fname,nodetype=int) + H2=nx.read_adjlist(fname,nodetype=int) + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_adjlist_digraph(self): + G=self.DG + (fd,fname)=tempfile.mkstemp() + nx.write_adjlist(G,fname) + H=nx.read_adjlist(fname,create_using=nx.DiGraph()) + H2=nx.read_adjlist(fname,create_using=nx.DiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_adjlist_multigraph(self): + G=self.XG + (fd,fname)=tempfile.mkstemp() + nx.write_adjlist(G,fname) + H=nx.read_adjlist(fname,nodetype=int, + create_using=nx.MultiGraph()) + H2=nx.read_adjlist(fname,nodetype=int, + create_using=nx.MultiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + def test_adjlist_multidigraph(self): + G=self.XDG + (fd,fname)=tempfile.mkstemp() + nx.write_adjlist(G,fname) + H=nx.read_adjlist(fname,nodetype=int, + create_using=nx.MultiDiGraph()) + H2=nx.read_adjlist(fname,nodetype=int, + create_using=nx.MultiDiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_adjlist_delimiter(self): + fh=io.BytesIO() + G = nx.path_graph(3) + nx.write_adjlist(G, fh, delimiter=':') + fh.seek(0) + H = nx.read_adjlist(fh, nodetype=int, delimiter=':') + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + + + + + + +class TestMultilineAdjlist(): + + def setUp(self): + self.G=nx.Graph(name="test") + e=[('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('a','f')] + self.G.add_edges_from(e) + self.G.add_node('g') + self.DG=nx.DiGraph(self.G) + self.DG.remove_edge('b','a') + self.DG.remove_edge('b','c') + self.XG=nx.MultiGraph() + self.XG.add_weighted_edges_from([(1,2,5),(1,2,5),(1,2,1),(3,3,42)]) + self. XDG=nx.MultiDiGraph(self.XG) + + + def test_multiline_adjlist_graph(self): + G=self.G + (fd,fname)=tempfile.mkstemp() + nx.write_multiline_adjlist(G,fname) + H=nx.read_multiline_adjlist(fname) + H2=nx.read_multiline_adjlist(fname) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_multiline_adjlist_digraph(self): + G=self.DG + (fd,fname)=tempfile.mkstemp() + nx.write_multiline_adjlist(G,fname) + H=nx.read_multiline_adjlist(fname,create_using=nx.DiGraph()) + H2=nx.read_multiline_adjlist(fname,create_using=nx.DiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_multiline_adjlist_integers(self): + (fd,fname)=tempfile.mkstemp() + G=nx.convert_node_labels_to_integers(self.G) + nx.write_multiline_adjlist(G,fname) + H=nx.read_multiline_adjlist(fname,nodetype=int) + H2=nx.read_multiline_adjlist(fname,nodetype=int) + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_multiline_adjlist_digraph(self): + G=self.DG + (fd,fname)=tempfile.mkstemp() + nx.write_multiline_adjlist(G,fname) + H=nx.read_multiline_adjlist(fname,create_using=nx.DiGraph()) + H2=nx.read_multiline_adjlist(fname,create_using=nx.DiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_edges_equal(H.edges(),G.edges()) + os.close(fd) + os.unlink(fname) + + + def test_multiline_adjlist_multigraph(self): + G=self.XG + (fd,fname)=tempfile.mkstemp() + nx.write_multiline_adjlist(G,fname) + H=nx.read_multiline_adjlist(fname,nodetype=int, + create_using=nx.MultiGraph()) + H2=nx.read_multiline_adjlist(fname,nodetype=int, + create_using=nx.MultiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + def test_multiline_adjlist_multidigraph(self): + G=self.XDG + (fd,fname)=tempfile.mkstemp() + nx.write_multiline_adjlist(G,fname) + H=nx.read_multiline_adjlist(fname,nodetype=int, + create_using=nx.MultiDiGraph()) + H2=nx.read_multiline_adjlist(fname,nodetype=int, + create_using=nx.MultiDiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + def test_multiline_adjlist_delimiter(self): + fh=io.BytesIO() + G = nx.path_graph(3) + nx.write_multiline_adjlist(G, fh, delimiter=':') + fh.seek(0) + H = nx.read_multiline_adjlist(fh, nodetype=int, delimiter=':') + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_edgelist.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_edgelist.py new file mode 100644 index 0000000..be70bba --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_edgelist.py @@ -0,0 +1,234 @@ +""" + Unit tests for edgelists. +""" +from nose.tools import assert_equal, assert_raises, assert_not_equal +import networkx as nx +import io +import tempfile +import os + +def assert_equal_edges(elist1,elist2): + if len(elist1[0]) == 2: + return assert_equal(sorted(sorted(e) for e in elist1), + sorted(sorted(e) for e in elist2)) + else: + return assert_equal(sorted((sorted((u, v)), d) for u, v, d in elist1), + sorted((sorted((u, v)), d) for u, v, d in elist2)) + +class TestEdgelist: + + def setUp(self): + self.G=nx.Graph(name="test") + e=[('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('a','f')] + self.G.add_edges_from(e) + self.G.add_node('g') + self.DG=nx.DiGraph(self.G) + self.XG=nx.MultiGraph() + self.XG.add_weighted_edges_from([(1,2,5),(1,2,5),(1,2,1),(3,3,42)]) + self. XDG=nx.MultiDiGraph(self.XG) + + + def test_read_edgelist_1(self): + s = b"""\ +# comment line +1 2 +# comment line +2 3 +""" + bytesIO = io.BytesIO(s) + G = nx.read_edgelist(bytesIO,nodetype=int) + assert_equal_edges(G.edges(),[(1,2),(2,3)]) + + def test_read_edgelist_2(self): + s = b"""\ +# comment line +1 2 2.0 +# comment line +2 3 3.0 +""" + bytesIO = io.BytesIO(s) + G = nx.read_edgelist(bytesIO,nodetype=int,data=False) + assert_equal_edges(G.edges(),[(1,2),(2,3)]) + + bytesIO = io.BytesIO(s) + G = nx.read_weighted_edgelist(bytesIO,nodetype=int) + assert_equal_edges(G.edges(data=True),[(1,2,{'weight':2.0}),(2,3,{'weight':3.0})]) + + def test_read_edgelist_3(self): + s = b"""\ +# comment line +1 2 {'weight':2.0} +# comment line +2 3 {'weight':3.0} +""" + bytesIO = io.BytesIO(s) + G = nx.read_edgelist(bytesIO,nodetype=int,data=False) + assert_equal_edges(G.edges(),[(1,2),(2,3)]) + + bytesIO = io.BytesIO(s) + G = nx.read_edgelist(bytesIO,nodetype=int,data=True) + assert_equal_edges(G.edges(data=True),[(1,2,{'weight':2.0}),(2,3,{'weight':3.0})]) + + def test_write_edgelist_1(self): + fh=io.BytesIO() + G=nx.Graph() + G.add_edges_from([(1,2),(2,3)]) + nx.write_edgelist(G,fh,data=False) + fh.seek(0) + assert_equal(fh.read(),b"1 2\n2 3\n") + + def test_write_edgelist_2(self): + fh=io.BytesIO() + G=nx.Graph() + G.add_edges_from([(1,2),(2,3)]) + nx.write_edgelist(G,fh,data=True) + fh.seek(0) + assert_equal(fh.read(),b"1 2 {}\n2 3 {}\n") + + def test_write_edgelist_3(self): + fh=io.BytesIO() + G=nx.Graph() + G.add_edge(1,2,weight=2.0) + G.add_edge(2,3,weight=3.0) + nx.write_edgelist(G,fh,data=True) + fh.seek(0) + assert_equal(fh.read(),b"1 2 {'weight': 2.0}\n2 3 {'weight': 3.0}\n") + + def test_write_edgelist_4(self): + fh=io.BytesIO() + G=nx.Graph() + G.add_edge(1,2,weight=2.0) + G.add_edge(2,3,weight=3.0) + nx.write_edgelist(G,fh,data=[('weight')]) + fh.seek(0) + assert_equal(fh.read(),b"1 2 2.0\n2 3 3.0\n") + + def test_unicode(self): + G = nx.Graph() + try: # Python 3.x + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + except ValueError: # Python 2.6+ + name1 = unichr(2344) + unichr(123) + unichr(6543) + name2 = unichr(5543) + unichr(1543) + unichr(324) + G.add_edge(name1, 'Radiohead', attr_dict={name2: 3}) + fd, fname = tempfile.mkstemp() + nx.write_edgelist(G, fname) + H = nx.read_edgelist(fname) + assert_equal(G.adj, H.adj) + os.close(fd) + os.unlink(fname) + + def test_latin1_error(self): + G = nx.Graph() + try: # Python 3.x + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + except ValueError: # Python 2.6+ + name1 = unichr(2344) + unichr(123) + unichr(6543) + name2 = unichr(5543) + unichr(1543) + unichr(324) + G.add_edge(name1, 'Radiohead', attr_dict={name2: 3}) + fd, fname = tempfile.mkstemp() + assert_raises(UnicodeEncodeError, + nx.write_edgelist, + G, fname, encoding = 'latin-1') + os.close(fd) + os.unlink(fname) + + def test_latin1(self): + G = nx.Graph() + try: # Python 3.x + blurb = chr(1245) # just to trigger the exception + name1 = 'Bj' + chr(246) + 'rk' + name2 = chr(220) + 'ber' + except ValueError: # Python 2.6+ + name1 = 'Bj' + unichr(246) + 'rk' + name2 = unichr(220) + 'ber' + G.add_edge(name1, 'Radiohead', attr_dict={name2: 3}) + fd, fname = tempfile.mkstemp() + nx.write_edgelist(G, fname, encoding = 'latin-1') + H = nx.read_edgelist(fname, encoding = 'latin-1') + assert_equal(G.adj, H.adj) + os.close(fd) + os.unlink(fname) + + + def test_edgelist_graph(self): + G=self.G + (fd,fname)=tempfile.mkstemp() + nx.write_edgelist(G,fname) + H=nx.read_edgelist(fname) + H2=nx.read_edgelist(fname) + assert_not_equal(H,H2) # they should be different graphs + G.remove_node('g') # isolated nodes are not written in edgelist + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + def test_edgelist_digraph(self): + G=self.DG + (fd,fname)=tempfile.mkstemp() + nx.write_edgelist(G,fname) + H=nx.read_edgelist(fname,create_using=nx.DiGraph()) + H2=nx.read_edgelist(fname,create_using=nx.DiGraph()) + assert_not_equal(H,H2) # they should be different graphs + G.remove_node('g') # isolated nodes are not written in edgelist + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_edgelist_integers(self): + G=nx.convert_node_labels_to_integers(self.G) + (fd,fname)=tempfile.mkstemp() + nx.write_edgelist(G,fname) + H=nx.read_edgelist(fname,nodetype=int) + # isolated nodes are not written in edgelist + G.remove_nodes_from(nx.isolates(G)) + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_edgelist_digraph(self): + G=self.DG + (fd,fname)=tempfile.mkstemp() + nx.write_edgelist(G,fname) + H=nx.read_edgelist(fname,create_using=nx.DiGraph()) + G.remove_node('g') # isolated nodes are not written in edgelist + H2=nx.read_edgelist(fname,create_using=nx.DiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + + def test_edgelist_multigraph(self): + G=self.XG + (fd,fname)=tempfile.mkstemp() + nx.write_edgelist(G,fname) + H=nx.read_edgelist(fname,nodetype=int,create_using=nx.MultiGraph()) + H2=nx.read_edgelist(fname,nodetype=int,create_using=nx.MultiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + def test_edgelist_multidigraph(self): + G=self.XDG + (fd,fname)=tempfile.mkstemp() + nx.write_edgelist(G,fname) + H=nx.read_edgelist(fname,nodetype=int,create_using=nx.MultiDiGraph()) + H2=nx.read_edgelist(fname,nodetype=int,create_using=nx.MultiDiGraph()) + assert_not_equal(H,H2) # they should be different graphs + assert_equal(sorted(H.nodes()),sorted(G.nodes())) + assert_equal(sorted(H.edges()),sorted(G.edges())) + os.close(fd) + os.unlink(fname) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gexf.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gexf.py new file mode 100644 index 0000000..bbd86ba --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gexf.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx as nx +import io + +class TestGEXF(object): + @classmethod + def setupClass(cls): + try: + import xml.etree.ElementTree + except ImportError: + raise SkipTest('xml.etree.ElementTree not available.') + + def setUp(self): + self.simple_directed_data=""" + + + + + + + + + + + +""" + self.simple_directed_graph=nx.DiGraph() + self.simple_directed_graph.add_node('0',label='Hello') + self.simple_directed_graph.add_node('1',label='World') + self.simple_directed_graph.add_edge('0','1',id='0') + + self.simple_directed_fh = \ + io.BytesIO(self.simple_directed_data.encode('UTF-8')) + + + self.attribute_data=""" + + + Gephi.org + A Web network + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + self.attribute_graph=nx.DiGraph() + self.attribute_graph.graph['node_default']={'frog':True} + self.attribute_graph.add_node('0', + label='Gephi', + url='http://gephi.org', + indegree=1) + self.attribute_graph.add_node('1', + label='Webatlas', + url='http://webatlas.fr', + indegree=2) + + self.attribute_graph.add_node('2', + label='RTGI', + url='http://rtgi.fr', + indegree=1) + + self.attribute_graph.add_node('3', + label='BarabasiLab', + url='http://barabasilab.com', + indegree=1, + frog=False) + self.attribute_graph.add_edge('0','1',id='0') + self.attribute_graph.add_edge('0','2',id='1') + self.attribute_graph.add_edge('1','0',id='2') + self.attribute_graph.add_edge('2','1',id='3') + self.attribute_graph.add_edge('0','3',id='4') + self.attribute_fh = io.BytesIO(self.attribute_data.encode('UTF-8')) + + self.simple_undirected_data=""" + + + + + + + + + + + +""" + self.simple_undirected_graph=nx.Graph() + self.simple_undirected_graph.add_node('0',label='Hello') + self.simple_undirected_graph.add_node('1',label='World') + self.simple_undirected_graph.add_edge('0','1',id='0') + + self.simple_undirected_fh = io.BytesIO(self.simple_undirected_data.encode('UTF-8')) + + + def test_read_simple_directed_graphml(self): + G=self.simple_directed_graph + H=nx.read_gexf(self.simple_directed_fh) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal(sorted(G.edges()),sorted(H.edges())) + assert_equal(sorted(G.edges(data=True)), + sorted(H.edges(data=True))) + self.simple_directed_fh.seek(0) + + def test_write_read_simple_directed_graphml(self): + G=self.simple_directed_graph + fh=io.BytesIO() + nx.write_gexf(G,fh) + fh.seek(0) + H=nx.read_gexf(fh) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal(sorted(G.edges()),sorted(H.edges())) + assert_equal(sorted(G.edges(data=True)), + sorted(H.edges(data=True))) + self.simple_directed_fh.seek(0) + + def test_read_simple_undirected_graphml(self): + G=self.simple_undirected_graph + H=nx.read_gexf(self.simple_undirected_fh) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal( + sorted(sorted(e) for e in G.edges()), + sorted(sorted(e) for e in H.edges())) + self.simple_undirected_fh.seek(0) + + def test_read_attribute_graphml(self): + G=self.attribute_graph + H=nx.read_gexf(self.attribute_fh) + assert_equal(sorted(G.nodes(True)),sorted(H.nodes(data=True))) + ge=sorted(G.edges(data=True)) + he=sorted(H.edges(data=True)) + for a,b in zip(ge,he): + assert_equal(a,b) + self.attribute_fh.seek(0) + + def test_directed_edge_in_undirected(self): + s=""" + + + + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_gexf,fh) + + def test_undirected_edge_in_directed(self): + s=""" + + + + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_gexf,fh) + + + def test_key_error(self): + s=""" + + + + + + + + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_gexf,fh) + + def test_relabel(self): + s=""" + + + + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + G=nx.read_gexf(fh,relabel=True) + assert_equal(sorted(G.nodes()),["Hello","Word"]) + + + def test_default_attribute(self): + G=nx.Graph() + G.add_node(1,label='1',color='green') + G.add_path([0,1,2,3]) + G.add_edge(1,2,foo=3) + G.graph['node_default']={'color':'yellow'} + G.graph['edge_default']={'foo':7} + fh = io.BytesIO() + nx.write_gexf(G,fh) + fh.seek(0) + H=nx.read_gexf(fh,node_type=int) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal( + sorted(sorted(e) for e in G.edges()), + sorted(sorted(e) for e in H.edges())) + assert_equal(G.graph,H.graph) + + def test_serialize_ints_to_strings(self): + G=nx.Graph() + G.add_node(1,id=7,label=77) + fh = io.BytesIO() + nx.write_gexf(G,fh) + fh.seek(0) + H=nx.read_gexf(fh,node_type=int) + assert_equal(H.nodes(),[7]) + assert_equal(H.node[7]['label'],'77') + + def test_write_with_node_attributes(self): + # Addresses #673. + G = nx.path_graph(4) + for i in range(4): + G.node[i]['id'] = i + G.node[i]['label'] = i + G.node[i]['pid'] = i + + expected = """ + + + + + + + + + + + + + +""" + obtained = '\n'.join(nx.generate_gexf(G)) + assert_equal( expected, obtained ) + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gml.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gml.py new file mode 100644 index 0000000..ed63bd2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gml.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +import io +from nose.tools import * +from nose import SkipTest +import networkx + +class TestGraph(object): + @classmethod + def setupClass(cls): + global pyparsing + try: + import pyparsing + except ImportError: + try: + import matplotlib.pyparsing as pyparsing + except: + raise SkipTest('gml test: pyparsing not available.') + + def setUp(self): + self.simple_data="""Creator me +graph [ + comment "This is a sample graph" + directed 1 + IsPlanar 1 + pos [ x 0 y 1 ] + node [ + id 1 + label "Node 1" + pos [ x 1 y 1 ] + ] + node [ + id 2 + pos [ x 1 y 2 ] + label "Node 2" + ] + node [ + id 3 + label "Node 3" + pos [ x 1 y 3 ] + ] + edge [ + source 1 + target 2 + label "Edge from node 1 to node 2" + color [line "blue" thickness 3] + + ] + edge [ + source 2 + target 3 + label "Edge from node 2 to node 3" + ] + edge [ + source 3 + target 1 label + "Edge from node 3 to node 1" + ] +] +""" + def test_parse_gml(self): + G=networkx.parse_gml(self.simple_data,relabel=True) + assert_equals(sorted(G.nodes()),\ + ['Node 1', 'Node 2', 'Node 3']) + assert_equals( [e for e in sorted(G.edges())],\ + [('Node 1', 'Node 2'), + ('Node 2', 'Node 3'), + ('Node 3', 'Node 1')]) + + assert_equals( [e for e in sorted(G.edges(data=True))],\ + [('Node 1', 'Node 2', + {'color': {'line': 'blue', 'thickness': 3}, + 'label': 'Edge from node 1 to node 2'}), + ('Node 2', 'Node 3', + {'label': 'Edge from node 2 to node 3'}), + ('Node 3', 'Node 1', + {'label': 'Edge from node 3 to node 1'})]) + + + def test_read_gml(self): + import os,tempfile + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + fh.write(self.simple_data) + fh.close() + Gin=networkx.read_gml(fname,relabel=True) + G=networkx.parse_gml(self.simple_data,relabel=True) + assert_equals( sorted(G.nodes(data=True)), sorted(Gin.nodes(data=True))) + assert_equals( sorted(G.edges(data=True)), sorted(Gin.edges(data=True))) + os.close(fd) + os.unlink(fname) + + def test_relabel_duplicate(self): + data=""" +graph +[ + label "" + directed 1 + node + [ + id 0 + label "same" + ] + node + [ + id 1 + label "same" + ] +] +""" + fh = io.BytesIO(data.encode('UTF-8')) + fh.seek(0) + assert_raises(networkx.NetworkXError,networkx.read_gml,fh,relabel=True) + + def test_bool(self): + G=networkx.Graph() + G.add_node(1,on=True) + G.add_edge(1,2,on=False) + data = '\n'.join(list(networkx.generate_gml(G))) + answer ="""graph [ + node [ + id 0 + label 1 + on 1 + ] + node [ + id 1 + label 2 + ] + edge [ + source 0 + target 1 + on 0 + ] +]""" + assert_equal(data,answer) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gpickle.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gpickle.py new file mode 100644 index 0000000..429f753 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_gpickle.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from nose.tools import assert_equal +import networkx as nx +import os,tempfile + +class TestGpickle(object): + def setUp(self): + G=nx.Graph(name="test") + e=[('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('a','f')] + G.add_edges_from(e,width=10) + G.add_node('g',color='green') + G.graph['number']=1 + self.G=G + + def test_gpickle(self): + G=self.G + (fd,fname)=tempfile.mkstemp() + nx.write_gpickle(G,fname); + Gin=nx.read_gpickle(fname); + assert_equal(sorted(G.nodes(data=True)), + sorted(Gin.nodes(data=True))) + assert_equal(sorted(G.edges(data=True)), + sorted(Gin.edges(data=True))) + assert_equal(G.graph,Gin.graph) + os.close(fd) + os.unlink(fname) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_graphml.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_graphml.py new file mode 100644 index 0000000..c21c89b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_graphml.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python +from nose.tools import * +from nose import SkipTest +import networkx as nx +import io +import tempfile +import os + +class TestGraph(object): + @classmethod + def setupClass(cls): + try: + import xml.etree.ElementTree + except ImportError: + raise SkipTest('xml.etree.ElementTree not available.') + + def setUp(self): + self.simple_directed_data=""" + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + self.simple_directed_graph=nx.DiGraph() + self.simple_directed_graph.add_node('n10') + self.simple_directed_graph.add_edge('n0','n2',id='foo') + self.simple_directed_graph.add_edges_from([('n1','n2'), + ('n2','n3'), + ('n3','n5'), + ('n3','n4'), + ('n4','n6'), + ('n6','n5'), + ('n5','n7'), + ('n6','n8'), + ('n8','n7'), + ('n8','n9'), + ]) + + self.simple_directed_fh = \ + io.BytesIO(self.simple_directed_data.encode('UTF-8')) + + + self.attribute_data=""" + + + yellow + + + + + green + + + + blue + + + red + + + + turquoise + + + 1.0 + + + 1.0 + + + 2.0 + + + + + + 1.1 + + + +""" + self.attribute_graph=nx.DiGraph(id='G') + self.attribute_graph.graph['node_default']={'color':'yellow'} + self.attribute_graph.add_node('n0',color='green') + self.attribute_graph.add_node('n2',color='blue') + self.attribute_graph.add_node('n3',color='red') + self.attribute_graph.add_node('n4') + self.attribute_graph.add_node('n5',color='turquoise') + self.attribute_graph.add_edge('n0','n2',id='e0',weight=1.0) + self.attribute_graph.add_edge('n0','n1',id='e1',weight=1.0) + self.attribute_graph.add_edge('n1','n3',id='e2',weight=2.0) + self.attribute_graph.add_edge('n3','n2',id='e3') + self.attribute_graph.add_edge('n2','n4',id='e4') + self.attribute_graph.add_edge('n3','n5',id='e5') + self.attribute_graph.add_edge('n5','n4',id='e6',weight=1.1) + self.attribute_fh = io.BytesIO(self.attribute_data.encode('UTF-8')) + + self.simple_undirected_data=""" + + + + + + + + + + +""" +# + self.simple_undirected_graph=nx.Graph() + self.simple_undirected_graph.add_node('n10') + self.simple_undirected_graph.add_edge('n0','n2',id='foo') + self.simple_undirected_graph.add_edges_from([('n1','n2'), + ('n2','n3'), + ]) + + self.simple_undirected_fh = io.BytesIO(self.simple_undirected_data.encode('UTF-8')) + + + def test_read_simple_directed_graphml(self): + G=self.simple_directed_graph + H=nx.read_graphml(self.simple_directed_fh) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal(sorted(G.edges()),sorted(H.edges())) + assert_equal(sorted(G.edges(data=True)), + sorted(H.edges(data=True))) + self.simple_directed_fh.seek(0) + + I=nx.parse_graphml(self.simple_directed_data) + assert_equal(sorted(G.nodes()),sorted(I.nodes())) + assert_equal(sorted(G.edges()),sorted(I.edges())) + assert_equal(sorted(G.edges(data=True)), + sorted(I.edges(data=True))) + + def test_write_read_simple_directed_graphml(self): + G=self.simple_directed_graph + fh=io.BytesIO() + nx.write_graphml(G,fh) + fh.seek(0) + H=nx.read_graphml(fh) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal(sorted(G.edges()),sorted(H.edges())) + assert_equal(sorted(G.edges(data=True)), + sorted(H.edges(data=True))) + self.simple_directed_fh.seek(0) + + def test_read_simple_undirected_graphml(self): + G=self.simple_undirected_graph + H=nx.read_graphml(self.simple_undirected_fh) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal( + sorted(sorted(e) for e in G.edges()), + sorted(sorted(e) for e in H.edges())) + self.simple_undirected_fh.seek(0) + + I=nx.parse_graphml(self.simple_undirected_data) + assert_equal(sorted(G.nodes()),sorted(I.nodes())) + assert_equal( + sorted(sorted(e) for e in G.edges()), + sorted(sorted(e) for e in I.edges())) + + def test_read_attribute_graphml(self): + G=self.attribute_graph + H=nx.read_graphml(self.attribute_fh) + assert_equal(sorted(G.nodes(True)),sorted(H.nodes(data=True))) + ge=sorted(G.edges(data=True)) + he=sorted(H.edges(data=True)) + for a,b in zip(ge,he): + assert_equal(a,b) + self.attribute_fh.seek(0) + + I=nx.parse_graphml(self.attribute_data) + assert_equal(sorted(G.nodes(True)),sorted(I.nodes(data=True))) + ge=sorted(G.edges(data=True)) + he=sorted(I.edges(data=True)) + for a,b in zip(ge,he): + assert_equal(a,b) + + def test_directed_edge_in_undirected(self): + s=""" + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_graphml,fh) + assert_raises(nx.NetworkXError,nx.parse_graphml,s) + + def test_undirected_edge_in_directed(self): + s=""" + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_graphml,fh) + assert_raises(nx.NetworkXError,nx.parse_graphml,s) + + def test_key_error(self): + s=""" + + + yellow + + + + + green + + + + blue + + + 1.0 + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_graphml,fh) + assert_raises(nx.NetworkXError,nx.parse_graphml,s) + + def test_hyperedge_error(self): + s=""" + + + yellow + + + + + green + + + + blue + + + + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + assert_raises(nx.NetworkXError,nx.read_graphml,fh) + assert_raises(nx.NetworkXError,nx.parse_graphml,s) + + # remove test until we get the "name" issue sorted + # https://networkx.lanl.gov/trac/ticket/544 + def test_default_attribute(self): + G=nx.Graph() + G.add_node(1,label=1,color='green') + G.add_path([0,1,2,3]) + G.add_edge(1,2,weight=3) + G.graph['node_default']={'color':'yellow'} + G.graph['edge_default']={'weight':7} + fh = io.BytesIO() + nx.write_graphml(G,fh) + fh.seek(0) + H=nx.read_graphml(fh,node_type=int) + assert_equal(sorted(G.nodes()),sorted(H.nodes())) + assert_equal( + sorted(sorted(e) for e in G.edges()), + sorted(sorted(e) for e in H.edges())) + assert_equal(G.graph,H.graph) + + def test_multigraph_keys(self): + # test that multigraphs use edge id attributes as key + pass + + def test_multigraph_to_graph(self): + # test converting multigraph to graph if no parallel edges are found + pass + + def test_yfiles_extension(self): + data=""" + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + + +""" + fh = io.BytesIO(data.encode('UTF-8')) + G=nx.read_graphml(fh) + assert_equal(G.edges(),[('n0','n1')]) + assert_equal(G['n0']['n1']['id'],'e0') + assert_equal(G.node['n0']['label'],'1') + assert_equal(G.node['n1']['label'],'2') + + H=nx.parse_graphml(data) + assert_equal(H.edges(),[('n0','n1')]) + assert_equal(H['n0']['n1']['id'],'e0') + assert_equal(H.node['n0']['label'],'1') + assert_equal(H.node['n1']['label'],'2') + + def test_unicode(self): + G = nx.Graph() + try: # Python 3.x + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + node_type=str + except ValueError: # Python 2.6+ + name1 = unichr(2344) + unichr(123) + unichr(6543) + name2 = unichr(5543) + unichr(1543) + unichr(324) + node_type=unicode + G.add_edge(name1, 'Radiohead', attr_dict={'foo': name2}) + fd, fname = tempfile.mkstemp() + nx.write_graphml(G, fname) + H = nx.read_graphml(fname,node_type=node_type) + assert_equal(G.adj, H.adj) + os.close(fd) + os.unlink(fname) + + + def test_bool(self): + s=""" + + + false + + + + True + + + + False + + + true + + + false + + + + + +""" + fh = io.BytesIO(s.encode('UTF-8')) + G=nx.read_graphml(fh) + assert_equal(G.node['n0']['test'],True) + assert_equal(G.node['n2']['test'],False) + + H=nx.parse_graphml(s) + assert_equal(H.node['n0']['test'],True) + assert_equal(H.node['n2']['test'],False) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_leda.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_leda.py new file mode 100644 index 0000000..1c3614e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_leda.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +import os,tempfile + +class TestLEDA(object): + + def test_parse_leda(self): + data="""#header section \nLEDA.GRAPH \nstring\nint\n-1\n#nodes section\n5 \n|{v1}| \n|{v2}| \n|{v3}| \n|{v4}| \n|{v5}| \n\n#edges section\n7 \n1 2 0 |{4}| \n1 3 0 |{3}| \n2 3 0 |{2}| \n3 4 0 |{3}| \n3 5 0 |{7}| \n4 5 0 |{6}| \n5 1 0 |{foo}|""" + G=nx.parse_leda(data) + G=nx.parse_leda(data.split('\n')) + assert_equal(sorted(G.nodes()), + ['v1', 'v2', 'v3', 'v4', 'v5']) + assert_equal([e for e in sorted(G.edges(data=True))], + [('v1', 'v2', {'label': '4'}), + ('v1', 'v3', {'label': '3'}), + ('v2', 'v3', {'label': '2'}), + ('v3', 'v4', {'label': '3'}), + ('v3', 'v5', {'label': '7'}), + ('v4', 'v5', {'label': '6'}), + ('v5', 'v1', {'label': 'foo'})]) + + + def test_read_LEDA(self): + data="""#header section \nLEDA.GRAPH \nstring\nint\n-1\n#nodes section\n5 \n|{v1}| \n|{v2}| \n|{v3}| \n|{v4}| \n|{v5}| \n\n#edges section\n7 \n1 2 0 |{4}| \n1 3 0 |{3}| \n2 3 0 |{2}| \n3 4 0 |{3}| \n3 5 0 |{7}| \n4 5 0 |{6}| \n5 1 0 |{foo}|""" + G=nx.parse_leda(data) + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + b=fh.write(data) + fh.close() + Gin=nx.read_leda(fname) + assert_equal(sorted(G.nodes()),sorted(Gin.nodes())) + assert_equal(sorted(G.edges()),sorted(Gin.edges())) + os.close(fd) + os.unlink(fname) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_p2g.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_p2g.py new file mode 100644 index 0000000..6bd7d4e --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_p2g.py @@ -0,0 +1,64 @@ +from nose.tools import assert_equal, assert_raises, assert_not_equal +import networkx as nx +import io +import tempfile +import os +from networkx.readwrite.p2g import * +from networkx.testing import * + + +class TestP2G: + + def setUp(self): + self.G=nx.Graph(name="test") + e=[('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('a','f')] + self.G.add_edges_from(e) + self.G.add_node('g') + self.DG=nx.DiGraph(self.G) + + def test_read_p2g(self): + s = b"""\ +name +3 4 +a +1 2 +b + +c +0 2 +""" + bytesIO = io.BytesIO(s) + G = read_p2g(bytesIO) + assert_equal(G.name,'name') + assert_equal(sorted(G),['a','b','c']) + edges = [(str(u),str(v)) for u,v in G.edges()] + assert_edges_equal(G.edges(),[('a','c'),('a','b'),('c','a'),('c','c')]) + + def test_write_p2g(self): + s=b"""foo +3 2 +1 +1 +2 +2 +3 + +""" + fh=io.BytesIO() + G=nx.DiGraph() + G.name='foo' + G.add_edges_from([(1,2),(2,3)]) + write_p2g(G,fh) + fh.seek(0) + r=fh.read() + assert_equal(r,s) + + def test_write_read_p2g(self): + fh=io.BytesIO() + G=nx.DiGraph() + G.name='foo' + G.add_edges_from([('a','b'),('b','c')]) + write_p2g(G,fh) + fh.seek(0) + H=read_p2g(fh) + assert_edges_equal(G.edges(),H.edges()) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_pajek.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_pajek.py new file mode 100644 index 0000000..a2e43d0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_pajek.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +""" +Pajek tests +""" +from nose.tools import assert_equal +from networkx import * +import os,tempfile +from io import open +from networkx.testing import * + +class TestPajek(object): + def setUp(self): + self.data="""*network Tralala\n*vertices 4\n 1 "A1" 0.0938 0.0896 ellipse x_fact 1 y_fact 1\n 2 "Bb" 0.8188 0.2458 ellipse x_fact 1 y_fact 1\n 3 "C" 0.3688 0.7792 ellipse x_fact 1\n 4 "D2" 0.9583 0.8563 ellipse x_fact 1\n*arcs\n1 1 1 h2 0 w 3 c Blue s 3 a1 -130 k1 0.6 a2 -130 k2 0.6 ap 0.5 l "Bezier loop" lc BlueViolet fos 20 lr 58 lp 0.3 la 360\n2 1 1 h2 0 a1 120 k1 1.3 a2 -120 k2 0.3 ap 25 l "Bezier arc" lphi 270 la 180 lr 19 lp 0.5\n1 2 1 h2 0 a1 40 k1 2.8 a2 30 k2 0.8 ap 25 l "Bezier arc" lphi 90 la 0 lp 0.65\n4 2 -1 h2 0 w 1 k1 -2 k2 250 ap 25 l "Circular arc" c Red lc OrangeRed\n3 4 1 p Dashed h2 0 w 2 c OliveGreen ap 25 l "Straight arc" lc PineGreen\n1 3 1 p Dashed h2 0 w 5 k1 -1 k2 -20 ap 25 l "Oval arc" c Brown lc Black\n3 3 -1 h1 6 w 1 h2 12 k1 -2 k2 -15 ap 0.5 l "Circular loop" c Red lc OrangeRed lphi 270 la 180""" + self.G=nx.MultiDiGraph() + self.G.add_nodes_from(['A1', 'Bb', 'C', 'D2']) + self.G.add_edges_from([('A1', 'A1'), ('A1', 'Bb'), ('A1', 'C'), + ('Bb', 'A1'),('C', 'C'), ('C', 'D2'), + ('D2', 'Bb')]) + + self.G.graph['name']='Tralala' + (self.fd,self.fname)=tempfile.mkstemp() + fh=open(self.fname,'wb') + fh.write(self.data.encode('UTF-8')) + fh.close() + + def tearDown(self): + os.close(self.fd) + os.unlink(self.fname) + + def test_parse_pajek_simple(self): + # Example without node positions or shape + data="""*Vertices 2\n1 "1"\n2 "2"\n*Edges\n1 2\n2 1""" + G=parse_pajek(data) + assert_equal(sorted(G.nodes()), ['1', '2']) + assert_edges_equal(G.edges(), [('1', '2'), ('1', '2')]) + + def test_parse_pajek(self): + G=parse_pajek(self.data) + assert_equal(sorted(G.nodes()), ['A1', 'Bb', 'C', 'D2']) + assert_edges_equal(G.edges(), [('A1', 'A1'), ('A1', 'Bb'), + ('A1', 'C'), ('Bb', 'A1'), + ('C', 'C'), ('C', 'D2'), ('D2', 'Bb')]) + + def test_read_pajek(self): + G=parse_pajek(self.data) + Gin=read_pajek(self.fname) + assert_equal(sorted(G.nodes()), sorted(Gin.nodes())) + assert_edges_equal(G.edges(), Gin.edges()) + assert_equal(self.G.graph,Gin.graph) + for n in G.node: + assert_equal(G.node[n],Gin.node[n]) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_shp.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_shp.py new file mode 100644 index 0000000..91c392c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_shp.py @@ -0,0 +1,140 @@ +"""Unit tests for shp. +""" + +import os +import tempfile +from nose import SkipTest +from nose.tools import assert_equal + +import networkx as nx + + +class TestShp(object): + @classmethod + def setupClass(cls): + global ogr + try: + from osgeo import ogr + except ImportError: + raise SkipTest('ogr not available.') + + def deletetmp(self, drv, *paths): + for p in paths: + if os.path.exists(p): + drv.DeleteDataSource(p) + + def setUp(self): + + def createlayer(driver): + lyr = shp.CreateLayer("edges", None, ogr.wkbLineString) + namedef = ogr.FieldDefn("Name", ogr.OFTString) + namedef.SetWidth(32) + lyr.CreateField(namedef) + return lyr + + drv = ogr.GetDriverByName("ESRI Shapefile") + + testdir = os.path.join(tempfile.gettempdir(), 'shpdir') + shppath = os.path.join(tempfile.gettempdir(), 'tmpshp.shp') + + self.deletetmp(drv, testdir, shppath) + os.mkdir(testdir) + + shp = drv.CreateDataSource(shppath) + lyr = createlayer(shp) + self.names = ['a', 'b', 'c'] # edgenames + self.paths = ( [(1.0, 1.0), (2.0, 2.0)], + [(2.0, 2.0), (3.0, 3.0)], + [(0.9, 0.9), (4.0, 2.0)] + ) + for path, name in zip(self.paths, self.names): + feat = ogr.Feature(lyr.GetLayerDefn()) + g = ogr.Geometry(ogr.wkbLineString) + map(lambda xy: g.AddPoint_2D(*xy), path) + feat.SetGeometry(g) + feat.SetField("Name", name) + lyr.CreateFeature(feat) + self.shppath = shppath + self.testdir = testdir + self.drv = drv + + def testload(self): + expected = nx.DiGraph() + map(expected.add_path, self.paths) + G = nx.read_shp(self.shppath) + assert_equal(sorted(expected.node), sorted(G.node)) + assert_equal(sorted(expected.edges()), sorted(G.edges())) + names = [G.get_edge_data(s, e)['Name'] for s, e in G.edges()] + assert_equal(self.names, sorted(names)) + + def checkgeom(self, lyr, expected): + feature = lyr.GetNextFeature() + actualwkt = [] + while feature: + actualwkt.append(feature.GetGeometryRef().ExportToWkt()) + feature = lyr.GetNextFeature() + assert_equal(sorted(expected), sorted(actualwkt)) + + def test_geometryexport(self): + expectedpoints = ( + "POINT (1 1)", + "POINT (2 2)", + "POINT (3 3)", + "POINT (0.9 0.9)", + "POINT (4 2)" + ) + expectedlines = ( + "LINESTRING (1 1,2 2)", + "LINESTRING (2 2,3 3)", + "LINESTRING (0.9 0.9,4 2)" + ) + tpath = os.path.join(tempfile.gettempdir(), 'shpdir') + G = nx.read_shp(self.shppath) + nx.write_shp(G, tpath) + shpdir = ogr.Open(tpath) + self.checkgeom(shpdir.GetLayerByName("nodes"), expectedpoints) + self.checkgeom(shpdir.GetLayerByName("edges"), expectedlines) + + def test_attributeexport(self): + def testattributes(lyr, graph): + feature = lyr.GetNextFeature() + while feature: + coords = [] + ref = feature.GetGeometryRef() + for i in xrange(ref.GetPointCount()): + coords.append(ref.GetPoint_2D(i)) + name = feature.GetFieldAsString('Name') + assert_equal(graph.get_edge_data(*coords)['Name'], name) + feature = lyr.GetNextFeature() + + tpath = os.path.join(tempfile.gettempdir(), 'shpdir') + + G = nx.read_shp(self.shppath) + nx.write_shp(G, tpath) + shpdir = ogr.Open(tpath) + edges = shpdir.GetLayerByName("edges") + testattributes(edges, G) + + def test_wkt_export(self): + G = nx.DiGraph() + tpath = os.path.join(tempfile.gettempdir(), 'shpdir') + points = ( + "POINT (0.9 0.9)", + "POINT (4 2)" + ) + line = ( + "LINESTRING (0.9 0.9,4 2)", + ) + G.add_node(1, Wkt=points[0]) + G.add_node(2, Wkt=points[1]) + G.add_edge(1, 2, Wkt=line[0]) + try: + nx.write_shp(G, tpath) + except Exception as e: + assert False, e + shpdir = ogr.Open(tpath) + self.checkgeom(shpdir.GetLayerByName("nodes"), points) + self.checkgeom(shpdir.GetLayerByName("edges"), line) + + def tearDown(self): + self.deletetmp(self.drv, self.testdir, self.shppath) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_sparsegraph6.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_sparsegraph6.py new file mode 100644 index 0000000..fea9161 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_sparsegraph6.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +from nose.tools import * +import networkx as nx +import os,tempfile + +class TestGraph6(object): + + def test_parse_graph6(self): + data="""DF{""" + G=nx.parse_graph6(data) + assert_equal(sorted(G.nodes()),[0, 1, 2, 3, 4]) + assert_equal([e for e in sorted(G.edges())], + [(0, 3), (0, 4), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]) + + def test_read_graph6(self): + data="""DF{""" + G=nx.parse_graph6(data) + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + b=fh.write(data) + fh.close() + Gin=nx.read_graph6(fname) + assert_equal(sorted(G.nodes()),sorted(Gin.nodes())) + assert_equal(sorted(G.edges()),sorted(Gin.edges())) + os.close(fd) + os.unlink(fname) + + def test_read_many_graph6(self): + # Read many graphs into list + data="""DF{\nD`{\nDqK\nD~{\n""" + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + b=fh.write(data) + fh.close() + glist=nx.read_graph6_list(fname) + assert_equal(len(glist),4) + for G in glist: + assert_equal(sorted(G.nodes()),[0, 1, 2, 3, 4]) + os.close(fd) + os.unlink(fname) + + +class TestSparseGraph6(object): + + def test_parse_sparse6(self): + data=""":Q___eDcdFcDeFcE`GaJ`IaHbKNbLM""" + G=nx.parse_sparse6(data) + assert_equal(sorted(G.nodes()), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17]) + assert_equal([e for e in sorted(G.edges())], + [(0, 1), (0, 2), (0, 3), (1, 12), (1, 14), (2, 13), + (2, 15), (3, 16), (3, 17), (4, 7), (4, 9), (4, 11), + (5, 6), (5, 8), (5, 9), (6, 10), (6, 11), (7, 8), + (7, 10), (8, 12), (9, 15), (10, 14), (11, 13), + (12, 16), (13, 17), (14, 17), (15, 16)]) + + + def test_read_sparse6(self): + data=""":Q___eDcdFcDeFcE`GaJ`IaHbKNbLM""" + G=nx.parse_sparse6(data) + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + b=fh.write(data) + fh.close() + Gin=nx.read_sparse6(fname) + assert_equal(sorted(G.nodes()),sorted(Gin.nodes())) + assert_equal(sorted(G.edges()),sorted(Gin.edges())) + os.close(fd) + os.unlink(fname) + + def test_read_many_graph6(self): + # Read many graphs into list + data=""":Q___eDcdFcDeFcE`GaJ`IaHbKNbLM\n:Q___dCfDEdcEgcbEGbFIaJ`JaHN`IM""" + (fd,fname)=tempfile.mkstemp() + fh=open(fname,'w') + b=fh.write(data) + fh.close() + glist=nx.read_sparse6_list(fname) + assert_equal(len(glist),2) + for G in glist: + assert_equal(sorted(G.nodes()), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17]) + os.close(fd) + os.unlink(fname) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_yaml.py b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_yaml.py new file mode 100644 index 0000000..ca0ff56 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/readwrite/tests/test_yaml.py @@ -0,0 +1,53 @@ +""" + Unit tests for yaml. +""" + +import os,tempfile +from nose import SkipTest +from nose.tools import assert_equal + +import networkx as nx + +class TestYaml(object): + @classmethod + def setupClass(cls): + global yaml + try: + import yaml + except ImportError: + raise SkipTest('yaml not available.') + + def setUp(self): + self.build_graphs() + + def build_graphs(self): + self.G = nx.Graph(name="test") + e = [('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('a','f')] + self.G.add_edges_from(e) + self.G.add_node('g') + + self.DG = nx.DiGraph(self.G) + + self.MG = nx.MultiGraph() + self.MG.add_weighted_edges_from([(1,2,5),(1,2,5),(1,2,1),(3,3,42)]) + + def assert_equal(self, G, data=False): + (fd, fname) = tempfile.mkstemp() + nx.write_yaml(G, fname) + Gin = nx.read_yaml(fname); + + assert_equal(sorted(G.nodes()),sorted(Gin.nodes())) + assert_equal(G.edges(data=data),Gin.edges(data=data)) + + os.close(fd) + os.unlink(fname) + + def testUndirected(self): + self.assert_equal(self.G, False) + + def testDirected(self): + self.assert_equal(self.DG, False) + + def testMultiGraph(self): + self.assert_equal(self.MG, True) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/relabel.py b/lib/python2.7/site-packages/setoolsgui/networkx/relabel.py new file mode 100644 index 0000000..4ff1196 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/relabel.py @@ -0,0 +1,205 @@ +# Copyright (C) 2006-2013 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx +__author__ = """\n""".join(['Aric Hagberg ', + 'Pieter Swart (swart@lanl.gov)', + 'Dan Schult (dschult@colgate.edu)']) +__all__ = ['convert_node_labels_to_integers', 'relabel_nodes'] + +def relabel_nodes(G, mapping, copy=True): + """Relabel the nodes of the graph G. + + Parameters + ---------- + G : graph + A NetworkX graph + + mapping : dictionary + A dictionary with the old labels as keys and new labels as values. + A partial mapping is allowed. + + copy : bool (optional, default=True) + If True return a copy, or if False relabel the nodes in place. + + Examples + -------- + >>> G=nx.path_graph(3) # nodes 0-1-2 + >>> mapping={0:'a',1:'b',2:'c'} + >>> H=nx.relabel_nodes(G,mapping) + >>> print(sorted(H.nodes())) + ['a', 'b', 'c'] + + >>> G=nx.path_graph(26) # nodes 0..25 + >>> mapping=dict(zip(G.nodes(),"abcdefghijklmnopqrstuvwxyz")) + >>> H=nx.relabel_nodes(G,mapping) # nodes a..z + >>> mapping=dict(zip(G.nodes(),range(1,27))) + >>> G1=nx.relabel_nodes(G,mapping) # nodes 1..26 + + Partial in-place mapping: + + >>> G=nx.path_graph(3) # nodes 0-1-2 + >>> mapping={0:'a',1:'b'} # 0->'a' and 1->'b' + >>> G=nx.relabel_nodes(G,mapping, copy=False) + + print(G.nodes()) + [2, 'b', 'a'] + + Mapping as function: + + >>> G=nx.path_graph(3) + >>> def mapping(x): + ... return x**2 + >>> H=nx.relabel_nodes(G,mapping) + >>> print(H.nodes()) + [0, 1, 4] + + Notes + ----- + Only the nodes specified in the mapping will be relabeled. + + The keyword setting copy=False modifies the graph in place. + This is not always possible if the mapping is circular. + In that case use copy=True. + + See Also + -------- + convert_node_labels_to_integers + """ + # you can pass a function f(old_label)->new_label + # but we'll just make a dictionary here regardless + if not hasattr(mapping,"__getitem__"): + m = dict((n,mapping(n)) for n in G) + else: + m=mapping + if copy: + return _relabel_copy(G,m) + else: + return _relabel_inplace(G,m) + + +def _relabel_inplace(G, mapping): + old_labels=set(mapping.keys()) + new_labels=set(mapping.values()) + if len(old_labels & new_labels) > 0: + # labels sets overlap + # can we topological sort and still do the relabeling? + D=nx.DiGraph(list(mapping.items())) + D.remove_edges_from(D.selfloop_edges()) + try: + nodes=nx.topological_sort(D) + except nx.NetworkXUnfeasible: + raise nx.NetworkXUnfeasible('The node label sets are overlapping ' + 'and no ordering can resolve the ' + 'mapping. Use copy=True.') + nodes.reverse() # reverse topological order + else: + # non-overlapping label sets + nodes=old_labels + + multigraph = G.is_multigraph() + directed = G.is_directed() + + for old in nodes: + try: + new=mapping[old] + except KeyError: + continue + try: + G.add_node(new,attr_dict=G.node[old]) + except KeyError: + raise KeyError("Node %s is not in the graph"%old) + if multigraph: + new_edges=[(new,old == target and new or target,key,data) + for (_,target,key,data) + in G.edges(old,data=True,keys=True)] + if directed: + new_edges+=[(old == source and new or source,new,key,data) + for (source,_,key,data) + in G.in_edges(old,data=True,keys=True)] + else: + new_edges=[(new,old == target and new or target,data) + for (_,target,data) in G.edges(old,data=True)] + if directed: + new_edges+=[(old == source and new or source,new,data) + for (source,_,data) in G.in_edges(old,data=True)] + G.remove_node(old) + G.add_edges_from(new_edges) + return G + +def _relabel_copy(G, mapping): + H=G.__class__() + H.name="(%s)" % G.name + if G.is_multigraph(): + H.add_edges_from( (mapping.get(n1,n1),mapping.get(n2,n2),k,d.copy()) + for (n1,n2,k,d) in G.edges_iter(keys=True,data=True)) + else: + H.add_edges_from( (mapping.get(n1,n1),mapping.get(n2,n2),d.copy()) + for (n1,n2,d) in G.edges_iter(data=True)) + + H.add_nodes_from(mapping.get(n,n) for n in G) + H.node.update(dict((mapping.get(n,n),d.copy()) for n,d in G.node.items())) + H.graph.update(G.graph.copy()) + + return H + + +def convert_node_labels_to_integers(G, first_label=0, ordering="default", + label_attribute=None): + """Return a copy of the graph G with the nodes relabeled with integers. + + Parameters + ---------- + G : graph + A NetworkX graph + + first_label : int, optional (default=0) + An integer specifying the offset in numbering nodes. + The n new integer labels are numbered first_label, ..., n-1+first_label. + + ordering : string + "default" : inherit node ordering from G.nodes() + "sorted" : inherit node ordering from sorted(G.nodes()) + "increasing degree" : nodes are sorted by increasing degree + "decreasing degree" : nodes are sorted by decreasing degree + + label_attribute : string, optional (default=None) + Name of node attribute to store old label. If None no attribute + is created. + + Notes + ----- + Node and edge attribute data are copied to the new (relabeled) graph. + + See Also + -------- + relabel_nodes + """ + N = G.number_of_nodes()+first_label + if ordering == "default": + mapping = dict(zip(G.nodes(),range(first_label,N))) + elif ordering == "sorted": + nlist = G.nodes() + nlist.sort() + mapping=dict(zip(nlist,range(first_label,N))) + elif ordering == "increasing degree": + dv_pairs=[(d,n) for (n,d) in G.degree_iter()] + dv_pairs.sort() # in-place sort from lowest to highest degree + mapping = dict(zip([n for d,n in dv_pairs],range(first_label,N))) + elif ordering == "decreasing degree": + dv_pairs = [(d,n) for (n,d) in G.degree_iter()] + dv_pairs.sort() # in-place sort from lowest to highest degree + dv_pairs.reverse() + mapping = dict(zip([n for d,n in dv_pairs],range(first_label,N))) + else: + raise nx.NetworkXError('Unknown node ordering: %s'%ordering) + H = relabel_nodes(G,mapping) + H.name="("+G.name+")_with_int_labels" + # create node attribute with the old label + if label_attribute is not None: + nx.set_node_attributes(H, label_attribute, + dict((v,k) for k,v in mapping.items())) + return H diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/release.py b/lib/python2.7/site-packages/setoolsgui/networkx/release.py new file mode 100644 index 0000000..285db5f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/release.py @@ -0,0 +1,254 @@ +"""Release data for NetworkX. + +When NetworkX is imported a number of steps are followed to determine +the version information. + + 1) If the release is not a development release (dev=False), then version + information is read from version.py, a file containing statically + defined version information. This file should exist on every + downloadable release of NetworkX since setup.py creates it during + packaging/installation. However, version.py might not exist if one + is running NetworkX from the mercurial repository. In the event that + version.py does not exist, then no vcs information will be available. + + 2) If the release is a development release, then version information + is read dynamically, when possible. If no dynamic information can be + read, then an attempt is made to read the information from version.py. + If version.py does not exist, then no vcs information will be available. + +Clarification: + version.py is created only by setup.py + +When setup.py creates version.py, it does so before packaging/installation. +So the created file is included in the source distribution. When a user +downloads a tar.gz file and extracts the files, the files will not be in a +live version control repository. So when the user runs setup.py to install +NetworkX, we must make sure write_versionfile() does not overwrite the +revision information contained in the version.py that was included in the +tar.gz file. This is why write_versionfile() includes an early escape. + +""" + +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. + +from __future__ import absolute_import + +import os +import sys +import time +import datetime +import subprocess + +basedir = os.path.abspath(os.path.split(__file__)[0]) + +def write_versionfile(): + """Creates a static file containing version information.""" + versionfile = os.path.join(basedir, 'version.py') + + text = '''""" +Version information for NetworkX, created during installation. + +Do not add this file to the repository. + +""" + +import datetime + +version = %(version)r +date = %(date)r + +# Was NetworkX built from a development version? If so, remember that the major +# and minor versions reference the "target" (rather than "current") release. +dev = %(dev)r + +# Format: (name, major, min, revision) +version_info = %(version_info)r + +# Format: a 'datetime.datetime' instance +date_info = %(date_info)r + +# Format: (vcs, vcs_tuple) +vcs_info = %(vcs_info)r + +''' + + # Try to update all information + date, date_info, version, version_info, vcs_info = get_info(dynamic=True) + + def writefile(): + fh = open(versionfile, 'w') + subs = { + 'dev' : dev, + 'version': version, + 'version_info': version_info, + 'date': date, + 'date_info': date_info, + 'vcs_info': vcs_info + } + fh.write(text % subs) + fh.close() + + if vcs_info[0] == 'mercurial': + # Then, we want to update version.py. + writefile() + else: + if os.path.isfile(versionfile): + # This is *good*, and the most likely place users will be when + # running setup.py. We do not want to overwrite version.py. + # Grab the version so that setup can use it. + sys.path.insert(0, basedir) + from version import version + del sys.path[0] + else: + # This is *bad*. It means the user might have a tarball that + # does not include version.py. Let this error raise so we can + # fix the tarball. + ##raise Exception('version.py not found!') + + # We no longer require that prepared tarballs include a version.py + # So we use the possibly trunctated value from get_info() + # Then we write a new file. + writefile() + + return version + +def get_revision(): + """Returns revision and vcs information, dynamically obtained.""" + vcs, revision, tag = None, None, None + + hgdir = os.path.join(basedir, '..', '.hg') + gitdir = os.path.join(basedir, '..', '.git') + + if os.path.isdir(hgdir): + vcs = 'mercurial' + try: + p = subprocess.Popen(['hg', 'id'], + cwd=basedir, + stdout=subprocess.PIPE) + except OSError: + # Could not run hg, even though this is a mercurial repository. + pass + else: + stdout = p.communicate()[0] + # Force strings instead of unicode. + x = list(map(str, stdout.decode().strip().split())) + + if len(x) == 0: + # Somehow stdout was empty. This can happen, for example, + # if you're running in a terminal which has redirected stdout. + # In this case, we do not use any revision/tag info. + pass + elif len(x) == 1: + # We don't have 'tip' or anything similar...so no tag. + revision = str(x[0]) + else: + revision = str(x[0]) + tag = str(x[1]) + + elif os.path.isdir(gitdir): + vcs = 'git' + # For now, we are not bothering with revision and tag. + + vcs_info = (vcs, (revision, tag)) + + return revision, vcs_info + +def get_info(dynamic=True): + ## Date information + date_info = datetime.datetime.now() + date = time.asctime(date_info.timetuple()) + + revision, version, version_info, vcs_info = None, None, None, None + + import_failed = False + dynamic_failed = False + + if dynamic: + revision, vcs_info = get_revision() + if revision is None: + dynamic_failed = True + + if dynamic_failed or not dynamic: + # This is where most final releases of NetworkX will be. + # All info should come from version.py. If it does not exist, then + # no vcs information will be provided. + sys.path.insert(0, basedir) + try: + from version import date, date_info, version, version_info, vcs_info + except ImportError: + import_failed = True + vcs_info = (None, (None, None)) + else: + revision = vcs_info[1][0] + del sys.path[0] + + if import_failed or (dynamic and not dynamic_failed): + # We are here if: + # we failed to determine static versioning info, or + # we successfully obtained dynamic revision info + version = ''.join([str(major), '.', str(minor)]) + if dev: + version += '.dev_' + date_info.strftime("%Y%m%d%H%M%S") + version_info = (name, major, minor, revision) + + return date, date_info, version, version_info, vcs_info + +## Version information +name = 'networkx' +major = "1" +minor = "8.1" + + +## Declare current release as a development release. +## Change to False before tagging a release; then change back. +dev = False + + +description = "Python package for creating and manipulating graphs and networks" + +long_description = \ +""" +NetworkX is a Python package for the creation, manipulation, and +study of the structure, dynamics, and functions of complex networks. + +""" +license = 'BSD' +authors = {'Hagberg' : ('Aric Hagberg','hagberg@lanl.gov'), + 'Schult' : ('Dan Schult','dschult@colgate.edu'), + 'Swart' : ('Pieter Swart','swart@lanl.gov') + } +maintainer = "NetworkX Developers" +maintainer_email = "networkx-discuss@googlegroups.com" +url = 'http://networkx.lanl.gov/' +download_url="http://networkx.lanl.gov/download/networkx" +platforms = ['Linux','Mac OSX','Windows','Unix'] +keywords = ['Networks', 'Graph Theory', 'Mathematics', 'network', 'graph', 'discrete mathematics', 'math'] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.1', + 'Programming Language :: Python :: 3.2', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Scientific/Engineering :: Bio-Informatics', + 'Topic :: Scientific/Engineering :: Information Analysis', + 'Topic :: Scientific/Engineering :: Mathematics', + 'Topic :: Scientific/Engineering :: Physics'] + +date, date_info, version, version_info, vcs_info = get_info() + +if __name__ == '__main__': + # Write versionfile for nightly snapshots. + write_versionfile() + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/testing/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/testing/__init__.py new file mode 100644 index 0000000..db57076 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/testing/__init__.py @@ -0,0 +1 @@ +from networkx.testing.utils import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/testing/tests/test_utils.py b/lib/python2.7/site-packages/setoolsgui/networkx/testing/tests/test_utils.py new file mode 100644 index 0000000..9c57649 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/testing/tests/test_utils.py @@ -0,0 +1,108 @@ +from nose.tools import * +import networkx as nx +from networkx.testing import * + +# thanks to numpy for this GenericTest class (numpy/testing/test_utils.py) +class _GenericTest(object): + def _test_equal(self, a, b): + self._assert_func(a, b) + + def _test_not_equal(self, a, b): + try: + self._assert_func(a, b) + passed = True + except AssertionError: + pass + else: + raise AssertionError("a and b are found equal but are not") + + +class TestNodesEqual(_GenericTest): + def setUp(self): + self._assert_func = assert_nodes_equal + + def test_nodes_equal(self): + a = [1,2,5,4] + b = [4,5,1,2] + self._test_equal(a,b) + + def test_nodes_not_equal(self): + a = [1,2,5,4] + b = [4,5,1,3] + self._test_not_equal(a,b) + + def test_nodes_with_data_equal(self): + G = nx.Graph() + G.add_nodes_from([1,2,3],color='red') + H = nx.Graph() + H.add_nodes_from([1,2,3],color='red') + self._test_equal(G.nodes(data=True), H.nodes(data=True)) + + def test_edges_with_data_not_equal(self): + G = nx.Graph() + G.add_nodes_from([1,2,3],color='red') + H = nx.Graph() + H.add_nodes_from([1,2,3],color='blue') + self._test_not_equal(G.nodes(data=True), H.nodes(data=True)) + + +class TestEdgesEqual(_GenericTest): + def setUp(self): + self._assert_func = assert_edges_equal + + def test_edges_equal(self): + a = [(1,2),(5,4)] + b = [(4,5),(1,2)] + self._test_equal(a,b) + + def test_edges_not_equal(self): + a = [(1,2),(5,4)] + b = [(4,5),(1,3)] + self._test_not_equal(a,b) + + def test_edges_with_data_equal(self): + G = nx.MultiGraph() + G.add_path([0,1,2],weight=1) + H = nx.MultiGraph() + H.add_path([0,1,2],weight=1) + self._test_equal(G.edges(data=True, keys=True), + H.edges(data=True, keys=True)) + + def test_edges_with_data_not_equal(self): + G = nx.MultiGraph() + G.add_path([0,1,2],weight=1) + H = nx.MultiGraph() + H.add_path([0,1,2],weight=2) + self._test_not_equal(G.edges(data=True, keys=True), + H.edges(data=True, keys=True)) + +class TestGraphsEqual(_GenericTest): + def setUp(self): + self._assert_func = assert_graphs_equal + + def test_graphs_equal(self): + G = nx.path_graph(4) + H = nx.Graph() + H.add_path(range(4)) + H.name='path_graph(4)' + self._test_equal(G,H) + + def test_graphs_not_equal(self): + G = nx.path_graph(4) + H = nx.Graph() + H.add_cycle(range(4)) + self._test_not_equal(G,H) + + def test_graphs_not_equal2(self): + G = nx.path_graph(4) + H = nx.Graph() + H.add_path(range(3)) + H.name='path_graph(4)' + self._test_not_equal(G,H) + + def test_graphs_not_equal3(self): + G = nx.path_graph(4) + H = nx.Graph() + H.add_path(range(4)) + H.name='path_graph(foo)' + self._test_not_equal(G,H) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/testing/utils.py b/lib/python2.7/site-packages/setoolsgui/networkx/testing/utils.py new file mode 100644 index 0000000..5cca7f8 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/testing/utils.py @@ -0,0 +1,57 @@ +import operator +from nose.tools import * +__all__ = ['assert_nodes_equal', 'assert_edges_equal','assert_graphs_equal'] + +def assert_nodes_equal(nlist1, nlist2): + # Assumes lists are either nodes, or (node,datadict) tuples, + # and also that nodes are orderable/sortable. + try: + l = len(nlist1[0]) + n1 = sorted(nlist1,key=operator.itemgetter(0)) + n2 = sorted(nlist2,key=operator.itemgetter(0)) + assert_equal(len(n1),len(n2)) + for a,b in zip(n1,n2): + assert_equal(a,b) + except TypeError: + assert_equal(set(nlist1),set(nlist2)) + return + +def assert_edges_equal(elist1, elist2): + # Assumes lists with u,v nodes either as + # edge tuples (u,v) + # edge tuples with data dicts (u,v,d) + # edge tuples with keys and data dicts (u,v,k, d) + # and also that nodes are orderable/sortable. + e1 = sorted(elist1,key=lambda x: sorted(x[0:2])) + e2 = sorted(elist2,key=lambda x: sorted(x[0:2])) + assert_equal(len(e1),len(e2)) + if len(e1) == 0: + return True + if len(e1[0]) == 2: + for a,b in zip(e1,e2): + assert_equal(set(a[0:2]),set(b[0:2])) + elif len(e1[0]) == 3: + for a,b in zip(e1,e2): + assert_equal(set(a[0:2]),set(b[0:2])) + assert_equal(a[2],b[2]) + elif len(e1[0]) == 4: + for a,b in zip(e1,e2): + assert_equal(set(a[0:2]),set(b[0:2])) + assert_equal(a[2],b[2]) + assert_equal(a[3],b[3]) + + +def assert_graphs_equal(graph1, graph2): + if graph1.is_multigraph(): + edges1 = graph1.edges(data=True,keys=True) + else: + edges1 = graph1.edges(data=True) + if graph2.is_multigraph(): + edges2 = graph2.edges(data=True,keys=True) + else: + edges2 = graph2.edges(data=True) + assert_nodes_equal(graph1.nodes(data=True), + graph2.nodes(data=True)) + assert_edges_equal(edges1, edges2) + assert_equal(graph1.graph,graph2.graph) + return diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/benchmark.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/benchmark.py new file mode 100644 index 0000000..5eb68d0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/benchmark.py @@ -0,0 +1,248 @@ +from timeit import Timer + +# This is gratefully modeled after the benchmarks found in +# the numpy svn repository. http://svn.scipy.org/svn/numpy/trunk + +class Benchmark(object): + """ + Benchmark a method or simple bit of code using different Graph classes. + If the test code is the same for each graph class, then you can set it + during instantiation through the argument test_string. + The argument test_string can also be a tuple of test code and setup code. + The code is entered as a string valid for use with the timeit module. + + Example: + >>> b=Benchmark(['Graph','XGraph']) + >>> b['Graph']=('G.add_nodes_from(nlist)','nlist=range(100)') + >>> b.run() + """ + def __init__(self,graph_classes,title='',test_string=None,runs=3,reps=1000): + self.runs = runs + self.reps = reps + self.title = title + self.class_tests = dict((gc,'') for gc in graph_classes) + # set up the test string if it is the same for all classes. + if test_string is not None: + if isinstance(test_string,tuple): + self['all']=test_string + else: + self['all']=(test_string,'') + + def __setitem__(self,graph_class,some_strs): + """ + Set a simple bit of code and setup string for the test. + Use this for cases where the code differs from one class to another. + """ + test_str, setup_str = some_strs + if graph_class == 'all': + graph_class = self.class_tests.keys() + elif not isinstance(graph_class,list): + graph_class = [graph_class] + + for GC in graph_class: + setup_string='import networkx as NX\nG=NX.%s.%s()\n'%\ + (GC.lower(),GC) + setup_str + self.class_tests[GC] = Timer(test_str, setup_string) + + + def run(self): + """Run the benchmark for each class and print results.""" + column_len = max(len(G) for G in self.class_tests) + + print('='*72) + if self.title: + print("%s: %s runs, %s reps"% (self.title,self.runs,self.reps)) + print('='*72) + + times=[] + for GC,timer in self.class_tests.items(): + name = GC.ljust(column_len) + try: + t=sum(timer.repeat(self.runs,self.reps))/self.runs +# print "%s: %s" % (name, timer.repeat(self.runs,self.reps)) + times.append((t,name)) + except Exception as e: + print("%s: Failed to benchmark (%s)." % (name,e)) + + + times.sort() + tmin=times[0][0] + for t,name in times: + print("%s: %5.2f %s" % (name, t/tmin*100.,t)) + print('-'*72) + print() + +if __name__ == "__main__": + # set up for all routines: + classes=['Graph','MultiGraph','DiGraph','MultiDiGraph'] + all_tests=['add_nodes','add_edges','remove_nodes','remove_edges',\ + 'neighbors','edges','degree','dijkstra','shortest path',\ + 'subgraph','edgedata_subgraph','laplacian'] + # Choose which tests to run + tests=all_tests + tests=['subgraph','edgedata_subgraph'] + #tests=all_tests[-1:] + N=100 + + if 'add_nodes' in tests: + title='Benchmark: Adding nodes' + test_string=('G.add_nodes_from(nlist)','nlist=range(%i)'%N) + b=Benchmark(classes,title,test_string,runs=3,reps=1000) + b.run() + + if 'add_edges' in tests: + title='Benchmark: Adding edges' + setup='elist=[(i,i+3) for i in range(%s-3)]\nG.add_nodes_from(range(%i))'%(N,N) + test_string=('G.add_edges_from(elist)',setup) + b=Benchmark(classes,title,test_string,runs=3,reps=1000) + b.run() + + if 'remove_nodes' in tests: + title='Benchmark: Adding and Deleting nodes' + setup='nlist=range(%i)'%N + test_string=('G.add_nodes_from(nlist)\nG.remove_nodes_from(nlist)',setup) + b=Benchmark(classes,title,test_string,runs=3,reps=1000) + b.run() + + if 'remove_edges' in tests: + title='Benchmark: Adding and Deleting edges' + setup='elist=[(i,i+3) for i in range(%s-3)]'%N + test_string=('G.add_edges_from(elist)\nG.remove_edges_from(elist)',setup) + b=Benchmark(classes,title,test_string,runs=3,reps=1000) + b.run() + + if 'neighbors' in tests: + N=500 + p=0.3 + title='Benchmark: reporting neighbors' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='for n in G:\n for nbr in G.neighbors(n):\n pass' + all_setup='H=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)\n' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'edges' in tests: + N=500 + p=0.3 + title='Benchmark: reporting edges' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='for n in G:\n for e in G.edges(n):\n pass' + all_setup='H=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)\n' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'degree' in tests: + N=500 + p=0.3 + title='Benchmark: reporting degree' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='for d in G.degree():\n pass' + all_setup='H=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)\n' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'dijkstra' in tests: + N=500 + p=0.3 + title='dijkstra single source shortest path' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='p=NX.single_source_dijkstra(G,i)' + all_setup='i=6\nH=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'shortest path' in tests: + N=500 + p=0.3 + title='single source shortest path' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='p=NX.single_source_shortest_path(G,i)' + all_setup='i=6\nH=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'subgraph' in tests: + N=500 + p=0.3 + title='subgraph method' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='G.subgraph(nlist)' + all_setup='nlist=range(100,150)\nH=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'edgedata_subgraph' in tests: + N=500 + p=0.3 + title='subgraph method with edge data present' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='G.subgraph(nlist)' + all_setup='nlist=range(100,150)\nH=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v,hi=3)' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)],hi=2)' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v,hi=1)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)],hi=2)' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() + + if 'laplacian' in tests: + N=500 + p=0.3 + title='creation of laplacian matrix' + b=Benchmark(classes,title,runs=3,reps=1) + test_string='NX.laplacian(G)' + all_setup='H=NX.binomial_graph(%s,%s)\nfor (u,v) in H.edges_iter():\n '%(N,p) + setup=all_setup+'G.add_edge(u,v)' + if 'Graph' in classes: b['Graph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'DiGraph' in classes: b['DiGraph']=(test_string,setup) + setup=all_setup+'G.add_edge(u,v)' + if 'MultiGraph' in classes: b['MultiGraph']=(test_string,setup) + setup=all_setup+'G.add_edges_from([(u,v),(v,u)])' + if 'MultiDiGraph' in classes: b['MultiDiGraph']=(test_string,setup) + b.run() diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/test.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test.py new file mode 100755 index 0000000..e776d32 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +import sys +from os import path,getcwd + +def run(verbosity=1,doctest=False,numpy=True): + """Run NetworkX tests. + + Parameters + ---------- + verbosity: integer, optional + Level of detail in test reports. Higher numbers provide more detail. + + doctest: bool, optional + True to run doctests in code modules + + numpy: bool, optional + True to test modules dependent on numpy + """ + try: + import nose + except ImportError: + raise ImportError(\ + "The nose package is needed to run the NetworkX tests.") + + sys.stderr.write("Running NetworkX tests:") + nx_install_dir=path.join(path.dirname(__file__), path.pardir) + # stop if running from source directory + if getcwd() == path.abspath(path.join(nx_install_dir,path.pardir)): + raise RuntimeError("Can't run tests from source directory.\n" + "Run 'nosetests' from the command line.") + + argv=[' ','--verbosity=%d'%verbosity, + '-w',nx_install_dir, + '-exe'] + if doctest: + argv.extend(['--with-doctest','--doctest-extension=txt']) + if not numpy: + argv.extend(['-A not numpy']) + + + nose.run(argv=argv) + +if __name__=="__main__": + run() + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert.py new file mode 100644 index 0000000..38a66e2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python + +"""Convert +======= +""" + +from nose.tools import * +from networkx import * +from networkx.convert import * +from networkx.algorithms.operators import * +from networkx.generators.classic import barbell_graph,cycle_graph + +class TestConvert(): + def edgelists_equal(self,e1,e2): + return sorted(sorted(e) for e in e1)==sorted(sorted(e) for e in e2) + + + + def test_simple_graphs(self): + for dest, source in [(to_dict_of_dicts, from_dict_of_dicts), + (to_dict_of_lists, from_dict_of_lists)]: + G=barbell_graph(10,3) + dod=dest(G) + + # Dict of [dicts, lists] + GG=source(dod) + assert_equal(sorted(G.nodes()), sorted(GG.nodes())) + assert_equal(sorted(G.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dod) + assert_equal(sorted(G.nodes()), sorted(GW.nodes())) + assert_equal(sorted(G.edges()), sorted(GW.edges())) + GI=Graph(dod) + assert_equal(sorted(G.nodes()), sorted(GI.nodes())) + assert_equal(sorted(G.edges()), sorted(GI.edges())) + + # With nodelist keyword + P4=path_graph(4) + P3=path_graph(3) + dod=dest(P4,nodelist=[0,1,2]) + Gdod=Graph(dod) + assert_equal(sorted(Gdod.nodes()), sorted(P3.nodes())) + assert_equal(sorted(Gdod.edges()), sorted(P3.edges())) + + def test_digraphs(self): + for dest, source in [(to_dict_of_dicts, from_dict_of_dicts), + (to_dict_of_lists, from_dict_of_lists)]: + G=cycle_graph(10) + + # Dict of [dicts, lists] + dod=dest(G) + GG=source(dod) + assert_equal(sorted(G.nodes()), sorted(GG.nodes())) + assert_equal(sorted(G.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dod) + assert_equal(sorted(G.nodes()), sorted(GW.nodes())) + assert_equal(sorted(G.edges()), sorted(GW.edges())) + GI=Graph(dod) + assert_equal(sorted(G.nodes()), sorted(GI.nodes())) + assert_equal(sorted(G.edges()), sorted(GI.edges())) + + G=cycle_graph(10,create_using=DiGraph()) + dod=dest(G) + GG=source(dod, create_using=DiGraph()) + assert_equal(sorted(G.nodes()), sorted(GG.nodes())) + assert_equal(sorted(G.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dod, create_using=DiGraph()) + assert_equal(sorted(G.nodes()), sorted(GW.nodes())) + assert_equal(sorted(G.edges()), sorted(GW.edges())) + GI=DiGraph(dod) + assert_equal(sorted(G.nodes()), sorted(GI.nodes())) + assert_equal(sorted(G.edges()), sorted(GI.edges())) + + def test_graph(self): + G=cycle_graph(10) + e=G.edges() + source=[u for u,v in e] + dest=[v for u,v in e] + ex=zip(source,dest,source) + G=Graph() + G.add_weighted_edges_from(ex) + + # Dict of dicts + dod=to_dict_of_dicts(G) + GG=from_dict_of_dicts(dod,create_using=Graph()) + assert_equal(sorted(G.nodes()), sorted(GG.nodes())) + assert_equal(sorted(G.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dod,create_using=Graph()) + assert_equal(sorted(G.nodes()), sorted(GW.nodes())) + assert_equal(sorted(G.edges()), sorted(GW.edges())) + GI=Graph(dod) + assert_equal(sorted(G.nodes()), sorted(GI.nodes())) + assert_equal(sorted(G.edges()), sorted(GI.edges())) + + # Dict of lists + dol=to_dict_of_lists(G) + GG=from_dict_of_lists(dol,create_using=Graph()) + # dict of lists throws away edge data so set it to none + enone=[(u,v,{}) for (u,v,d) in G.edges(data=True)] + assert_equal(sorted(G.nodes()), sorted(GG.nodes())) + assert_equal(enone, sorted(GG.edges(data=True))) + GW=to_networkx_graph(dol,create_using=Graph()) + assert_equal(sorted(G.nodes()), sorted(GW.nodes())) + assert_equal(enone, sorted(GW.edges(data=True))) + GI=Graph(dol) + assert_equal(sorted(G.nodes()), sorted(GI.nodes())) + assert_equal(enone, sorted(GI.edges(data=True))) + + + def test_with_multiedges_self_loops(self): + G=cycle_graph(10) + e=G.edges() + source,dest = list(zip(*e)) + ex=list(zip(source,dest,source)) + XG=Graph() + XG.add_weighted_edges_from(ex) + XGM=MultiGraph() + XGM.add_weighted_edges_from(ex) + XGM.add_edge(0,1,weight=2) # multiedge + XGS=Graph() + XGS.add_weighted_edges_from(ex) + XGS.add_edge(0,0,weight=100) # self loop + + # Dict of dicts + # with self loops, OK + dod=to_dict_of_dicts(XGS) + GG=from_dict_of_dicts(dod,create_using=Graph()) + assert_equal(sorted(XGS.nodes()), sorted(GG.nodes())) + assert_equal(sorted(XGS.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dod,create_using=Graph()) + assert_equal(sorted(XGS.nodes()), sorted(GW.nodes())) + assert_equal(sorted(XGS.edges()), sorted(GW.edges())) + GI=Graph(dod) + assert_equal(sorted(XGS.nodes()), sorted(GI.nodes())) + assert_equal(sorted(XGS.edges()), sorted(GI.edges())) + + # Dict of lists + # with self loops, OK + dol=to_dict_of_lists(XGS) + GG=from_dict_of_lists(dol,create_using=Graph()) + # dict of lists throws away edge data so set it to none + enone=[(u,v,{}) for (u,v,d) in XGS.edges(data=True)] + assert_equal(sorted(XGS.nodes()), sorted(GG.nodes())) + assert_equal(enone, sorted(GG.edges(data=True))) + GW=to_networkx_graph(dol,create_using=Graph()) + assert_equal(sorted(XGS.nodes()), sorted(GW.nodes())) + assert_equal(enone, sorted(GW.edges(data=True))) + GI=Graph(dol) + assert_equal(sorted(XGS.nodes()), sorted(GI.nodes())) + assert_equal(enone, sorted(GI.edges(data=True))) + + # Dict of dicts + # with multiedges, OK + dod=to_dict_of_dicts(XGM) + GG=from_dict_of_dicts(dod,create_using=MultiGraph(), + multigraph_input=True) + assert_equal(sorted(XGM.nodes()), sorted(GG.nodes())) + assert_equal(sorted(XGM.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dod,create_using=MultiGraph(),multigraph_input=True) + assert_equal(sorted(XGM.nodes()), sorted(GW.nodes())) + assert_equal(sorted(XGM.edges()), sorted(GW.edges())) + GI=MultiGraph(dod) # convert can't tell whether to duplicate edges! + assert_equal(sorted(XGM.nodes()), sorted(GI.nodes())) + #assert_not_equal(sorted(XGM.edges()), sorted(GI.edges())) + assert_false(sorted(XGM.edges()) == sorted(GI.edges())) + GE=from_dict_of_dicts(dod,create_using=MultiGraph(), + multigraph_input=False) + assert_equal(sorted(XGM.nodes()), sorted(GE.nodes())) + assert_not_equal(sorted(XGM.edges()), sorted(GE.edges())) + GI=MultiGraph(XGM) + assert_equal(sorted(XGM.nodes()), sorted(GI.nodes())) + assert_equal(sorted(XGM.edges()), sorted(GI.edges())) + GM=MultiGraph(G) + assert_equal(sorted(GM.nodes()), sorted(G.nodes())) + assert_equal(sorted(GM.edges()), sorted(G.edges())) + + # Dict of lists + # with multiedges, OK, but better write as DiGraph else you'll + # get double edges + dol=to_dict_of_lists(G) + GG=from_dict_of_lists(dol,create_using=MultiGraph()) + assert_equal(sorted(G.nodes()), sorted(GG.nodes())) + assert_equal(sorted(G.edges()), sorted(GG.edges())) + GW=to_networkx_graph(dol,create_using=MultiGraph()) + assert_equal(sorted(G.nodes()), sorted(GW.nodes())) + assert_equal(sorted(G.edges()), sorted(GW.edges())) + GI=MultiGraph(dol) + assert_equal(sorted(G.nodes()), sorted(GI.nodes())) + assert_equal(sorted(G.edges()), sorted(GI.edges())) + + def test_edgelists(self): + P=path_graph(4) + e=[(0,1),(1,2),(2,3)] + G=Graph(e) + assert_equal(sorted(G.nodes()), sorted(P.nodes())) + assert_equal(sorted(G.edges()), sorted(P.edges())) + assert_equal(sorted(G.edges(data=True)), sorted(P.edges(data=True))) + + e=[(0,1,{}),(1,2,{}),(2,3,{})] + G=Graph(e) + assert_equal(sorted(G.nodes()), sorted(P.nodes())) + assert_equal(sorted(G.edges()), sorted(P.edges())) + assert_equal(sorted(G.edges(data=True)), sorted(P.edges(data=True))) + + e=((n,n+1) for n in range(3)) + G=Graph(e) + assert_equal(sorted(G.nodes()), sorted(P.nodes())) + assert_equal(sorted(G.edges()), sorted(P.edges())) + assert_equal(sorted(G.edges(data=True)), sorted(P.edges(data=True))) + + def test_directed_to_undirected(self): + edges1 = [(0, 1), (1, 2), (2, 0)] + edges2 = [(0, 1), (1, 2), (0, 2)] + assert_true(self.edgelists_equal(nx.Graph(nx.DiGraph(edges1)).edges(),edges1)) + assert_true(self.edgelists_equal(nx.Graph(nx.DiGraph(edges2)).edges(),edges1)) + assert_true(self.edgelists_equal(nx.MultiGraph(nx.DiGraph(edges1)).edges(),edges1)) + assert_true(self.edgelists_equal(nx.MultiGraph(nx.DiGraph(edges2)).edges(),edges1)) + + assert_true(self.edgelists_equal(nx.MultiGraph(nx.MultiDiGraph(edges1)).edges(), + edges1)) + assert_true(self.edgelists_equal(nx.MultiGraph(nx.MultiDiGraph(edges2)).edges(), + edges1)) + + assert_true(self.edgelists_equal(nx.Graph(nx.MultiDiGraph(edges1)).edges(),edges1)) + assert_true(self.edgelists_equal(nx.Graph(nx.MultiDiGraph(edges2)).edges(),edges1)) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_numpy.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_numpy.py new file mode 100644 index 0000000..2a4ef96 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_numpy.py @@ -0,0 +1,172 @@ +from nose import SkipTest +from nose.tools import assert_raises, assert_true, assert_equal + +import networkx as nx +from networkx.generators.classic import barbell_graph,cycle_graph,path_graph + +class TestConvertNumpy(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global np + global np_assert_equal + try: + import numpy as np + np_assert_equal=np.testing.assert_equal + except ImportError: + raise SkipTest('NumPy not available.') + + def __init__(self): + self.G1 = barbell_graph(10, 3) + self.G2 = cycle_graph(10, create_using=nx.DiGraph()) + + self.G3 = self.create_weighted(nx.Graph()) + self.G4 = self.create_weighted(nx.DiGraph()) + + def create_weighted(self, G): + g = cycle_graph(4) + e = g.edges() + source = [u for u,v in e] + dest = [v for u,v in e] + weight = [s+10 for s in source] + ex = zip(source, dest, weight) + G.add_weighted_edges_from(ex) + return G + + def assert_equal(self, G1, G2): + assert_true( sorted(G1.nodes())==sorted(G2.nodes()) ) + assert_true( sorted(G1.edges())==sorted(G2.edges()) ) + + def identity_conversion(self, G, A, create_using): + GG = nx.from_numpy_matrix(A, create_using=create_using) + self.assert_equal(G, GG) + GW = nx.to_networkx_graph(A, create_using=create_using) + self.assert_equal(G, GW) + GI = create_using.__class__(A) + self.assert_equal(G, GI) + + def test_shape(self): + "Conversion from non-square array." + A=np.array([[1,2,3],[4,5,6]]) + assert_raises(nx.NetworkXError, nx.from_numpy_matrix, A) + + def test_identity_graph_matrix(self): + "Conversion from graph to matrix to graph." + A = nx.to_numpy_matrix(self.G1) + self.identity_conversion(self.G1, A, nx.Graph()) + + def test_identity_graph_array(self): + "Conversion from graph to array to graph." + A = nx.to_numpy_matrix(self.G1) + A = np.asarray(A) + self.identity_conversion(self.G1, A, nx.Graph()) + + def test_identity_digraph_matrix(self): + """Conversion from digraph to matrix to digraph.""" + A = nx.to_numpy_matrix(self.G2) + self.identity_conversion(self.G2, A, nx.DiGraph()) + + def test_identity_digraph_array(self): + """Conversion from digraph to array to digraph.""" + A = nx.to_numpy_matrix(self.G2) + A = np.asarray(A) + self.identity_conversion(self.G2, A, nx.DiGraph()) + + def test_identity_weighted_graph_matrix(self): + """Conversion from weighted graph to matrix to weighted graph.""" + A = nx.to_numpy_matrix(self.G3) + self.identity_conversion(self.G3, A, nx.Graph()) + + def test_identity_weighted_graph_array(self): + """Conversion from weighted graph to array to weighted graph.""" + A = nx.to_numpy_matrix(self.G3) + A = np.asarray(A) + self.identity_conversion(self.G3, A, nx.Graph()) + + def test_identity_weighted_digraph_matrix(self): + """Conversion from weighted digraph to matrix to weighted digraph.""" + A = nx.to_numpy_matrix(self.G4) + self.identity_conversion(self.G4, A, nx.DiGraph()) + + def test_identity_weighted_digraph_array(self): + """Conversion from weighted digraph to array to weighted digraph.""" + A = nx.to_numpy_matrix(self.G4) + A = np.asarray(A) + self.identity_conversion(self.G4, A, nx.DiGraph()) + + def test_nodelist(self): + """Conversion from graph to matrix to graph with nodelist.""" + P4 = path_graph(4) + P3 = path_graph(3) + nodelist = P3.nodes() + A = nx.to_numpy_matrix(P4, nodelist=nodelist) + GA = nx.Graph(A) + self.assert_equal(GA, P3) + + # Make nodelist ambiguous by containing duplicates. + nodelist += [nodelist[0]] + assert_raises(nx.NetworkXError, nx.to_numpy_matrix, P3, nodelist=nodelist) + + def test_weight_keyword(self): + WP4 = nx.Graph() + WP4.add_edges_from( (n,n+1,dict(weight=0.5,other=0.3)) for n in range(3) ) + P4 = path_graph(4) + A = nx.to_numpy_matrix(P4) + np_assert_equal(A, nx.to_numpy_matrix(WP4,weight=None)) + np_assert_equal(0.5*A, nx.to_numpy_matrix(WP4)) + np_assert_equal(0.3*A, nx.to_numpy_matrix(WP4,weight='other')) + + def test_from_numpy_matrix_type(self): + A=np.matrix([[1]]) + G=nx.from_numpy_matrix(A) + assert_equal(type(G[0][0]['weight']),int) + + A=np.matrix([[1]]).astype(np.float) + G=nx.from_numpy_matrix(A) + assert_equal(type(G[0][0]['weight']),float) + + A=np.matrix([[1]]).astype(np.str) + G=nx.from_numpy_matrix(A) + assert_equal(type(G[0][0]['weight']),str) + + A=np.matrix([[1]]).astype(np.bool) + G=nx.from_numpy_matrix(A) + assert_equal(type(G[0][0]['weight']),bool) + + A=np.matrix([[1]]).astype(np.complex) + G=nx.from_numpy_matrix(A) + assert_equal(type(G[0][0]['weight']),complex) + + A=np.matrix([[1]]).astype(np.object) + assert_raises(TypeError,nx.from_numpy_matrix,A) + + def test_from_numpy_matrix_dtype(self): + dt=[('weight',float),('cost',int)] + A=np.matrix([[(1.0,2)]],dtype=dt) + G=nx.from_numpy_matrix(A) + assert_equal(type(G[0][0]['weight']),float) + assert_equal(type(G[0][0]['cost']),int) + assert_equal(G[0][0]['cost'],2) + assert_equal(G[0][0]['weight'],1.0) + + def test_to_numpy_recarray(self): + G=nx.Graph() + G.add_edge(1,2,weight=7.0,cost=5) + A=nx.to_numpy_recarray(G,dtype=[('weight',float),('cost',int)]) + assert_equal(sorted(A.dtype.names),['cost','weight']) + assert_equal(A.weight[0,1],7.0) + assert_equal(A.weight[0,0],0.0) + assert_equal(A.cost[0,1],5) + assert_equal(A.cost[0,0],0) + + def test_numpy_multigraph(self): + G=nx.MultiGraph() + G.add_edge(1,2,weight=7) + G.add_edge(1,2,weight=70) + A=nx.to_numpy_matrix(G) + assert_equal(A[1,0],77) + A=nx.to_numpy_matrix(G,multigraph_weight=min) + assert_equal(A[1,0],7) + A=nx.to_numpy_matrix(G,multigraph_weight=max) + assert_equal(A[1,0],70) + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_scipy.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_scipy.py new file mode 100644 index 0000000..f90dee7 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_convert_scipy.py @@ -0,0 +1,179 @@ +from nose import SkipTest +from nose.tools import assert_raises, assert_true, assert_equal, raises + +import networkx as nx +from networkx.generators.classic import barbell_graph,cycle_graph,path_graph + +class TestConvertNumpy(object): + @classmethod + def setupClass(cls): + global np, sp, sparse, np_assert_equal + try: + import numpy as np + import scipy as sp + import scipy.sparse as sparse + np_assert_equal=np.testing.assert_equal + except ImportError: + raise SkipTest('SciPy sparse library not available.') + + def __init__(self): + self.G1 = barbell_graph(10, 3) + self.G2 = cycle_graph(10, create_using=nx.DiGraph()) + + self.G3 = self.create_weighted(nx.Graph()) + self.G4 = self.create_weighted(nx.DiGraph()) + + def create_weighted(self, G): + g = cycle_graph(4) + e = g.edges() + source = [u for u,v in e] + dest = [v for u,v in e] + weight = [s+10 for s in source] + ex = zip(source, dest, weight) + G.add_weighted_edges_from(ex) + return G + + def assert_equal(self, G1, G2): + assert_true( sorted(G1.nodes())==sorted(G2.nodes()) ) + assert_true( sorted(G1.edges())==sorted(G2.edges()) ) + + def identity_conversion(self, G, A, create_using): + GG = nx.from_scipy_sparse_matrix(A, create_using=create_using) + self.assert_equal(G, GG) + + GW = nx.to_networkx_graph(A, create_using=create_using) + self.assert_equal(G, GW) + + GI = create_using.__class__(A) + self.assert_equal(G, GI) + + ACSR = A.tocsr() + GI = create_using.__class__(ACSR) + self.assert_equal(G, GI) + + ACOO = A.tocoo() + GI = create_using.__class__(ACOO) + self.assert_equal(G, GI) + + ACSC = A.tocsc() + GI = create_using.__class__(ACSC) + self.assert_equal(G, GI) + + AD = A.todense() + GI = create_using.__class__(AD) + self.assert_equal(G, GI) + + AA = A.toarray() + GI = create_using.__class__(AA) + self.assert_equal(G, GI) + + def test_shape(self): + "Conversion from non-square sparse array." + A = sp.sparse.lil_matrix([[1,2,3],[4,5,6]]) + assert_raises(nx.NetworkXError, nx.from_scipy_sparse_matrix, A) + + def test_identity_graph_matrix(self): + "Conversion from graph to sparse matrix to graph." + A = nx.to_scipy_sparse_matrix(self.G1) + self.identity_conversion(self.G1, A, nx.Graph()) + + def test_identity_digraph_matrix(self): + "Conversion from digraph to sparse matrix to digraph." + A = nx.to_scipy_sparse_matrix(self.G2) + self.identity_conversion(self.G2, A, nx.DiGraph()) + + def test_identity_weighted_graph_matrix(self): + """Conversion from weighted graph to sparse matrix to weighted graph.""" + A = nx.to_scipy_sparse_matrix(self.G3) + self.identity_conversion(self.G3, A, nx.Graph()) + + def test_identity_weighted_digraph_matrix(self): + """Conversion from weighted digraph to sparse matrix to weighted digraph.""" + A = nx.to_scipy_sparse_matrix(self.G4) + self.identity_conversion(self.G4, A, nx.DiGraph()) + + def test_nodelist(self): + """Conversion from graph to sparse matrix to graph with nodelist.""" + P4 = path_graph(4) + P3 = path_graph(3) + nodelist = P3.nodes() + A = nx.to_scipy_sparse_matrix(P4, nodelist=nodelist) + GA = nx.Graph(A) + self.assert_equal(GA, P3) + + # Make nodelist ambiguous by containing duplicates. + nodelist += [nodelist[0]] + assert_raises(nx.NetworkXError, nx.to_numpy_matrix, P3, + nodelist=nodelist) + + def test_weight_keyword(self): + WP4 = nx.Graph() + WP4.add_edges_from( (n,n+1,dict(weight=0.5,other=0.3)) + for n in range(3) ) + P4 = path_graph(4) + A = nx.to_scipy_sparse_matrix(P4) + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + np_assert_equal(0.5*A.todense(), + nx.to_scipy_sparse_matrix(WP4).todense()) + np_assert_equal(0.3*A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight='other').todense()) + + def test_format_keyword(self): + WP4 = nx.Graph() + WP4.add_edges_from( (n,n+1,dict(weight=0.5,other=0.3)) + for n in range(3) ) + P4 = path_graph(4) + A = nx.to_scipy_sparse_matrix(P4, format='csr') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + A = nx.to_scipy_sparse_matrix(P4, format='csc') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + A = nx.to_scipy_sparse_matrix(P4, format='coo') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + A = nx.to_scipy_sparse_matrix(P4, format='bsr') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + A = nx.to_scipy_sparse_matrix(P4, format='lil') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + A = nx.to_scipy_sparse_matrix(P4, format='dia') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + A = nx.to_scipy_sparse_matrix(P4, format='dok') + np_assert_equal(A.todense(), + nx.to_scipy_sparse_matrix(WP4,weight=None).todense()) + + @raises(nx.NetworkXError) + def test_format_keyword_fail(self): + WP4 = nx.Graph() + WP4.add_edges_from( (n,n+1,dict(weight=0.5,other=0.3)) + for n in range(3) ) + P4 = path_graph(4) + nx.to_scipy_sparse_matrix(P4, format='any_other') + + @raises(nx.NetworkXError) + def test_null_fail(self): + nx.to_scipy_sparse_matrix(nx.Graph()) + + def test_empty(self): + G = nx.Graph() + G.add_node(1) + M = nx.to_scipy_sparse_matrix(G) + np_assert_equal(M.todense(), np.matrix([[0]])) + + def test_ordering(self): + G = nx.DiGraph() + G.add_edge(1,2) + G.add_edge(2,3) + G.add_edge(3,1) + M = nx.to_scipy_sparse_matrix(G,nodelist=[3,2,1]) + np_assert_equal(M.todense(), np.matrix([[0,0,1],[1,0,0],[0,1,0]])) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_exceptions.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_exceptions.py new file mode 100644 index 0000000..796fdd2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_exceptions.py @@ -0,0 +1,33 @@ +from nose.tools import raises +import networkx as nx + +# smoke tests for exceptions + +@raises(nx.NetworkXException) +def test_raises_networkx_exception(): + raise nx.NetworkXException + +@raises(nx.NetworkXError) +def test_raises_networkx_error(): + raise nx.NetworkXError + +@raises(nx.NetworkXPointlessConcept) +def test_raises_networkx_pointless_concept(): + raise nx.NetworkXPointlessConcept + +@raises(nx.NetworkXAlgorithmError) +def test_raises_networkx_algorithm_error(): + raise nx.NetworkXAlgorithmError + +@raises(nx.NetworkXUnfeasible) +def test_raises_networkx_unfeasible(): + raise nx.NetworkXUnfeasible + +@raises(nx.NetworkXNoPath) +def test_raises_networkx_no_path(): + raise nx.NetworkXNoPath + +@raises(nx.NetworkXUnbounded) +def test_raises_networkx_unbounded(): + raise nx.NetworkXUnbounded + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_relabel.py b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_relabel.py new file mode 100644 index 0000000..e9dd2af --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/tests/test_relabel.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +from nose.tools import * +from networkx import * +from networkx.convert import * +from networkx.algorithms.operators import * +from networkx.generators.classic import barbell_graph,cycle_graph +from networkx.testing import * + +class TestRelabel(): + def test_convert_node_labels_to_integers(self): + # test that empty graph converts fine for all options + G=empty_graph() + H=convert_node_labels_to_integers(G,100) + assert_equal(H.name, '(empty_graph(0))_with_int_labels') + assert_equal(H.nodes(), []) + assert_equal(H.edges(), []) + + for opt in ["default", "sorted", "increasing degree", + "decreasing degree"]: + G=empty_graph() + H=convert_node_labels_to_integers(G,100, ordering=opt) + assert_equal(H.name, '(empty_graph(0))_with_int_labels') + assert_equal(H.nodes(), []) + assert_equal(H.edges(), []) + + G=empty_graph() + G.add_edges_from([('A','B'),('A','C'),('B','C'),('C','D')]) + G.name="paw" + H=convert_node_labels_to_integers(G) + degH=H.degree().values() + degG=G.degree().values() + assert_equal(sorted(degH), sorted(degG)) + + H=convert_node_labels_to_integers(G,1000) + degH=H.degree().values() + degG=G.degree().values() + assert_equal(sorted(degH), sorted(degG)) + assert_equal(H.nodes(), [1000, 1001, 1002, 1003]) + + H=convert_node_labels_to_integers(G,ordering="increasing degree") + degH=H.degree().values() + degG=G.degree().values() + assert_equal(sorted(degH), sorted(degG)) + assert_equal(degree(H,0), 1) + assert_equal(degree(H,1), 2) + assert_equal(degree(H,2), 2) + assert_equal(degree(H,3), 3) + + H=convert_node_labels_to_integers(G,ordering="decreasing degree") + degH=H.degree().values() + degG=G.degree().values() + assert_equal(sorted(degH), sorted(degG)) + assert_equal(degree(H,0), 3) + assert_equal(degree(H,1), 2) + assert_equal(degree(H,2), 2) + assert_equal(degree(H,3), 1) + + H=convert_node_labels_to_integers(G,ordering="increasing degree", + label_attribute='label') + degH=H.degree().values() + degG=G.degree().values() + assert_equal(sorted(degH), sorted(degG)) + assert_equal(degree(H,0), 1) + assert_equal(degree(H,1), 2) + assert_equal(degree(H,2), 2) + assert_equal(degree(H,3), 3) + + # check mapping + assert_equal(H.node[3]['label'],'C') + assert_equal(H.node[0]['label'],'D') + assert_true(H.node[1]['label']=='A' or H.node[2]['label']=='A') + assert_true(H.node[1]['label']=='B' or H.node[2]['label']=='B') + + def test_convert_to_integers2(self): + G=empty_graph() + G.add_edges_from([('C','D'),('A','B'),('A','C'),('B','C')]) + G.name="paw" + H=convert_node_labels_to_integers(G,ordering="sorted") + degH=H.degree().values() + degG=G.degree().values() + assert_equal(sorted(degH), sorted(degG)) + + H=convert_node_labels_to_integers(G,ordering="sorted", + label_attribute='label') + assert_equal(H.node[0]['label'],'A') + assert_equal(H.node[1]['label'],'B') + assert_equal(H.node[2]['label'],'C') + assert_equal(H.node[3]['label'],'D') + + @raises(nx.NetworkXError) + def test_convert_to_integers_raise(self): + G = nx.Graph() + H=convert_node_labels_to_integers(G,ordering="increasing age") + + + def test_relabel_nodes_copy(self): + G=empty_graph() + G.add_edges_from([('A','B'),('A','C'),('B','C'),('C','D')]) + mapping={'A':'aardvark','B':'bear','C':'cat','D':'dog'} + H=relabel_nodes(G,mapping) + assert_equal(sorted(H.nodes()), ['aardvark', 'bear', 'cat', 'dog']) + + def test_relabel_nodes_function(self): + G=empty_graph() + G.add_edges_from([('A','B'),('A','C'),('B','C'),('C','D')]) + # function mapping no longer encouraged but works + def mapping(n): + return ord(n) + H=relabel_nodes(G,mapping) + assert_equal(sorted(H.nodes()), [65, 66, 67, 68]) + + def test_relabel_nodes_graph(self): + G=Graph([('A','B'),('A','C'),('B','C'),('C','D')]) + mapping={'A':'aardvark','B':'bear','C':'cat','D':'dog'} + H=relabel_nodes(G,mapping) + assert_equal(sorted(H.nodes()), ['aardvark', 'bear', 'cat', 'dog']) + + def test_relabel_nodes_digraph(self): + G=DiGraph([('A','B'),('A','C'),('B','C'),('C','D')]) + mapping={'A':'aardvark','B':'bear','C':'cat','D':'dog'} + H=relabel_nodes(G,mapping,copy=False) + assert_equal(sorted(H.nodes()), ['aardvark', 'bear', 'cat', 'dog']) + + def test_relabel_nodes_multigraph(self): + G=MultiGraph([('a','b'),('a','b')]) + mapping={'a':'aardvark','b':'bear'} + G=relabel_nodes(G,mapping,copy=False) + assert_equal(sorted(G.nodes()), ['aardvark', 'bear']) + assert_edges_equal(sorted(G.edges()), + [('aardvark', 'bear'), ('aardvark', 'bear')]) + + def test_relabel_nodes_multidigraph(self): + G=MultiDiGraph([('a','b'),('a','b')]) + mapping={'a':'aardvark','b':'bear'} + G=relabel_nodes(G,mapping,copy=False) + assert_equal(sorted(G.nodes()), ['aardvark', 'bear']) + assert_equal(sorted(G.edges()), + [('aardvark', 'bear'), ('aardvark', 'bear')]) + + @raises(KeyError) + def test_relabel_nodes_missing(self): + G=Graph([('A','B'),('A','C'),('B','C'),('C','D')]) + mapping={0:'aardvark'} + G=relabel_nodes(G,mapping,copy=False) + + + def test_relabel_toposort(self): + K5=nx.complete_graph(4) + G=nx.complete_graph(4) + G=nx.relabel_nodes(G,dict( [(i,i+1) for i in range(4)]),copy=False) + nx.is_isomorphic(K5,G) + G=nx.complete_graph(4) + G=nx.relabel_nodes(G,dict( [(i,i-1) for i in range(4)]),copy=False) + nx.is_isomorphic(K5,G) + + + def test_relabel_selfloop(self): + G = nx.DiGraph([(1, 1), (1, 2), (2, 3)]) + G = nx.relabel_nodes(G, {1: 'One', 2: 'Two', 3: 'Three'}, copy=False) + assert_equal(sorted(G.nodes()),['One','Three','Two']) + G = nx.MultiDiGraph([(1, 1), (1, 2), (2, 3)]) + G = nx.relabel_nodes(G, {1: 'One', 2: 'Two', 3: 'Three'}, copy=False) + assert_equal(sorted(G.nodes()),['One','Three','Two']) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/__init__.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/__init__.py new file mode 100644 index 0000000..d443064 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/__init__.py @@ -0,0 +1,5 @@ +from networkx.utils.misc import * +from networkx.utils.decorators import * +from networkx.utils.random_sequence import * +from networkx.utils.union_find import * +from networkx.utils.rcm import * diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/decorators.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/decorators.py new file mode 100644 index 0000000..def1548 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/decorators.py @@ -0,0 +1,270 @@ +import sys + +from collections import defaultdict +from os.path import splitext + +import networkx as nx +from networkx.external.decorator import decorator +from networkx.utils import is_string_like + +def not_implemented_for(*graph_types): + """Decorator to mark algorithms as not implemented + + Parameters + ---------- + graph_types : container of strings + Entries must be one of 'directed','undirected', 'multigraph', 'graph'. + + Returns + ------- + _require : function + The decorated function. + + Raises + ------ + NetworkXNotImplemnted + If any of the packages cannot be imported + + Notes + ----- + Multiple types are joined logically with "and". + For "or" use multiple @not_implemented_for() lines. + + Examples + -------- + Decorate functions like this:: + + @not_implemnted_for('directed') + def sp_function(): + pass + + @not_implemnted_for('directed','multigraph') + def sp_np_function(): + pass + """ + @decorator + def _not_implemented_for(f,*args,**kwargs): + graph = args[0] + terms= {'directed':graph.is_directed(), + 'undirected':not graph.is_directed(), + 'multigraph':graph.is_multigraph(), + 'graph':not graph.is_multigraph()} + match = True + try: + for t in graph_types: + match = match and terms[t] + except KeyError: + raise KeyError('use one or more of ', + 'directed, undirected, multigraph, graph') + if match: + raise nx.NetworkXNotImplemented('not implemented for %s type'% + ' '.join(graph_types)) + else: + return f(*args,**kwargs) + return _not_implemented_for + + +def require(*packages): + """Decorator to check whether specific packages can be imported. + + If a package cannot be imported, then NetworkXError is raised. + If all packages can be imported, then the original function is called. + + Parameters + ---------- + packages : container of strings + Container of module names that will be imported. + + Returns + ------- + _require : function + The decorated function. + + Raises + ------ + NetworkXError + If any of the packages cannot be imported + + Examples + -------- + Decorate functions like this:: + + @require('scipy') + def sp_function(): + import scipy + pass + + @require('numpy','scipy') + def sp_np_function(): + import numpy + import scipy + pass + """ + @decorator + def _require(f,*args,**kwargs): + for package in reversed(packages): + try: + __import__(package) + except: + msg = "{0} requires {1}" + raise nx.NetworkXError( msg.format(f.__name__, package) ) + return f(*args,**kwargs) + return _require + + +def _open_gz(path, mode): + import gzip + return gzip.open(path,mode=mode) + +def _open_bz2(path, mode): + import bz2 + return bz2.BZ2File(path,mode=mode) + +# To handle new extensions, define a function accepting a `path` and `mode`. +# Then add the extension to _dispatch_dict. +_dispatch_dict = defaultdict(lambda : open) +_dispatch_dict['.gz'] = _open_gz +_dispatch_dict['.bz2'] = _open_bz2 +_dispatch_dict['.gzip'] = _open_gz + + +def open_file(path_arg, mode='r'): + """Decorator to ensure clean opening and closing of files. + + Parameters + ---------- + path_arg : int + Location of the path argument in args. Even if the argument is a + named positional argument (with a default value), you must specify its + index as a positional argument. + mode : str + String for opening mode. + + Returns + ------- + _open_file : function + Function which cleanly executes the io. + + Examples + -------- + Decorate functions like this:: + + @open_file(0,'r') + def read_function(pathname): + pass + + @open_file(1,'w') + def write_function(G,pathname): + pass + + @open_file(1,'w') + def write_function(G, pathname='graph.dot') + pass + + @open_file('path', 'w+') + def another_function(arg, **kwargs): + path = kwargs['path'] + pass + """ + # Note that this decorator solves the problem when a path argument is + # specified as a string, but it does not handle the situation when the + # function wants to accept a default of None (and then handle it). + # Here is an example: + # + # @open_file('path') + # def some_function(arg1, arg2, path=None): + # if path is None: + # fobj = tempfile.NamedTemporaryFile(delete=False) + # close_fobj = True + # else: + # # `path` could have been a string or file object or something + # # similar. In any event, the decorator has given us a file object + # # and it will close it for us, if it should. + # fobj = path + # close_fobj = False + # + # try: + # fobj.write('blah') + # finally: + # if close_fobj: + # fobj.close() + # + # Normally, we'd want to use "with" to ensure that fobj gets closed. + # However, recall that the decorator will make `path` a file object for + # us, and using "with" would undesirably close that file object. Instead, + # you use a try block, as shown above. When we exit the function, fobj will + # be closed, if it should be, by the decorator. + + @decorator + def _open_file(func, *args, **kwargs): + + # Note that since we have used @decorator, *args, and **kwargs have + # already been resolved to match the function signature of func. This + # means default values have been propagated. For example, the function + # func(x, y, a=1, b=2, **kwargs) if called as func(0,1,b=5,c=10) would + # have args=(0,1,1,5) and kwargs={'c':10}. + + # First we parse the arguments of the decorator. The path_arg could + # be an positional argument or a keyword argument. Even if it is + try: + # path_arg is a required positional argument + # This works precisely because we are using @decorator + path = args[path_arg] + except TypeError: + # path_arg is a keyword argument. It is "required" in the sense + # that it must exist, according to the decorator specification, + # It can exist in `kwargs` by a developer specified default value + # or it could have been explicitly set by the user. + try: + path = kwargs[path_arg] + except KeyError: + # Could not find the keyword. Thus, no default was specified + # in the function signature and the user did not provide it. + msg = 'Missing required keyword argument: {0}' + raise nx.NetworkXError(msg.format(path_arg)) + else: + is_kwarg = True + except IndexError: + # A "required" argument was missing. This can only happen if + # the decorator of the function was incorrectly specified. + # So this probably is not a user error, but a developer error. + msg = "path_arg of open_file decorator is incorrect" + raise nx.NetworkXError(msg) + else: + is_kwarg = False + + # Now we have the path_arg. There are two types of input to consider: + # 1) string representing a path that should be opened + # 2) an already opened file object + if is_string_like(path): + ext = splitext(path)[1] + fobj = _dispatch_dict[ext](path, mode=mode) + close_fobj = True + elif hasattr(path, 'read'): + # path is already a file-like object + fobj = path + close_fobj = False + else: + # could be None, in which case the algorithm will deal with it + fobj = path + close_fobj = False + + # Insert file object into args or kwargs. + if is_kwarg: + new_args = args + kwargs[path_arg] = fobj + else: + # args is a tuple, so we must convert to list before modifying it. + new_args = list(args) + new_args[path_arg] = fobj + + # Finally, we call the original function, making sure to close the fobj. + try: + result = func(*new_args, **kwargs) + finally: + if close_fobj: + fobj.close() + + return result + + return _open_file diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/misc.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/misc.py new file mode 100644 index 0000000..a942753 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/misc.py @@ -0,0 +1,151 @@ +""" +Miscellaneous Helpers for NetworkX. + +These are not imported into the base networkx namespace but +can be accessed, for example, as + +>>> import networkx +>>> networkx.utils.is_string_like('spam') +True +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import sys +import subprocess +import uuid + +import networkx as nx +from networkx.external.decorator import decorator + +__author__ = '\n'.join(['Aric Hagberg (hagberg@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)', + 'Ben Edwards(bedwards@cs.unm.edu)']) +### some cookbook stuff +# used in deciding whether something is a bunch of nodes, edges, etc. +# see G.add_nodes and others in Graph Class in networkx/base.py + +def is_string_like(obj): # from John Hunter, types-free version + """Check if obj is string.""" + try: + obj + '' + except (TypeError, ValueError): + return False + return True + +def iterable(obj): + """ Return True if obj is iterable with a well-defined len().""" + if hasattr(obj,"__iter__"): return True + try: + len(obj) + except: + return False + return True + +def flatten(obj, result=None): + """ Return flattened version of (possibly nested) iterable object. """ + if not iterable(obj) or is_string_like(obj): + return obj + if result is None: + result = [] + for item in obj: + if not iterable(item) or is_string_like(item): + result.append(item) + else: + flatten(item, result) + return obj.__class__(result) + +def is_list_of_ints( intlist ): + """ Return True if list is a list of ints. """ + if not isinstance(intlist,list): return False + for i in intlist: + if not isinstance(i,int): return False + return True + +def make_str(t): + """Return the string representation of t.""" + if is_string_like(t): return t + return str(t) + +def cumulative_sum(numbers): + """Yield cumulative sum of numbers. + + >>> import networkx.utils as utils + >>> list(utils.cumulative_sum([1,2,3,4])) + [1, 3, 6, 10] + """ + csum = 0 + for n in numbers: + csum += n + yield csum + +def generate_unique_node(): + """ Generate a unique node label.""" + return str(uuid.uuid1()) + +def default_opener(filename): + """Opens `filename` using system's default program. + + Parameters + ---------- + filename : str + The path of the file to be opened. + + """ + cmds = {'darwin': ['open'], + 'linux2': ['xdg-open'], + 'win32': ['cmd.exe', '/C', 'start', '']} + cmd = cmds[sys.platform] + [filename] + subprocess.call(cmd) + + +def dict_to_numpy_array(d,mapping=None): + """Convert a dictionary of dictionaries to a numpy array + with optional mapping.""" + try: + return dict_to_numpy_array2(d, mapping) + except AttributeError: + return dict_to_numpy_array1(d,mapping) + +def dict_to_numpy_array2(d,mapping=None): + """Convert a dictionary of dictionaries to a 2d numpy array + with optional mapping.""" + try: + import numpy + except ImportError: + raise ImportError( + "dict_to_numpy_array requires numpy : http://scipy.org/ ") + if mapping is None: + s=set(d.keys()) + for k,v in d.items(): + s.update(v.keys()) + mapping=dict(zip(s,range(len(s)))) + n=len(mapping) + a = numpy.zeros((n, n)) + for k1, row in d.items(): + for k2, value in row.items(): + i=mapping[k1] + j=mapping[k2] + a[i,j] = value + return a + +def dict_to_numpy_array1(d,mapping=None): + """Convert a dictionary of numbers to a 1d numpy array + with optional mapping.""" + try: + import numpy + except ImportError: + raise ImportError( + "dict_to_numpy_array requires numpy : http://scipy.org/ ") + if mapping is None: + s = set(d.keys()) + mapping = dict(zip(s,range(len(s)))) + n = len(mapping) + a = numpy.zeros(n) + for k1, value in d.items(): + i = mapping[k1] + a[i] = value + return a diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/random_sequence.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/random_sequence.py new file mode 100644 index 0000000..a2f947d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/random_sequence.py @@ -0,0 +1,222 @@ +""" +Utilities for generating random numbers, random sequences, and +random selections. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import random +import sys +import networkx as nx +__author__ = '\n'.join(['Aric Hagberg (hagberg@lanl.gov)', + 'Dan Schult(dschult@colgate.edu)', + 'Ben Edwards(bedwards@cs.unm.edu)']) + +def create_degree_sequence(n, sfunction=None, max_tries=50, **kwds): + """ Attempt to create a valid degree sequence of length n using + specified function sfunction(n,**kwds). + + Parameters + ---------- + n : int + Length of degree sequence = number of nodes + sfunction: function + Function which returns a list of n real or integer values. + Called as "sfunction(n,**kwds)". + max_tries: int + Max number of attempts at creating valid degree sequence. + + Notes + ----- + Repeatedly create a degree sequence by calling sfunction(n,**kwds) + until achieving a valid degree sequence. If unsuccessful after + max_tries attempts, raise an exception. + + For examples of sfunctions that return sequences of random numbers, + see networkx.Utils. + + Examples + -------- + >>> from networkx.utils import uniform_sequence, create_degree_sequence + >>> seq=create_degree_sequence(10,uniform_sequence) + """ + tries=0 + max_deg=n + while tries < max_tries: + trialseq=sfunction(n,**kwds) + # round to integer values in the range [0,max_deg] + seq=[min(max_deg, max( int(round(s)),0 )) for s in trialseq] + # if graphical return, else throw away and try again + if nx.is_valid_degree_sequence(seq): + return seq + tries+=1 + raise nx.NetworkXError(\ + "Exceeded max (%d) attempts at a valid sequence."%max_tries) + + +# The same helpers for choosing random sequences from distributions +# uses Python's random module +# http://www.python.org/doc/current/lib/module-random.html + +def pareto_sequence(n,exponent=1.0): + """ + Return sample sequence of length n from a Pareto distribution. + """ + return [random.paretovariate(exponent) for i in range(n)] + + +def powerlaw_sequence(n,exponent=2.0): + """ + Return sample sequence of length n from a power law distribution. + """ + return [random.paretovariate(exponent-1) for i in range(n)] + +def zipf_rv(alpha, xmin=1, seed=None): + r"""Return a random value chosen from the Zipf distribution. + + The return value is an integer drawn from the probability distribution + ::math:: + + p(x)=\frac{x^{-\alpha}}{\zeta(\alpha,x_{min})}, + + where `\zeta(\alpha,x_{min})` is the Hurwitz zeta function. + + Parameters + ---------- + alpha : float + Exponent value of the distribution + xmin : int + Minimum value + seed : int + Seed value for random number generator + + Returns + ------- + x : int + Random value from Zipf distribution + + Raises + ------ + ValueError: + If xmin < 1 or + If alpha <= 1 + + Notes + ----- + The rejection algorithm generates random values for a the power-law + distribution in uniformly bounded expected time dependent on + parameters. See [1] for details on its operation. + + Examples + -------- + >>> nx.zipf_rv(alpha=2, xmin=3, seed=42) # doctest: +SKIP + + References + ---------- + ..[1] Luc Devroye, Non-Uniform Random Variate Generation, + Springer-Verlag, New York, 1986. + """ + if xmin < 1: + raise ValueError("xmin < 1") + if alpha <= 1: + raise ValueError("a <= 1.0") + if not seed is None: + random.seed(seed) + a1 = alpha - 1.0 + b = 2**a1 + while True: + u = 1.0 - random.random() # u in (0,1] + v = random.random() # v in [0,1) + x = int(xmin*u**-(1.0/a1)) + t = (1.0+(1.0/x))**a1 + if v*x*(t-1.0)/(b-1.0) <= t/b: + break + return x + +def zipf_sequence(n, alpha=2.0, xmin=1): + """Return a sample sequence of length n from a Zipf distribution with + exponent parameter alpha and minimum value xmin. + + See Also + -------- + zipf_rv + """ + return [ zipf_rv(alpha,xmin) for _ in range(n)] + +def uniform_sequence(n): + """ + Return sample sequence of length n from a uniform distribution. + """ + return [ random.uniform(0,n) for i in range(n)] + + +def cumulative_distribution(distribution): + """Return normalized cumulative distribution from discrete distribution.""" + + cdf=[] + cdf.append(0.0) + psum=float(sum(distribution)) + for i in range(0,len(distribution)): + cdf.append(cdf[i]+distribution[i]/psum) + return cdf + + +def discrete_sequence(n, distribution=None, cdistribution=None): + """ + Return sample sequence of length n from a given discrete distribution + or discrete cumulative distribution. + + One of the following must be specified. + + distribution = histogram of values, will be normalized + + cdistribution = normalized discrete cumulative distribution + + """ + import bisect + + if cdistribution is not None: + cdf=cdistribution + elif distribution is not None: + cdf=cumulative_distribution(distribution) + else: + raise nx.NetworkXError( + "discrete_sequence: distribution or cdistribution missing") + + + # get a uniform random number + inputseq=[random.random() for i in range(n)] + + # choose from CDF + seq=[bisect.bisect_left(cdf,s)-1 for s in inputseq] + return seq + + +def random_weighted_sample(mapping, k): + """Return k items without replacement from a weighted sample. + + The input is a dictionary of items with weights as values. + """ + if k > len(mapping): + raise ValueError("sample larger than population") + sample = set() + while len(sample) < k: + sample.add(weighted_choice(mapping)) + return list(sample) + +def weighted_choice(mapping): + """Return a single element from a weighted sample. + + The input is a dictionary of items with weights as values. + """ + # use roulette method + rnd = random.random() * sum(mapping.values()) + for k, w in mapping.items(): + rnd -= w + if rnd < 0: + return k + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/rcm.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/rcm.py new file mode 100644 index 0000000..b21bd9b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/rcm.py @@ -0,0 +1,150 @@ +""" +Cuthill-McKee ordering of graph nodes to produce sparse matrices +""" +# Copyright (C) 2011 by +# Aric Hagberg +# All rights reserved. +# BSD license. +from operator import itemgetter +import networkx as nx +__author__ = """\n""".join(['Aric Hagberg ']) +__all__ = ['cuthill_mckee_ordering', + 'reverse_cuthill_mckee_ordering'] + +def cuthill_mckee_ordering(G, start=None): + """Generate an ordering (permutation) of the graph nodes to make + a sparse matrix. + + Uses the Cuthill-McKee heuristic (based on breadth-first search) [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + start : node, optional + Start algorithm and specified node. The node should be on the + periphery of the graph for best results. + + Returns + ------- + nodes : generator + Generator of nodes in Cuthill-McKee ordering. + + Examples + -------- + >>> from networkx.utils import cuthill_mckee_ordering + >>> G = nx.path_graph(4) + >>> rcm = list(cuthill_mckee_ordering(G)) + >>> A = nx.adjacency_matrix(G, nodelist=rcm) # doctest: +SKIP + + See Also + -------- + reverse_cuthill_mckee_ordering + + Notes + ----- + The optimal solution the the bandwidth reduction is NP-complete [2]_. + + References + ---------- + .. [1] E. Cuthill and J. McKee. + Reducing the bandwidth of sparse symmetric matrices, + In Proc. 24th Nat. Conf. ACM, pages 157-172, 1969. + http://doi.acm.org/10.1145/800195.805928 + .. [2] Steven S. Skiena. 1997. The Algorithm Design Manual. + Springer-Verlag New York, Inc., New York, NY, USA. + """ + for c in nx.connected_components(G): + for n in connected_cuthill_mckee_ordering(G.subgraph(c), start): + yield n + +def reverse_cuthill_mckee_ordering(G, start=None): + """Generate an ordering (permutation) of the graph nodes to make + a sparse matrix. + + Uses the reverse Cuthill-McKee heuristic (based on breadth-first search) + [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + start : node, optional + Start algorithm and specified node. The node should be on the + periphery of the graph for best results. + + Returns + ------- + nodes : generator + Generator of nodes in reverse Cuthill-McKee ordering. + + Examples + -------- + >>> from networkx.utils import reverse_cuthill_mckee_ordering + >>> G = nx.path_graph(4) + >>> rcm = list(reverse_cuthill_mckee_ordering(G)) + >>> A = nx.adjacency_matrix(G, nodelist=rcm) # doctest: +SKIP + + See Also + -------- + cuthill_mckee_ordering + + Notes + ----- + The optimal solution the the bandwidth reduction is NP-complete [2]_. + + References + ---------- + .. [1] E. Cuthill and J. McKee. + Reducing the bandwidth of sparse symmetric matrices, + In Proc. 24th Nat. Conf. ACM, pages 157-72, 1969. + http://doi.acm.org/10.1145/800195.805928 + .. [2] Steven S. Skiena. 1997. The Algorithm Design Manual. + Springer-Verlag New York, Inc., New York, NY, USA. + """ + return reversed(list(cuthill_mckee_ordering(G, start=start))) + +def connected_cuthill_mckee_ordering(G, start=None): + # the cuthill mckee algorithm for connected graphs + if start is None: + (_, start) = find_pseudo_peripheral_node_pair(G) + yield start + visited = set([start]) + stack = [(start, iter(G[start]))] + while stack: + parent,children = stack[0] + if parent not in visited: + yield parent + try: + child = next(children) + if child not in visited: + yield child + visited.add(child) + # add children to stack, sorted by degree (lowest first) + nd = sorted(G.degree(G[child]).items(), key=itemgetter(1)) + children = (n for n,d in nd) + stack.append((child,children)) + except StopIteration: + stack.pop(0) + +def find_pseudo_peripheral_node_pair(G, start=None): + # helper for cuthill-mckee to find a "pseudo peripheral pair" + # to use as good starting node + if start is None: + u = next(G.nodes_iter()) + else: + u = start + lp = 0 + v = u + while True: + spl = nx.shortest_path_length(G, v) + l = max(spl.values()) + if l <= lp: + break + lp = l + farthest = [n for n,dist in spl.items() if dist==l] + v, deg = sorted(G.degree(farthest).items(), key=itemgetter(1))[0] + return u, v + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_decorators.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_decorators.py new file mode 100644 index 0000000..964b2c9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_decorators.py @@ -0,0 +1,160 @@ +import tempfile +import os + +from nose.tools import * + +import networkx as nx +from networkx.utils.decorators import open_file,require,not_implemented_for + +def test_not_implemented_decorator(): + @not_implemented_for('directed') + def test1(G): + pass + test1(nx.Graph()) + +@raises(KeyError) +def test_not_implemented_decorator_key(): + @not_implemented_for('foo') + def test1(G): + pass + test1(nx.Graph()) + +@raises(nx.NetworkXNotImplemented) +def test_not_implemented_decorator_raise(): + @not_implemented_for('graph') + def test1(G): + pass + test1(nx.Graph()) + + +def test_require_decorator1(): + @require('os','sys') + def test1(): + import os + import sys + test1() + +def test_require_decorator2(): + @require('blahhh') + def test2(): + import blahhh + assert_raises(nx.NetworkXError, test2) + +class TestOpenFileDecorator(object): + def setUp(self): + self.text = ['Blah... ', 'BLAH ', 'BLAH!!!!'] + self.fobj = tempfile.NamedTemporaryFile('wb+', delete=False) + self.name = self.fobj.name + + def write(self, path): + for text in self.text: + path.write(text.encode('ascii')) + + @open_file(1, 'r') + def read(self, path): + return path.readlines()[0] + + @staticmethod + @open_file(0, 'wb') + def writer_arg0(path): + path.write('demo'.encode('ascii')) + + @open_file(1, 'wb+') + def writer_arg1(self, path): + self.write(path) + + @open_file(2, 'wb') + def writer_arg2default(self, x, path=None): + if path is None: + fh = tempfile.NamedTemporaryFile('wb+', delete=False) + close_fh = True + else: + fh = path + close_fh = False + + try: + self.write(fh) + finally: + if close_fh: + fh.close() + + @open_file(4, 'wb') + def writer_arg4default(self, x, y, other='hello', path=None, **kwargs): + if path is None: + fh = tempfile.NamedTemporaryFile('wb+', delete=False) + close_fh = True + else: + fh = path + close_fh = False + + try: + self.write(fh) + finally: + if close_fh: + fh.close() + + @open_file('path', 'wb') + def writer_kwarg(self, **kwargs): + path = kwargs.get('path', None) + if path is None: + fh = tempfile.NamedTemporaryFile('wb+', delete=False) + close_fh = True + else: + fh = path + close_fh = False + + try: + self.write(fh) + finally: + if close_fh: + fh.close() + + def test_writer_arg0_str(self): + self.writer_arg0(self.name) + + def test_writer_arg0_fobj(self): + self.writer_arg0(self.fobj) + + def test_writer_arg1_str(self): + self.writer_arg1(self.name) + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_arg1_fobj(self): + self.writer_arg1(self.fobj) + assert_false(self.fobj.closed) + self.fobj.close() + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_arg2default_str(self): + self.writer_arg2default(0, path=None) + self.writer_arg2default(0, path=self.name) + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_arg2default_fobj(self): + self.writer_arg2default(0, path=self.fobj) + assert_false(self.fobj.closed) + self.fobj.close() + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_arg2default_fobj(self): + self.writer_arg2default(0, path=None) + + def test_writer_arg4default_fobj(self): + self.writer_arg4default(0, 1, dog='dog', other='other2') + self.writer_arg4default(0, 1, dog='dog', other='other2', path=self.name) + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_kwarg_str(self): + self.writer_kwarg(path=self.name) + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_kwarg_fobj(self): + self.writer_kwarg(path=self.fobj) + self.fobj.close() + assert_equal( self.read(self.name), ''.join(self.text) ) + + def test_writer_kwarg_fobj(self): + self.writer_kwarg(path=None) + + def tearDown(self): + self.fobj.close() diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_misc.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_misc.py new file mode 100644 index 0000000..77b8196 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_misc.py @@ -0,0 +1,72 @@ +from nose.tools import * +from nose import SkipTest +import networkx as nx +from networkx.utils import * + +def test_is_string_like(): + assert_true(is_string_like("aaaa")) + assert_false(is_string_like(None)) + assert_false(is_string_like(123)) + +def test_iterable(): + assert_false(iterable(None)) + assert_false(iterable(10)) + assert_true(iterable([1,2,3])) + assert_true(iterable((1,2,3))) + assert_true(iterable({1:"A",2:"X"})) + assert_true(iterable("ABC")) + +def test_graph_iterable(): + K=nx.complete_graph(10) + assert_true(iterable(K)) + assert_true(iterable(K.nodes_iter())) + assert_true(iterable(K.edges_iter())) + +def test_is_list_of_ints(): + assert_true(is_list_of_ints([1,2,3,42])) + assert_false(is_list_of_ints([1,2,3,"kermit"])) + +def test_random_number_distribution(): + # smoke test only + z=uniform_sequence(20) + z=powerlaw_sequence(20,exponent=2.5) + z=pareto_sequence(20,exponent=1.5) + z=discrete_sequence(20,distribution=[0,0,0,0,1,1,1,1,2,2,3]) + +class TestNumpyArray(object): + numpy=1 # nosetests attribute, use nosetests -a 'not numpy' to skip test + @classmethod + def setupClass(cls): + global numpy + global assert_equal + global assert_almost_equal + try: + import numpy + from numpy.testing import assert_equal,assert_almost_equal + except ImportError: + raise SkipTest('NumPy not available.') + + def test_dict_to_numpy_array1(self): + d = {'a':1,'b':2} + a = dict_to_numpy_array1(d) + assert_equal(a, numpy.array([1,2])) + a = dict_to_numpy_array1(d, mapping = {'b':0,'a':1}) + assert_equal(a, numpy.array([2,1])) + + def test_dict_to_numpy_array2(self): + d = {'a': {'a':1,'b':2}, + 'b': {'a':10,'b':20}} + a = dict_to_numpy_array(d) + assert_equal(a, numpy.array([[1,2],[10,20]])) + a = dict_to_numpy_array2(d, mapping = {'b':0,'a':1}) + assert_equal(a, numpy.array([[20,10],[2,1]])) + + + def test_dict_to_numpy_array(self): + d = {'a': {'a':1,'b':2}, + 'b': {'a':10,'b':20}} + a = dict_to_numpy_array(d) + assert_equal(a, numpy.array([[1,2],[10,20]])) + d = {'a':1,'b':2} + a = dict_to_numpy_array1(d) + assert_equal(a, numpy.array([1,2])) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_random_sequence.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_random_sequence.py new file mode 100644 index 0000000..0c3634a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_random_sequence.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +from nose.tools import * +from networkx.utils import uniform_sequence,powerlaw_sequence,\ + create_degree_sequence,zipf_rv,zipf_sequence,random_weighted_sample,\ + weighted_choice +import networkx.utils + +def test_degree_sequences(): + seq=create_degree_sequence(10,uniform_sequence) + assert_equal(len(seq), 10) + seq=create_degree_sequence(10,powerlaw_sequence) + assert_equal(len(seq), 10) + +def test_zipf_rv(): + r = zipf_rv(2.3) + assert_true(type(r),int) + assert_raises(ValueError,zipf_rv,0.5) + assert_raises(ValueError,zipf_rv,2,xmin=0) + +def test_zipf_sequence(): + s = zipf_sequence(10) + assert_equal(len(s),10) + +def test_random_weighted_sample(): + mapping={'a':10,'b':20} + s = random_weighted_sample(mapping,2) + assert_equal(sorted(s),sorted(mapping.keys())) + assert_raises(ValueError,random_weighted_sample,mapping,3) + +def test_random_weighted_choice(): + mapping={'a':10,'b':0} + c = weighted_choice(mapping) + assert_equal(c,'a') diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_rcm.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_rcm.py new file mode 100644 index 0000000..f267586 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/tests/test_rcm.py @@ -0,0 +1,13 @@ +from nose.tools import * +from networkx.utils import reverse_cuthill_mckee_ordering +import networkx as nx + +def test_reverse_cuthill_mckee(): + # example graph from + # http://www.boost.org/doc/libs/1_37_0/libs/graph/example/cuthill_mckee_ordering.cpp + G = nx.Graph([(0,3),(0,5),(1,2),(1,4),(1,6),(1,9),(2,3), + (2,4),(3,5),(3,8),(4,6),(5,6),(5,7),(6,7)]) + rcm = list(reverse_cuthill_mckee_ordering(G,start=0)) + assert_equal(rcm,[9, 1, 4, 6, 7, 2, 8, 5, 3, 0]) + rcm = list(reverse_cuthill_mckee_ordering(G)) + assert_equal(rcm,[0, 8, 5, 7, 3, 6, 4, 2, 1, 9]) diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/utils/union_find.py b/lib/python2.7/site-packages/setoolsgui/networkx/utils/union_find.py new file mode 100644 index 0000000..d05dd92 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/utils/union_find.py @@ -0,0 +1,75 @@ +""" +Union-find data structure. +""" +# Copyright (C) 2004-2011 by +# Aric Hagberg +# Dan Schult +# Pieter Swart +# All rights reserved. +# BSD license. +import networkx as nx + +class UnionFind: + """Union-find data structure. + + Each unionFind instance X maintains a family of disjoint sets of + hashable objects, supporting the following two methods: + + - X[item] returns a name for the set containing the given item. + Each set is named by an arbitrarily-chosen one of its members; as + long as the set remains unchanged it will keep the same name. If + the item is not yet part of a set in X, a new singleton set is + created for it. + + - X.union(item1, item2, ...) merges the sets containing each item + into a single larger set. If any item is not yet part of a set + in X, it is added to X as one of the members of the merged set. + + Union-find data structure. Based on Josiah Carlson's code, + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/215912 + with significant additional changes by D. Eppstein. + http://www.ics.uci.edu/~eppstein/PADS/UnionFind.py + + """ + + def __init__(self): + """Create a new empty union-find structure.""" + self.weights = {} + self.parents = {} + + def __getitem__(self, object): + """Find and return the name of the set containing the object.""" + + # check for previously unknown object + if object not in self.parents: + self.parents[object] = object + self.weights[object] = 1 + return object + + # find path of objects leading to the root + path = [object] + root = self.parents[object] + while root != path[-1]: + path.append(root) + root = self.parents[root] + + # compress the path and return + for ancestor in path: + self.parents[ancestor] = root + return root + + def __iter__(self): + """Iterate through all items ever found or unioned by this structure.""" + return iter(self.parents) + + def union(self, *objects): + """Find the sets containing the objects and merge them all.""" + roots = [self[x] for x in objects] + heaviest = max([(self.weights[r],r) for r in roots])[1] + for r in roots: + if r != heaviest: + self.weights[heaviest] += self.weights[r] + self.parents[r] = heaviest + + + diff --git a/lib/python2.7/site-packages/setoolsgui/networkx/version.py b/lib/python2.7/site-packages/setoolsgui/networkx/version.py new file mode 100644 index 0000000..7369f8b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/networkx/version.py @@ -0,0 +1,25 @@ +""" +Version information for NetworkX, created during installation. + +Do not add this file to the repository. + +""" + +import datetime + +version = '1.8.1' +date = 'Sun Aug 4 07:56:54 2013' + +# Was NetworkX built from a development version? If so, remember that the major +# and minor versions reference the "target" (rather than "current") release. +dev = False + +# Format: (name, major, min, revision) +version_info = ('networkx', '1', '8.1', None) + +# Format: a 'datetime.datetime' instance +date_info = datetime.datetime(2013, 8, 4, 7, 56, 54, 416491) + +# Format: (vcs, vcs_tuple) +vcs_info = (None, (None, None)) + diff --git a/lib/python2.7/site-packages/setoolsgui/selinux/__init__.py b/lib/python2.7/site-packages/setoolsgui/selinux/__init__.py new file mode 100644 index 0000000..b81b031 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/selinux/__init__.py @@ -0,0 +1,2445 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 2.0.11 +# +# Do not make changes to this file unless you know what you are doing--modify +# the SWIG interface file instead. + + + + + +from sys import version_info +if version_info >= (2,6,0): + def swig_import_helper(): + from os.path import dirname + import imp + fp = None + try: + fp, pathname, description = imp.find_module('_selinux', [dirname(__file__)]) + except ImportError: + import _selinux + return _selinux + if fp is not None: + try: + _mod = imp.load_module('_selinux', fp, pathname, description) + finally: + fp.close() + return _mod + _selinux = swig_import_helper() + del swig_import_helper +else: + import _selinux +del version_info +try: + _swig_property = property +except NameError: + pass # Python < 2.2 doesn't have 'property'. +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'SwigPyObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError(name) + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +try: + _object = object + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 + + +import shutil, os, stat + +DISABLED = -1 +PERMISSIVE = 0 +ENFORCING = 1 + +def restorecon(path, recursive=False): + """ Restore SELinux context on a given path """ + + try: + mode = os.lstat(path)[stat.ST_MODE] + status, context = matchpathcon(path, mode) + except OSError: + path = os.path.realpath(os.path.expanduser(path)) + mode = os.lstat(path)[stat.ST_MODE] + status, context = matchpathcon(path, mode) + + if status == 0: + status, oldcontext = lgetfilecon(path) + if context != oldcontext: + lsetfilecon(path, context) + + if recursive: + for root, dirs, files in os.walk(path): + for name in files + dirs: + restorecon(os.path.join(root, name)) + +def chcon(path, context, recursive=False): + """ Set the SELinux context on a given path """ + lsetfilecon(path, context) + if recursive: + for root, dirs, files in os.walk(path): + for name in files + dirs: + lsetfilecon(os.path.join(root,name), context) + +def copytree(src, dest): + """ An SELinux-friendly shutil.copytree method """ + shutil.copytree(src, dest) + restorecon(dest, recursive=True) + +def install(src, dest): + """ An SELinux-friendly shutil.move method """ + shutil.move(src, dest) + restorecon(dest, recursive=True) + +class security_id(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, security_id, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, security_id, name) + __repr__ = _swig_repr + __swig_setmethods__["ctx"] = _selinux.security_id_ctx_set + __swig_getmethods__["ctx"] = _selinux.security_id_ctx_get + if _newclass:ctx = _swig_property(_selinux.security_id_ctx_get, _selinux.security_id_ctx_set) + __swig_setmethods__["refcnt"] = _selinux.security_id_refcnt_set + __swig_getmethods__["refcnt"] = _selinux.security_id_refcnt_get + if _newclass:refcnt = _swig_property(_selinux.security_id_refcnt_get, _selinux.security_id_refcnt_set) + def __init__(self): + this = _selinux.new_security_id() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_security_id + __del__ = lambda self : None; +security_id_swigregister = _selinux.security_id_swigregister +security_id_swigregister(security_id) + + +def avc_sid_to_context(*args): + return _selinux.avc_sid_to_context(*args) +avc_sid_to_context = _selinux.avc_sid_to_context + +def avc_sid_to_context_raw(*args): + return _selinux.avc_sid_to_context_raw(*args) +avc_sid_to_context_raw = _selinux.avc_sid_to_context_raw + +def avc_context_to_sid(*args): + return _selinux.avc_context_to_sid(*args) +avc_context_to_sid = _selinux.avc_context_to_sid + +def avc_context_to_sid_raw(*args): + return _selinux.avc_context_to_sid_raw(*args) +avc_context_to_sid_raw = _selinux.avc_context_to_sid_raw + +def sidget(*args): + return _selinux.sidget(*args) +sidget = _selinux.sidget + +def sidput(*args): + return _selinux.sidput(*args) +sidput = _selinux.sidput + +def avc_get_initial_sid(*args): + return _selinux.avc_get_initial_sid(*args) +avc_get_initial_sid = _selinux.avc_get_initial_sid +class avc_entry_ref(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_entry_ref, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_entry_ref, name) + __repr__ = _swig_repr + __swig_setmethods__["ae"] = _selinux.avc_entry_ref_ae_set + __swig_getmethods__["ae"] = _selinux.avc_entry_ref_ae_get + if _newclass:ae = _swig_property(_selinux.avc_entry_ref_ae_get, _selinux.avc_entry_ref_ae_set) + def __init__(self): + this = _selinux.new_avc_entry_ref() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_entry_ref + __del__ = lambda self : None; +avc_entry_ref_swigregister = _selinux.avc_entry_ref_swigregister +avc_entry_ref_swigregister(avc_entry_ref) + +class avc_memory_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_memory_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_memory_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_malloc"] = _selinux.avc_memory_callback_func_malloc_set + __swig_getmethods__["func_malloc"] = _selinux.avc_memory_callback_func_malloc_get + if _newclass:func_malloc = _swig_property(_selinux.avc_memory_callback_func_malloc_get, _selinux.avc_memory_callback_func_malloc_set) + __swig_setmethods__["func_free"] = _selinux.avc_memory_callback_func_free_set + __swig_getmethods__["func_free"] = _selinux.avc_memory_callback_func_free_get + if _newclass:func_free = _swig_property(_selinux.avc_memory_callback_func_free_get, _selinux.avc_memory_callback_func_free_set) + def __init__(self): + this = _selinux.new_avc_memory_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_memory_callback + __del__ = lambda self : None; +avc_memory_callback_swigregister = _selinux.avc_memory_callback_swigregister +avc_memory_callback_swigregister(avc_memory_callback) + +class avc_log_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_log_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_log_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_log"] = _selinux.avc_log_callback_func_log_set + __swig_getmethods__["func_log"] = _selinux.avc_log_callback_func_log_get + if _newclass:func_log = _swig_property(_selinux.avc_log_callback_func_log_get, _selinux.avc_log_callback_func_log_set) + __swig_setmethods__["func_audit"] = _selinux.avc_log_callback_func_audit_set + __swig_getmethods__["func_audit"] = _selinux.avc_log_callback_func_audit_get + if _newclass:func_audit = _swig_property(_selinux.avc_log_callback_func_audit_get, _selinux.avc_log_callback_func_audit_set) + def __init__(self): + this = _selinux.new_avc_log_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_log_callback + __del__ = lambda self : None; +avc_log_callback_swigregister = _selinux.avc_log_callback_swigregister +avc_log_callback_swigregister(avc_log_callback) + +class avc_thread_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_thread_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_thread_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_create_thread"] = _selinux.avc_thread_callback_func_create_thread_set + __swig_getmethods__["func_create_thread"] = _selinux.avc_thread_callback_func_create_thread_get + if _newclass:func_create_thread = _swig_property(_selinux.avc_thread_callback_func_create_thread_get, _selinux.avc_thread_callback_func_create_thread_set) + __swig_setmethods__["func_stop_thread"] = _selinux.avc_thread_callback_func_stop_thread_set + __swig_getmethods__["func_stop_thread"] = _selinux.avc_thread_callback_func_stop_thread_get + if _newclass:func_stop_thread = _swig_property(_selinux.avc_thread_callback_func_stop_thread_get, _selinux.avc_thread_callback_func_stop_thread_set) + def __init__(self): + this = _selinux.new_avc_thread_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_thread_callback + __del__ = lambda self : None; +avc_thread_callback_swigregister = _selinux.avc_thread_callback_swigregister +avc_thread_callback_swigregister(avc_thread_callback) + +class avc_lock_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_lock_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_lock_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_alloc_lock"] = _selinux.avc_lock_callback_func_alloc_lock_set + __swig_getmethods__["func_alloc_lock"] = _selinux.avc_lock_callback_func_alloc_lock_get + if _newclass:func_alloc_lock = _swig_property(_selinux.avc_lock_callback_func_alloc_lock_get, _selinux.avc_lock_callback_func_alloc_lock_set) + __swig_setmethods__["func_get_lock"] = _selinux.avc_lock_callback_func_get_lock_set + __swig_getmethods__["func_get_lock"] = _selinux.avc_lock_callback_func_get_lock_get + if _newclass:func_get_lock = _swig_property(_selinux.avc_lock_callback_func_get_lock_get, _selinux.avc_lock_callback_func_get_lock_set) + __swig_setmethods__["func_release_lock"] = _selinux.avc_lock_callback_func_release_lock_set + __swig_getmethods__["func_release_lock"] = _selinux.avc_lock_callback_func_release_lock_get + if _newclass:func_release_lock = _swig_property(_selinux.avc_lock_callback_func_release_lock_get, _selinux.avc_lock_callback_func_release_lock_set) + __swig_setmethods__["func_free_lock"] = _selinux.avc_lock_callback_func_free_lock_set + __swig_getmethods__["func_free_lock"] = _selinux.avc_lock_callback_func_free_lock_get + if _newclass:func_free_lock = _swig_property(_selinux.avc_lock_callback_func_free_lock_get, _selinux.avc_lock_callback_func_free_lock_set) + def __init__(self): + this = _selinux.new_avc_lock_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_lock_callback + __del__ = lambda self : None; +avc_lock_callback_swigregister = _selinux.avc_lock_callback_swigregister +avc_lock_callback_swigregister(avc_lock_callback) + +AVC_OPT_UNUSED = _selinux.AVC_OPT_UNUSED +AVC_OPT_SETENFORCE = _selinux.AVC_OPT_SETENFORCE + +def avc_init(*args): + return _selinux.avc_init(*args) +avc_init = _selinux.avc_init + +def avc_open(*args): + return _selinux.avc_open(*args) +avc_open = _selinux.avc_open + +def avc_cleanup(): + return _selinux.avc_cleanup() +avc_cleanup = _selinux.avc_cleanup + +def avc_reset(): + return _selinux.avc_reset() +avc_reset = _selinux.avc_reset + +def avc_destroy(): + return _selinux.avc_destroy() +avc_destroy = _selinux.avc_destroy + +def avc_has_perm_noaudit(*args): + return _selinux.avc_has_perm_noaudit(*args) +avc_has_perm_noaudit = _selinux.avc_has_perm_noaudit + +def avc_has_perm(*args): + return _selinux.avc_has_perm(*args) +avc_has_perm = _selinux.avc_has_perm + +def avc_audit(*args): + return _selinux.avc_audit(*args) +avc_audit = _selinux.avc_audit + +def avc_compute_create(*args): + return _selinux.avc_compute_create(*args) +avc_compute_create = _selinux.avc_compute_create + +def avc_compute_member(*args): + return _selinux.avc_compute_member(*args) +avc_compute_member = _selinux.avc_compute_member +AVC_CALLBACK_GRANT = _selinux.AVC_CALLBACK_GRANT +AVC_CALLBACK_TRY_REVOKE = _selinux.AVC_CALLBACK_TRY_REVOKE +AVC_CALLBACK_REVOKE = _selinux.AVC_CALLBACK_REVOKE +AVC_CALLBACK_RESET = _selinux.AVC_CALLBACK_RESET +AVC_CALLBACK_AUDITALLOW_ENABLE = _selinux.AVC_CALLBACK_AUDITALLOW_ENABLE +AVC_CALLBACK_AUDITALLOW_DISABLE = _selinux.AVC_CALLBACK_AUDITALLOW_DISABLE +AVC_CALLBACK_AUDITDENY_ENABLE = _selinux.AVC_CALLBACK_AUDITDENY_ENABLE +AVC_CALLBACK_AUDITDENY_DISABLE = _selinux.AVC_CALLBACK_AUDITDENY_DISABLE +AVC_CACHE_STATS = _selinux.AVC_CACHE_STATS +class avc_cache_stats(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, avc_cache_stats, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, avc_cache_stats, name) + __repr__ = _swig_repr + __swig_setmethods__["entry_lookups"] = _selinux.avc_cache_stats_entry_lookups_set + __swig_getmethods__["entry_lookups"] = _selinux.avc_cache_stats_entry_lookups_get + if _newclass:entry_lookups = _swig_property(_selinux.avc_cache_stats_entry_lookups_get, _selinux.avc_cache_stats_entry_lookups_set) + __swig_setmethods__["entry_hits"] = _selinux.avc_cache_stats_entry_hits_set + __swig_getmethods__["entry_hits"] = _selinux.avc_cache_stats_entry_hits_get + if _newclass:entry_hits = _swig_property(_selinux.avc_cache_stats_entry_hits_get, _selinux.avc_cache_stats_entry_hits_set) + __swig_setmethods__["entry_misses"] = _selinux.avc_cache_stats_entry_misses_set + __swig_getmethods__["entry_misses"] = _selinux.avc_cache_stats_entry_misses_get + if _newclass:entry_misses = _swig_property(_selinux.avc_cache_stats_entry_misses_get, _selinux.avc_cache_stats_entry_misses_set) + __swig_setmethods__["entry_discards"] = _selinux.avc_cache_stats_entry_discards_set + __swig_getmethods__["entry_discards"] = _selinux.avc_cache_stats_entry_discards_get + if _newclass:entry_discards = _swig_property(_selinux.avc_cache_stats_entry_discards_get, _selinux.avc_cache_stats_entry_discards_set) + __swig_setmethods__["cav_lookups"] = _selinux.avc_cache_stats_cav_lookups_set + __swig_getmethods__["cav_lookups"] = _selinux.avc_cache_stats_cav_lookups_get + if _newclass:cav_lookups = _swig_property(_selinux.avc_cache_stats_cav_lookups_get, _selinux.avc_cache_stats_cav_lookups_set) + __swig_setmethods__["cav_hits"] = _selinux.avc_cache_stats_cav_hits_set + __swig_getmethods__["cav_hits"] = _selinux.avc_cache_stats_cav_hits_get + if _newclass:cav_hits = _swig_property(_selinux.avc_cache_stats_cav_hits_get, _selinux.avc_cache_stats_cav_hits_set) + __swig_setmethods__["cav_probes"] = _selinux.avc_cache_stats_cav_probes_set + __swig_getmethods__["cav_probes"] = _selinux.avc_cache_stats_cav_probes_get + if _newclass:cav_probes = _swig_property(_selinux.avc_cache_stats_cav_probes_get, _selinux.avc_cache_stats_cav_probes_set) + __swig_setmethods__["cav_misses"] = _selinux.avc_cache_stats_cav_misses_set + __swig_getmethods__["cav_misses"] = _selinux.avc_cache_stats_cav_misses_get + if _newclass:cav_misses = _swig_property(_selinux.avc_cache_stats_cav_misses_get, _selinux.avc_cache_stats_cav_misses_set) + def __init__(self): + this = _selinux.new_avc_cache_stats() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_avc_cache_stats + __del__ = lambda self : None; +avc_cache_stats_swigregister = _selinux.avc_cache_stats_swigregister +avc_cache_stats_swigregister(avc_cache_stats) + + +def avc_av_stats(): + return _selinux.avc_av_stats() +avc_av_stats = _selinux.avc_av_stats + +def avc_sid_stats(): + return _selinux.avc_sid_stats() +avc_sid_stats = _selinux.avc_sid_stats + +def avc_netlink_open(*args): + return _selinux.avc_netlink_open(*args) +avc_netlink_open = _selinux.avc_netlink_open + +def avc_netlink_loop(): + return _selinux.avc_netlink_loop() +avc_netlink_loop = _selinux.avc_netlink_loop + +def avc_netlink_close(): + return _selinux.avc_netlink_close() +avc_netlink_close = _selinux.avc_netlink_close + +def selinux_status_open(*args): + return _selinux.selinux_status_open(*args) +selinux_status_open = _selinux.selinux_status_open + +def selinux_status_close(): + return _selinux.selinux_status_close() +selinux_status_close = _selinux.selinux_status_close + +def selinux_status_updated(): + return _selinux.selinux_status_updated() +selinux_status_updated = _selinux.selinux_status_updated + +def selinux_status_getenforce(): + return _selinux.selinux_status_getenforce() +selinux_status_getenforce = _selinux.selinux_status_getenforce + +def selinux_status_policyload(): + return _selinux.selinux_status_policyload() +selinux_status_policyload = _selinux.selinux_status_policyload + +def selinux_status_deny_unknown(): + return _selinux.selinux_status_deny_unknown() +selinux_status_deny_unknown = _selinux.selinux_status_deny_unknown +COMMON_FILE__IOCTL = _selinux.COMMON_FILE__IOCTL +COMMON_FILE__READ = _selinux.COMMON_FILE__READ +COMMON_FILE__WRITE = _selinux.COMMON_FILE__WRITE +COMMON_FILE__CREATE = _selinux.COMMON_FILE__CREATE +COMMON_FILE__GETATTR = _selinux.COMMON_FILE__GETATTR +COMMON_FILE__SETATTR = _selinux.COMMON_FILE__SETATTR +COMMON_FILE__LOCK = _selinux.COMMON_FILE__LOCK +COMMON_FILE__RELABELFROM = _selinux.COMMON_FILE__RELABELFROM +COMMON_FILE__RELABELTO = _selinux.COMMON_FILE__RELABELTO +COMMON_FILE__APPEND = _selinux.COMMON_FILE__APPEND +COMMON_FILE__UNLINK = _selinux.COMMON_FILE__UNLINK +COMMON_FILE__LINK = _selinux.COMMON_FILE__LINK +COMMON_FILE__RENAME = _selinux.COMMON_FILE__RENAME +COMMON_FILE__EXECUTE = _selinux.COMMON_FILE__EXECUTE +COMMON_FILE__SWAPON = _selinux.COMMON_FILE__SWAPON +COMMON_FILE__QUOTAON = _selinux.COMMON_FILE__QUOTAON +COMMON_FILE__MOUNTON = _selinux.COMMON_FILE__MOUNTON +COMMON_SOCKET__IOCTL = _selinux.COMMON_SOCKET__IOCTL +COMMON_SOCKET__READ = _selinux.COMMON_SOCKET__READ +COMMON_SOCKET__WRITE = _selinux.COMMON_SOCKET__WRITE +COMMON_SOCKET__CREATE = _selinux.COMMON_SOCKET__CREATE +COMMON_SOCKET__GETATTR = _selinux.COMMON_SOCKET__GETATTR +COMMON_SOCKET__SETATTR = _selinux.COMMON_SOCKET__SETATTR +COMMON_SOCKET__LOCK = _selinux.COMMON_SOCKET__LOCK +COMMON_SOCKET__RELABELFROM = _selinux.COMMON_SOCKET__RELABELFROM +COMMON_SOCKET__RELABELTO = _selinux.COMMON_SOCKET__RELABELTO +COMMON_SOCKET__APPEND = _selinux.COMMON_SOCKET__APPEND +COMMON_SOCKET__BIND = _selinux.COMMON_SOCKET__BIND +COMMON_SOCKET__CONNECT = _selinux.COMMON_SOCKET__CONNECT +COMMON_SOCKET__LISTEN = _selinux.COMMON_SOCKET__LISTEN +COMMON_SOCKET__ACCEPT = _selinux.COMMON_SOCKET__ACCEPT +COMMON_SOCKET__GETOPT = _selinux.COMMON_SOCKET__GETOPT +COMMON_SOCKET__SETOPT = _selinux.COMMON_SOCKET__SETOPT +COMMON_SOCKET__SHUTDOWN = _selinux.COMMON_SOCKET__SHUTDOWN +COMMON_SOCKET__RECVFROM = _selinux.COMMON_SOCKET__RECVFROM +COMMON_SOCKET__SENDTO = _selinux.COMMON_SOCKET__SENDTO +COMMON_SOCKET__RECV_MSG = _selinux.COMMON_SOCKET__RECV_MSG +COMMON_SOCKET__SEND_MSG = _selinux.COMMON_SOCKET__SEND_MSG +COMMON_SOCKET__NAME_BIND = _selinux.COMMON_SOCKET__NAME_BIND +COMMON_IPC__CREATE = _selinux.COMMON_IPC__CREATE +COMMON_IPC__DESTROY = _selinux.COMMON_IPC__DESTROY +COMMON_IPC__GETATTR = _selinux.COMMON_IPC__GETATTR +COMMON_IPC__SETATTR = _selinux.COMMON_IPC__SETATTR +COMMON_IPC__READ = _selinux.COMMON_IPC__READ +COMMON_IPC__WRITE = _selinux.COMMON_IPC__WRITE +COMMON_IPC__ASSOCIATE = _selinux.COMMON_IPC__ASSOCIATE +COMMON_IPC__UNIX_READ = _selinux.COMMON_IPC__UNIX_READ +COMMON_IPC__UNIX_WRITE = _selinux.COMMON_IPC__UNIX_WRITE +COMMON_DATABASE__CREATE = _selinux.COMMON_DATABASE__CREATE +COMMON_DATABASE__DROP = _selinux.COMMON_DATABASE__DROP +COMMON_DATABASE__GETATTR = _selinux.COMMON_DATABASE__GETATTR +COMMON_DATABASE__SETATTR = _selinux.COMMON_DATABASE__SETATTR +COMMON_DATABASE__RELABELFROM = _selinux.COMMON_DATABASE__RELABELFROM +COMMON_DATABASE__RELABELTO = _selinux.COMMON_DATABASE__RELABELTO +FILESYSTEM__MOUNT = _selinux.FILESYSTEM__MOUNT +FILESYSTEM__REMOUNT = _selinux.FILESYSTEM__REMOUNT +FILESYSTEM__UNMOUNT = _selinux.FILESYSTEM__UNMOUNT +FILESYSTEM__GETATTR = _selinux.FILESYSTEM__GETATTR +FILESYSTEM__RELABELFROM = _selinux.FILESYSTEM__RELABELFROM +FILESYSTEM__RELABELTO = _selinux.FILESYSTEM__RELABELTO +FILESYSTEM__TRANSITION = _selinux.FILESYSTEM__TRANSITION +FILESYSTEM__ASSOCIATE = _selinux.FILESYSTEM__ASSOCIATE +FILESYSTEM__QUOTAMOD = _selinux.FILESYSTEM__QUOTAMOD +FILESYSTEM__QUOTAGET = _selinux.FILESYSTEM__QUOTAGET +DIR__IOCTL = _selinux.DIR__IOCTL +DIR__READ = _selinux.DIR__READ +DIR__WRITE = _selinux.DIR__WRITE +DIR__CREATE = _selinux.DIR__CREATE +DIR__GETATTR = _selinux.DIR__GETATTR +DIR__SETATTR = _selinux.DIR__SETATTR +DIR__LOCK = _selinux.DIR__LOCK +DIR__RELABELFROM = _selinux.DIR__RELABELFROM +DIR__RELABELTO = _selinux.DIR__RELABELTO +DIR__APPEND = _selinux.DIR__APPEND +DIR__UNLINK = _selinux.DIR__UNLINK +DIR__LINK = _selinux.DIR__LINK +DIR__RENAME = _selinux.DIR__RENAME +DIR__EXECUTE = _selinux.DIR__EXECUTE +DIR__SWAPON = _selinux.DIR__SWAPON +DIR__QUOTAON = _selinux.DIR__QUOTAON +DIR__MOUNTON = _selinux.DIR__MOUNTON +DIR__ADD_NAME = _selinux.DIR__ADD_NAME +DIR__REMOVE_NAME = _selinux.DIR__REMOVE_NAME +DIR__REPARENT = _selinux.DIR__REPARENT +DIR__SEARCH = _selinux.DIR__SEARCH +DIR__RMDIR = _selinux.DIR__RMDIR +DIR__OPEN = _selinux.DIR__OPEN +FILE__IOCTL = _selinux.FILE__IOCTL +FILE__READ = _selinux.FILE__READ +FILE__WRITE = _selinux.FILE__WRITE +FILE__CREATE = _selinux.FILE__CREATE +FILE__GETATTR = _selinux.FILE__GETATTR +FILE__SETATTR = _selinux.FILE__SETATTR +FILE__LOCK = _selinux.FILE__LOCK +FILE__RELABELFROM = _selinux.FILE__RELABELFROM +FILE__RELABELTO = _selinux.FILE__RELABELTO +FILE__APPEND = _selinux.FILE__APPEND +FILE__UNLINK = _selinux.FILE__UNLINK +FILE__LINK = _selinux.FILE__LINK +FILE__RENAME = _selinux.FILE__RENAME +FILE__EXECUTE = _selinux.FILE__EXECUTE +FILE__SWAPON = _selinux.FILE__SWAPON +FILE__QUOTAON = _selinux.FILE__QUOTAON +FILE__MOUNTON = _selinux.FILE__MOUNTON +FILE__EXECUTE_NO_TRANS = _selinux.FILE__EXECUTE_NO_TRANS +FILE__ENTRYPOINT = _selinux.FILE__ENTRYPOINT +FILE__EXECMOD = _selinux.FILE__EXECMOD +FILE__OPEN = _selinux.FILE__OPEN +LNK_FILE__IOCTL = _selinux.LNK_FILE__IOCTL +LNK_FILE__READ = _selinux.LNK_FILE__READ +LNK_FILE__WRITE = _selinux.LNK_FILE__WRITE +LNK_FILE__CREATE = _selinux.LNK_FILE__CREATE +LNK_FILE__GETATTR = _selinux.LNK_FILE__GETATTR +LNK_FILE__SETATTR = _selinux.LNK_FILE__SETATTR +LNK_FILE__LOCK = _selinux.LNK_FILE__LOCK +LNK_FILE__RELABELFROM = _selinux.LNK_FILE__RELABELFROM +LNK_FILE__RELABELTO = _selinux.LNK_FILE__RELABELTO +LNK_FILE__APPEND = _selinux.LNK_FILE__APPEND +LNK_FILE__UNLINK = _selinux.LNK_FILE__UNLINK +LNK_FILE__LINK = _selinux.LNK_FILE__LINK +LNK_FILE__RENAME = _selinux.LNK_FILE__RENAME +LNK_FILE__EXECUTE = _selinux.LNK_FILE__EXECUTE +LNK_FILE__SWAPON = _selinux.LNK_FILE__SWAPON +LNK_FILE__QUOTAON = _selinux.LNK_FILE__QUOTAON +LNK_FILE__MOUNTON = _selinux.LNK_FILE__MOUNTON +CHR_FILE__IOCTL = _selinux.CHR_FILE__IOCTL +CHR_FILE__READ = _selinux.CHR_FILE__READ +CHR_FILE__WRITE = _selinux.CHR_FILE__WRITE +CHR_FILE__CREATE = _selinux.CHR_FILE__CREATE +CHR_FILE__GETATTR = _selinux.CHR_FILE__GETATTR +CHR_FILE__SETATTR = _selinux.CHR_FILE__SETATTR +CHR_FILE__LOCK = _selinux.CHR_FILE__LOCK +CHR_FILE__RELABELFROM = _selinux.CHR_FILE__RELABELFROM +CHR_FILE__RELABELTO = _selinux.CHR_FILE__RELABELTO +CHR_FILE__APPEND = _selinux.CHR_FILE__APPEND +CHR_FILE__UNLINK = _selinux.CHR_FILE__UNLINK +CHR_FILE__LINK = _selinux.CHR_FILE__LINK +CHR_FILE__RENAME = _selinux.CHR_FILE__RENAME +CHR_FILE__EXECUTE = _selinux.CHR_FILE__EXECUTE +CHR_FILE__SWAPON = _selinux.CHR_FILE__SWAPON +CHR_FILE__QUOTAON = _selinux.CHR_FILE__QUOTAON +CHR_FILE__MOUNTON = _selinux.CHR_FILE__MOUNTON +CHR_FILE__EXECUTE_NO_TRANS = _selinux.CHR_FILE__EXECUTE_NO_TRANS +CHR_FILE__ENTRYPOINT = _selinux.CHR_FILE__ENTRYPOINT +CHR_FILE__EXECMOD = _selinux.CHR_FILE__EXECMOD +CHR_FILE__OPEN = _selinux.CHR_FILE__OPEN +BLK_FILE__IOCTL = _selinux.BLK_FILE__IOCTL +BLK_FILE__READ = _selinux.BLK_FILE__READ +BLK_FILE__WRITE = _selinux.BLK_FILE__WRITE +BLK_FILE__CREATE = _selinux.BLK_FILE__CREATE +BLK_FILE__GETATTR = _selinux.BLK_FILE__GETATTR +BLK_FILE__SETATTR = _selinux.BLK_FILE__SETATTR +BLK_FILE__LOCK = _selinux.BLK_FILE__LOCK +BLK_FILE__RELABELFROM = _selinux.BLK_FILE__RELABELFROM +BLK_FILE__RELABELTO = _selinux.BLK_FILE__RELABELTO +BLK_FILE__APPEND = _selinux.BLK_FILE__APPEND +BLK_FILE__UNLINK = _selinux.BLK_FILE__UNLINK +BLK_FILE__LINK = _selinux.BLK_FILE__LINK +BLK_FILE__RENAME = _selinux.BLK_FILE__RENAME +BLK_FILE__EXECUTE = _selinux.BLK_FILE__EXECUTE +BLK_FILE__SWAPON = _selinux.BLK_FILE__SWAPON +BLK_FILE__QUOTAON = _selinux.BLK_FILE__QUOTAON +BLK_FILE__MOUNTON = _selinux.BLK_FILE__MOUNTON +BLK_FILE__OPEN = _selinux.BLK_FILE__OPEN +SOCK_FILE__IOCTL = _selinux.SOCK_FILE__IOCTL +SOCK_FILE__READ = _selinux.SOCK_FILE__READ +SOCK_FILE__WRITE = _selinux.SOCK_FILE__WRITE +SOCK_FILE__CREATE = _selinux.SOCK_FILE__CREATE +SOCK_FILE__GETATTR = _selinux.SOCK_FILE__GETATTR +SOCK_FILE__SETATTR = _selinux.SOCK_FILE__SETATTR +SOCK_FILE__LOCK = _selinux.SOCK_FILE__LOCK +SOCK_FILE__RELABELFROM = _selinux.SOCK_FILE__RELABELFROM +SOCK_FILE__RELABELTO = _selinux.SOCK_FILE__RELABELTO +SOCK_FILE__APPEND = _selinux.SOCK_FILE__APPEND +SOCK_FILE__UNLINK = _selinux.SOCK_FILE__UNLINK +SOCK_FILE__LINK = _selinux.SOCK_FILE__LINK +SOCK_FILE__RENAME = _selinux.SOCK_FILE__RENAME +SOCK_FILE__EXECUTE = _selinux.SOCK_FILE__EXECUTE +SOCK_FILE__SWAPON = _selinux.SOCK_FILE__SWAPON +SOCK_FILE__QUOTAON = _selinux.SOCK_FILE__QUOTAON +SOCK_FILE__MOUNTON = _selinux.SOCK_FILE__MOUNTON +FIFO_FILE__IOCTL = _selinux.FIFO_FILE__IOCTL +FIFO_FILE__READ = _selinux.FIFO_FILE__READ +FIFO_FILE__WRITE = _selinux.FIFO_FILE__WRITE +FIFO_FILE__CREATE = _selinux.FIFO_FILE__CREATE +FIFO_FILE__GETATTR = _selinux.FIFO_FILE__GETATTR +FIFO_FILE__SETATTR = _selinux.FIFO_FILE__SETATTR +FIFO_FILE__LOCK = _selinux.FIFO_FILE__LOCK +FIFO_FILE__RELABELFROM = _selinux.FIFO_FILE__RELABELFROM +FIFO_FILE__RELABELTO = _selinux.FIFO_FILE__RELABELTO +FIFO_FILE__APPEND = _selinux.FIFO_FILE__APPEND +FIFO_FILE__UNLINK = _selinux.FIFO_FILE__UNLINK +FIFO_FILE__LINK = _selinux.FIFO_FILE__LINK +FIFO_FILE__RENAME = _selinux.FIFO_FILE__RENAME +FIFO_FILE__EXECUTE = _selinux.FIFO_FILE__EXECUTE +FIFO_FILE__SWAPON = _selinux.FIFO_FILE__SWAPON +FIFO_FILE__QUOTAON = _selinux.FIFO_FILE__QUOTAON +FIFO_FILE__MOUNTON = _selinux.FIFO_FILE__MOUNTON +FIFO_FILE__OPEN = _selinux.FIFO_FILE__OPEN +FD__USE = _selinux.FD__USE +SOCKET__IOCTL = _selinux.SOCKET__IOCTL +SOCKET__READ = _selinux.SOCKET__READ +SOCKET__WRITE = _selinux.SOCKET__WRITE +SOCKET__CREATE = _selinux.SOCKET__CREATE +SOCKET__GETATTR = _selinux.SOCKET__GETATTR +SOCKET__SETATTR = _selinux.SOCKET__SETATTR +SOCKET__LOCK = _selinux.SOCKET__LOCK +SOCKET__RELABELFROM = _selinux.SOCKET__RELABELFROM +SOCKET__RELABELTO = _selinux.SOCKET__RELABELTO +SOCKET__APPEND = _selinux.SOCKET__APPEND +SOCKET__BIND = _selinux.SOCKET__BIND +SOCKET__CONNECT = _selinux.SOCKET__CONNECT +SOCKET__LISTEN = _selinux.SOCKET__LISTEN +SOCKET__ACCEPT = _selinux.SOCKET__ACCEPT +SOCKET__GETOPT = _selinux.SOCKET__GETOPT +SOCKET__SETOPT = _selinux.SOCKET__SETOPT +SOCKET__SHUTDOWN = _selinux.SOCKET__SHUTDOWN +SOCKET__RECVFROM = _selinux.SOCKET__RECVFROM +SOCKET__SENDTO = _selinux.SOCKET__SENDTO +SOCKET__RECV_MSG = _selinux.SOCKET__RECV_MSG +SOCKET__SEND_MSG = _selinux.SOCKET__SEND_MSG +SOCKET__NAME_BIND = _selinux.SOCKET__NAME_BIND +TCP_SOCKET__IOCTL = _selinux.TCP_SOCKET__IOCTL +TCP_SOCKET__READ = _selinux.TCP_SOCKET__READ +TCP_SOCKET__WRITE = _selinux.TCP_SOCKET__WRITE +TCP_SOCKET__CREATE = _selinux.TCP_SOCKET__CREATE +TCP_SOCKET__GETATTR = _selinux.TCP_SOCKET__GETATTR +TCP_SOCKET__SETATTR = _selinux.TCP_SOCKET__SETATTR +TCP_SOCKET__LOCK = _selinux.TCP_SOCKET__LOCK +TCP_SOCKET__RELABELFROM = _selinux.TCP_SOCKET__RELABELFROM +TCP_SOCKET__RELABELTO = _selinux.TCP_SOCKET__RELABELTO +TCP_SOCKET__APPEND = _selinux.TCP_SOCKET__APPEND +TCP_SOCKET__BIND = _selinux.TCP_SOCKET__BIND +TCP_SOCKET__CONNECT = _selinux.TCP_SOCKET__CONNECT +TCP_SOCKET__LISTEN = _selinux.TCP_SOCKET__LISTEN +TCP_SOCKET__ACCEPT = _selinux.TCP_SOCKET__ACCEPT +TCP_SOCKET__GETOPT = _selinux.TCP_SOCKET__GETOPT +TCP_SOCKET__SETOPT = _selinux.TCP_SOCKET__SETOPT +TCP_SOCKET__SHUTDOWN = _selinux.TCP_SOCKET__SHUTDOWN +TCP_SOCKET__RECVFROM = _selinux.TCP_SOCKET__RECVFROM +TCP_SOCKET__SENDTO = _selinux.TCP_SOCKET__SENDTO +TCP_SOCKET__RECV_MSG = _selinux.TCP_SOCKET__RECV_MSG +TCP_SOCKET__SEND_MSG = _selinux.TCP_SOCKET__SEND_MSG +TCP_SOCKET__NAME_BIND = _selinux.TCP_SOCKET__NAME_BIND +TCP_SOCKET__CONNECTTO = _selinux.TCP_SOCKET__CONNECTTO +TCP_SOCKET__NEWCONN = _selinux.TCP_SOCKET__NEWCONN +TCP_SOCKET__ACCEPTFROM = _selinux.TCP_SOCKET__ACCEPTFROM +TCP_SOCKET__NODE_BIND = _selinux.TCP_SOCKET__NODE_BIND +TCP_SOCKET__NAME_CONNECT = _selinux.TCP_SOCKET__NAME_CONNECT +UDP_SOCKET__IOCTL = _selinux.UDP_SOCKET__IOCTL +UDP_SOCKET__READ = _selinux.UDP_SOCKET__READ +UDP_SOCKET__WRITE = _selinux.UDP_SOCKET__WRITE +UDP_SOCKET__CREATE = _selinux.UDP_SOCKET__CREATE +UDP_SOCKET__GETATTR = _selinux.UDP_SOCKET__GETATTR +UDP_SOCKET__SETATTR = _selinux.UDP_SOCKET__SETATTR +UDP_SOCKET__LOCK = _selinux.UDP_SOCKET__LOCK +UDP_SOCKET__RELABELFROM = _selinux.UDP_SOCKET__RELABELFROM +UDP_SOCKET__RELABELTO = _selinux.UDP_SOCKET__RELABELTO +UDP_SOCKET__APPEND = _selinux.UDP_SOCKET__APPEND +UDP_SOCKET__BIND = _selinux.UDP_SOCKET__BIND +UDP_SOCKET__CONNECT = _selinux.UDP_SOCKET__CONNECT +UDP_SOCKET__LISTEN = _selinux.UDP_SOCKET__LISTEN +UDP_SOCKET__ACCEPT = _selinux.UDP_SOCKET__ACCEPT +UDP_SOCKET__GETOPT = _selinux.UDP_SOCKET__GETOPT +UDP_SOCKET__SETOPT = _selinux.UDP_SOCKET__SETOPT +UDP_SOCKET__SHUTDOWN = _selinux.UDP_SOCKET__SHUTDOWN +UDP_SOCKET__RECVFROM = _selinux.UDP_SOCKET__RECVFROM +UDP_SOCKET__SENDTO = _selinux.UDP_SOCKET__SENDTO +UDP_SOCKET__RECV_MSG = _selinux.UDP_SOCKET__RECV_MSG +UDP_SOCKET__SEND_MSG = _selinux.UDP_SOCKET__SEND_MSG +UDP_SOCKET__NAME_BIND = _selinux.UDP_SOCKET__NAME_BIND +UDP_SOCKET__NODE_BIND = _selinux.UDP_SOCKET__NODE_BIND +RAWIP_SOCKET__IOCTL = _selinux.RAWIP_SOCKET__IOCTL +RAWIP_SOCKET__READ = _selinux.RAWIP_SOCKET__READ +RAWIP_SOCKET__WRITE = _selinux.RAWIP_SOCKET__WRITE +RAWIP_SOCKET__CREATE = _selinux.RAWIP_SOCKET__CREATE +RAWIP_SOCKET__GETATTR = _selinux.RAWIP_SOCKET__GETATTR +RAWIP_SOCKET__SETATTR = _selinux.RAWIP_SOCKET__SETATTR +RAWIP_SOCKET__LOCK = _selinux.RAWIP_SOCKET__LOCK +RAWIP_SOCKET__RELABELFROM = _selinux.RAWIP_SOCKET__RELABELFROM +RAWIP_SOCKET__RELABELTO = _selinux.RAWIP_SOCKET__RELABELTO +RAWIP_SOCKET__APPEND = _selinux.RAWIP_SOCKET__APPEND +RAWIP_SOCKET__BIND = _selinux.RAWIP_SOCKET__BIND +RAWIP_SOCKET__CONNECT = _selinux.RAWIP_SOCKET__CONNECT +RAWIP_SOCKET__LISTEN = _selinux.RAWIP_SOCKET__LISTEN +RAWIP_SOCKET__ACCEPT = _selinux.RAWIP_SOCKET__ACCEPT +RAWIP_SOCKET__GETOPT = _selinux.RAWIP_SOCKET__GETOPT +RAWIP_SOCKET__SETOPT = _selinux.RAWIP_SOCKET__SETOPT +RAWIP_SOCKET__SHUTDOWN = _selinux.RAWIP_SOCKET__SHUTDOWN +RAWIP_SOCKET__RECVFROM = _selinux.RAWIP_SOCKET__RECVFROM +RAWIP_SOCKET__SENDTO = _selinux.RAWIP_SOCKET__SENDTO +RAWIP_SOCKET__RECV_MSG = _selinux.RAWIP_SOCKET__RECV_MSG +RAWIP_SOCKET__SEND_MSG = _selinux.RAWIP_SOCKET__SEND_MSG +RAWIP_SOCKET__NAME_BIND = _selinux.RAWIP_SOCKET__NAME_BIND +RAWIP_SOCKET__NODE_BIND = _selinux.RAWIP_SOCKET__NODE_BIND +NODE__TCP_RECV = _selinux.NODE__TCP_RECV +NODE__TCP_SEND = _selinux.NODE__TCP_SEND +NODE__UDP_RECV = _selinux.NODE__UDP_RECV +NODE__UDP_SEND = _selinux.NODE__UDP_SEND +NODE__RAWIP_RECV = _selinux.NODE__RAWIP_RECV +NODE__RAWIP_SEND = _selinux.NODE__RAWIP_SEND +NODE__ENFORCE_DEST = _selinux.NODE__ENFORCE_DEST +NODE__DCCP_RECV = _selinux.NODE__DCCP_RECV +NODE__DCCP_SEND = _selinux.NODE__DCCP_SEND +NODE__RECVFROM = _selinux.NODE__RECVFROM +NODE__SENDTO = _selinux.NODE__SENDTO +NETIF__TCP_RECV = _selinux.NETIF__TCP_RECV +NETIF__TCP_SEND = _selinux.NETIF__TCP_SEND +NETIF__UDP_RECV = _selinux.NETIF__UDP_RECV +NETIF__UDP_SEND = _selinux.NETIF__UDP_SEND +NETIF__RAWIP_RECV = _selinux.NETIF__RAWIP_RECV +NETIF__RAWIP_SEND = _selinux.NETIF__RAWIP_SEND +NETIF__DCCP_RECV = _selinux.NETIF__DCCP_RECV +NETIF__DCCP_SEND = _selinux.NETIF__DCCP_SEND +NETIF__INGRESS = _selinux.NETIF__INGRESS +NETIF__EGRESS = _selinux.NETIF__EGRESS +NETLINK_SOCKET__IOCTL = _selinux.NETLINK_SOCKET__IOCTL +NETLINK_SOCKET__READ = _selinux.NETLINK_SOCKET__READ +NETLINK_SOCKET__WRITE = _selinux.NETLINK_SOCKET__WRITE +NETLINK_SOCKET__CREATE = _selinux.NETLINK_SOCKET__CREATE +NETLINK_SOCKET__GETATTR = _selinux.NETLINK_SOCKET__GETATTR +NETLINK_SOCKET__SETATTR = _selinux.NETLINK_SOCKET__SETATTR +NETLINK_SOCKET__LOCK = _selinux.NETLINK_SOCKET__LOCK +NETLINK_SOCKET__RELABELFROM = _selinux.NETLINK_SOCKET__RELABELFROM +NETLINK_SOCKET__RELABELTO = _selinux.NETLINK_SOCKET__RELABELTO +NETLINK_SOCKET__APPEND = _selinux.NETLINK_SOCKET__APPEND +NETLINK_SOCKET__BIND = _selinux.NETLINK_SOCKET__BIND +NETLINK_SOCKET__CONNECT = _selinux.NETLINK_SOCKET__CONNECT +NETLINK_SOCKET__LISTEN = _selinux.NETLINK_SOCKET__LISTEN +NETLINK_SOCKET__ACCEPT = _selinux.NETLINK_SOCKET__ACCEPT +NETLINK_SOCKET__GETOPT = _selinux.NETLINK_SOCKET__GETOPT +NETLINK_SOCKET__SETOPT = _selinux.NETLINK_SOCKET__SETOPT +NETLINK_SOCKET__SHUTDOWN = _selinux.NETLINK_SOCKET__SHUTDOWN +NETLINK_SOCKET__RECVFROM = _selinux.NETLINK_SOCKET__RECVFROM +NETLINK_SOCKET__SENDTO = _selinux.NETLINK_SOCKET__SENDTO +NETLINK_SOCKET__RECV_MSG = _selinux.NETLINK_SOCKET__RECV_MSG +NETLINK_SOCKET__SEND_MSG = _selinux.NETLINK_SOCKET__SEND_MSG +NETLINK_SOCKET__NAME_BIND = _selinux.NETLINK_SOCKET__NAME_BIND +PACKET_SOCKET__IOCTL = _selinux.PACKET_SOCKET__IOCTL +PACKET_SOCKET__READ = _selinux.PACKET_SOCKET__READ +PACKET_SOCKET__WRITE = _selinux.PACKET_SOCKET__WRITE +PACKET_SOCKET__CREATE = _selinux.PACKET_SOCKET__CREATE +PACKET_SOCKET__GETATTR = _selinux.PACKET_SOCKET__GETATTR +PACKET_SOCKET__SETATTR = _selinux.PACKET_SOCKET__SETATTR +PACKET_SOCKET__LOCK = _selinux.PACKET_SOCKET__LOCK +PACKET_SOCKET__RELABELFROM = _selinux.PACKET_SOCKET__RELABELFROM +PACKET_SOCKET__RELABELTO = _selinux.PACKET_SOCKET__RELABELTO +PACKET_SOCKET__APPEND = _selinux.PACKET_SOCKET__APPEND +PACKET_SOCKET__BIND = _selinux.PACKET_SOCKET__BIND +PACKET_SOCKET__CONNECT = _selinux.PACKET_SOCKET__CONNECT +PACKET_SOCKET__LISTEN = _selinux.PACKET_SOCKET__LISTEN +PACKET_SOCKET__ACCEPT = _selinux.PACKET_SOCKET__ACCEPT +PACKET_SOCKET__GETOPT = _selinux.PACKET_SOCKET__GETOPT +PACKET_SOCKET__SETOPT = _selinux.PACKET_SOCKET__SETOPT +PACKET_SOCKET__SHUTDOWN = _selinux.PACKET_SOCKET__SHUTDOWN +PACKET_SOCKET__RECVFROM = _selinux.PACKET_SOCKET__RECVFROM +PACKET_SOCKET__SENDTO = _selinux.PACKET_SOCKET__SENDTO +PACKET_SOCKET__RECV_MSG = _selinux.PACKET_SOCKET__RECV_MSG +PACKET_SOCKET__SEND_MSG = _selinux.PACKET_SOCKET__SEND_MSG +PACKET_SOCKET__NAME_BIND = _selinux.PACKET_SOCKET__NAME_BIND +KEY_SOCKET__IOCTL = _selinux.KEY_SOCKET__IOCTL +KEY_SOCKET__READ = _selinux.KEY_SOCKET__READ +KEY_SOCKET__WRITE = _selinux.KEY_SOCKET__WRITE +KEY_SOCKET__CREATE = _selinux.KEY_SOCKET__CREATE +KEY_SOCKET__GETATTR = _selinux.KEY_SOCKET__GETATTR +KEY_SOCKET__SETATTR = _selinux.KEY_SOCKET__SETATTR +KEY_SOCKET__LOCK = _selinux.KEY_SOCKET__LOCK +KEY_SOCKET__RELABELFROM = _selinux.KEY_SOCKET__RELABELFROM +KEY_SOCKET__RELABELTO = _selinux.KEY_SOCKET__RELABELTO +KEY_SOCKET__APPEND = _selinux.KEY_SOCKET__APPEND +KEY_SOCKET__BIND = _selinux.KEY_SOCKET__BIND +KEY_SOCKET__CONNECT = _selinux.KEY_SOCKET__CONNECT +KEY_SOCKET__LISTEN = _selinux.KEY_SOCKET__LISTEN +KEY_SOCKET__ACCEPT = _selinux.KEY_SOCKET__ACCEPT +KEY_SOCKET__GETOPT = _selinux.KEY_SOCKET__GETOPT +KEY_SOCKET__SETOPT = _selinux.KEY_SOCKET__SETOPT +KEY_SOCKET__SHUTDOWN = _selinux.KEY_SOCKET__SHUTDOWN +KEY_SOCKET__RECVFROM = _selinux.KEY_SOCKET__RECVFROM +KEY_SOCKET__SENDTO = _selinux.KEY_SOCKET__SENDTO +KEY_SOCKET__RECV_MSG = _selinux.KEY_SOCKET__RECV_MSG +KEY_SOCKET__SEND_MSG = _selinux.KEY_SOCKET__SEND_MSG +KEY_SOCKET__NAME_BIND = _selinux.KEY_SOCKET__NAME_BIND +UNIX_STREAM_SOCKET__IOCTL = _selinux.UNIX_STREAM_SOCKET__IOCTL +UNIX_STREAM_SOCKET__READ = _selinux.UNIX_STREAM_SOCKET__READ +UNIX_STREAM_SOCKET__WRITE = _selinux.UNIX_STREAM_SOCKET__WRITE +UNIX_STREAM_SOCKET__CREATE = _selinux.UNIX_STREAM_SOCKET__CREATE +UNIX_STREAM_SOCKET__GETATTR = _selinux.UNIX_STREAM_SOCKET__GETATTR +UNIX_STREAM_SOCKET__SETATTR = _selinux.UNIX_STREAM_SOCKET__SETATTR +UNIX_STREAM_SOCKET__LOCK = _selinux.UNIX_STREAM_SOCKET__LOCK +UNIX_STREAM_SOCKET__RELABELFROM = _selinux.UNIX_STREAM_SOCKET__RELABELFROM +UNIX_STREAM_SOCKET__RELABELTO = _selinux.UNIX_STREAM_SOCKET__RELABELTO +UNIX_STREAM_SOCKET__APPEND = _selinux.UNIX_STREAM_SOCKET__APPEND +UNIX_STREAM_SOCKET__BIND = _selinux.UNIX_STREAM_SOCKET__BIND +UNIX_STREAM_SOCKET__CONNECT = _selinux.UNIX_STREAM_SOCKET__CONNECT +UNIX_STREAM_SOCKET__LISTEN = _selinux.UNIX_STREAM_SOCKET__LISTEN +UNIX_STREAM_SOCKET__ACCEPT = _selinux.UNIX_STREAM_SOCKET__ACCEPT +UNIX_STREAM_SOCKET__GETOPT = _selinux.UNIX_STREAM_SOCKET__GETOPT +UNIX_STREAM_SOCKET__SETOPT = _selinux.UNIX_STREAM_SOCKET__SETOPT +UNIX_STREAM_SOCKET__SHUTDOWN = _selinux.UNIX_STREAM_SOCKET__SHUTDOWN +UNIX_STREAM_SOCKET__RECVFROM = _selinux.UNIX_STREAM_SOCKET__RECVFROM +UNIX_STREAM_SOCKET__SENDTO = _selinux.UNIX_STREAM_SOCKET__SENDTO +UNIX_STREAM_SOCKET__RECV_MSG = _selinux.UNIX_STREAM_SOCKET__RECV_MSG +UNIX_STREAM_SOCKET__SEND_MSG = _selinux.UNIX_STREAM_SOCKET__SEND_MSG +UNIX_STREAM_SOCKET__NAME_BIND = _selinux.UNIX_STREAM_SOCKET__NAME_BIND +UNIX_STREAM_SOCKET__CONNECTTO = _selinux.UNIX_STREAM_SOCKET__CONNECTTO +UNIX_STREAM_SOCKET__NEWCONN = _selinux.UNIX_STREAM_SOCKET__NEWCONN +UNIX_STREAM_SOCKET__ACCEPTFROM = _selinux.UNIX_STREAM_SOCKET__ACCEPTFROM +UNIX_DGRAM_SOCKET__IOCTL = _selinux.UNIX_DGRAM_SOCKET__IOCTL +UNIX_DGRAM_SOCKET__READ = _selinux.UNIX_DGRAM_SOCKET__READ +UNIX_DGRAM_SOCKET__WRITE = _selinux.UNIX_DGRAM_SOCKET__WRITE +UNIX_DGRAM_SOCKET__CREATE = _selinux.UNIX_DGRAM_SOCKET__CREATE +UNIX_DGRAM_SOCKET__GETATTR = _selinux.UNIX_DGRAM_SOCKET__GETATTR +UNIX_DGRAM_SOCKET__SETATTR = _selinux.UNIX_DGRAM_SOCKET__SETATTR +UNIX_DGRAM_SOCKET__LOCK = _selinux.UNIX_DGRAM_SOCKET__LOCK +UNIX_DGRAM_SOCKET__RELABELFROM = _selinux.UNIX_DGRAM_SOCKET__RELABELFROM +UNIX_DGRAM_SOCKET__RELABELTO = _selinux.UNIX_DGRAM_SOCKET__RELABELTO +UNIX_DGRAM_SOCKET__APPEND = _selinux.UNIX_DGRAM_SOCKET__APPEND +UNIX_DGRAM_SOCKET__BIND = _selinux.UNIX_DGRAM_SOCKET__BIND +UNIX_DGRAM_SOCKET__CONNECT = _selinux.UNIX_DGRAM_SOCKET__CONNECT +UNIX_DGRAM_SOCKET__LISTEN = _selinux.UNIX_DGRAM_SOCKET__LISTEN +UNIX_DGRAM_SOCKET__ACCEPT = _selinux.UNIX_DGRAM_SOCKET__ACCEPT +UNIX_DGRAM_SOCKET__GETOPT = _selinux.UNIX_DGRAM_SOCKET__GETOPT +UNIX_DGRAM_SOCKET__SETOPT = _selinux.UNIX_DGRAM_SOCKET__SETOPT +UNIX_DGRAM_SOCKET__SHUTDOWN = _selinux.UNIX_DGRAM_SOCKET__SHUTDOWN +UNIX_DGRAM_SOCKET__RECVFROM = _selinux.UNIX_DGRAM_SOCKET__RECVFROM +UNIX_DGRAM_SOCKET__SENDTO = _selinux.UNIX_DGRAM_SOCKET__SENDTO +UNIX_DGRAM_SOCKET__RECV_MSG = _selinux.UNIX_DGRAM_SOCKET__RECV_MSG +UNIX_DGRAM_SOCKET__SEND_MSG = _selinux.UNIX_DGRAM_SOCKET__SEND_MSG +UNIX_DGRAM_SOCKET__NAME_BIND = _selinux.UNIX_DGRAM_SOCKET__NAME_BIND +PROCESS__FORK = _selinux.PROCESS__FORK +PROCESS__TRANSITION = _selinux.PROCESS__TRANSITION +PROCESS__SIGCHLD = _selinux.PROCESS__SIGCHLD +PROCESS__SIGKILL = _selinux.PROCESS__SIGKILL +PROCESS__SIGSTOP = _selinux.PROCESS__SIGSTOP +PROCESS__SIGNULL = _selinux.PROCESS__SIGNULL +PROCESS__SIGNAL = _selinux.PROCESS__SIGNAL +PROCESS__PTRACE = _selinux.PROCESS__PTRACE +PROCESS__GETSCHED = _selinux.PROCESS__GETSCHED +PROCESS__SETSCHED = _selinux.PROCESS__SETSCHED +PROCESS__GETSESSION = _selinux.PROCESS__GETSESSION +PROCESS__GETPGID = _selinux.PROCESS__GETPGID +PROCESS__SETPGID = _selinux.PROCESS__SETPGID +PROCESS__GETCAP = _selinux.PROCESS__GETCAP +PROCESS__SETCAP = _selinux.PROCESS__SETCAP +PROCESS__SHARE = _selinux.PROCESS__SHARE +PROCESS__GETATTR = _selinux.PROCESS__GETATTR +PROCESS__SETEXEC = _selinux.PROCESS__SETEXEC +PROCESS__SETFSCREATE = _selinux.PROCESS__SETFSCREATE +PROCESS__NOATSECURE = _selinux.PROCESS__NOATSECURE +PROCESS__SIGINH = _selinux.PROCESS__SIGINH +PROCESS__SETRLIMIT = _selinux.PROCESS__SETRLIMIT +PROCESS__RLIMITINH = _selinux.PROCESS__RLIMITINH +PROCESS__DYNTRANSITION = _selinux.PROCESS__DYNTRANSITION +PROCESS__SETCURRENT = _selinux.PROCESS__SETCURRENT +PROCESS__EXECMEM = _selinux.PROCESS__EXECMEM +PROCESS__EXECSTACK = _selinux.PROCESS__EXECSTACK +PROCESS__EXECHEAP = _selinux.PROCESS__EXECHEAP +PROCESS__SETKEYCREATE = _selinux.PROCESS__SETKEYCREATE +PROCESS__SETSOCKCREATE = _selinux.PROCESS__SETSOCKCREATE +IPC__CREATE = _selinux.IPC__CREATE +IPC__DESTROY = _selinux.IPC__DESTROY +IPC__GETATTR = _selinux.IPC__GETATTR +IPC__SETATTR = _selinux.IPC__SETATTR +IPC__READ = _selinux.IPC__READ +IPC__WRITE = _selinux.IPC__WRITE +IPC__ASSOCIATE = _selinux.IPC__ASSOCIATE +IPC__UNIX_READ = _selinux.IPC__UNIX_READ +IPC__UNIX_WRITE = _selinux.IPC__UNIX_WRITE +SEM__CREATE = _selinux.SEM__CREATE +SEM__DESTROY = _selinux.SEM__DESTROY +SEM__GETATTR = _selinux.SEM__GETATTR +SEM__SETATTR = _selinux.SEM__SETATTR +SEM__READ = _selinux.SEM__READ +SEM__WRITE = _selinux.SEM__WRITE +SEM__ASSOCIATE = _selinux.SEM__ASSOCIATE +SEM__UNIX_READ = _selinux.SEM__UNIX_READ +SEM__UNIX_WRITE = _selinux.SEM__UNIX_WRITE +MSGQ__CREATE = _selinux.MSGQ__CREATE +MSGQ__DESTROY = _selinux.MSGQ__DESTROY +MSGQ__GETATTR = _selinux.MSGQ__GETATTR +MSGQ__SETATTR = _selinux.MSGQ__SETATTR +MSGQ__READ = _selinux.MSGQ__READ +MSGQ__WRITE = _selinux.MSGQ__WRITE +MSGQ__ASSOCIATE = _selinux.MSGQ__ASSOCIATE +MSGQ__UNIX_READ = _selinux.MSGQ__UNIX_READ +MSGQ__UNIX_WRITE = _selinux.MSGQ__UNIX_WRITE +MSGQ__ENQUEUE = _selinux.MSGQ__ENQUEUE +MSG__SEND = _selinux.MSG__SEND +MSG__RECEIVE = _selinux.MSG__RECEIVE +SHM__CREATE = _selinux.SHM__CREATE +SHM__DESTROY = _selinux.SHM__DESTROY +SHM__GETATTR = _selinux.SHM__GETATTR +SHM__SETATTR = _selinux.SHM__SETATTR +SHM__READ = _selinux.SHM__READ +SHM__WRITE = _selinux.SHM__WRITE +SHM__ASSOCIATE = _selinux.SHM__ASSOCIATE +SHM__UNIX_READ = _selinux.SHM__UNIX_READ +SHM__UNIX_WRITE = _selinux.SHM__UNIX_WRITE +SHM__LOCK = _selinux.SHM__LOCK +SECURITY__COMPUTE_AV = _selinux.SECURITY__COMPUTE_AV +SECURITY__COMPUTE_CREATE = _selinux.SECURITY__COMPUTE_CREATE +SECURITY__COMPUTE_MEMBER = _selinux.SECURITY__COMPUTE_MEMBER +SECURITY__CHECK_CONTEXT = _selinux.SECURITY__CHECK_CONTEXT +SECURITY__LOAD_POLICY = _selinux.SECURITY__LOAD_POLICY +SECURITY__COMPUTE_RELABEL = _selinux.SECURITY__COMPUTE_RELABEL +SECURITY__COMPUTE_USER = _selinux.SECURITY__COMPUTE_USER +SECURITY__SETENFORCE = _selinux.SECURITY__SETENFORCE +SECURITY__SETBOOL = _selinux.SECURITY__SETBOOL +SECURITY__SETSECPARAM = _selinux.SECURITY__SETSECPARAM +SECURITY__SETCHECKREQPROT = _selinux.SECURITY__SETCHECKREQPROT +SYSTEM__IPC_INFO = _selinux.SYSTEM__IPC_INFO +SYSTEM__SYSLOG_READ = _selinux.SYSTEM__SYSLOG_READ +SYSTEM__SYSLOG_MOD = _selinux.SYSTEM__SYSLOG_MOD +SYSTEM__SYSLOG_CONSOLE = _selinux.SYSTEM__SYSLOG_CONSOLE +CAPABILITY__CHOWN = _selinux.CAPABILITY__CHOWN +CAPABILITY__DAC_OVERRIDE = _selinux.CAPABILITY__DAC_OVERRIDE +CAPABILITY__DAC_READ_SEARCH = _selinux.CAPABILITY__DAC_READ_SEARCH +CAPABILITY__FOWNER = _selinux.CAPABILITY__FOWNER +CAPABILITY__FSETID = _selinux.CAPABILITY__FSETID +CAPABILITY__KILL = _selinux.CAPABILITY__KILL +CAPABILITY__SETGID = _selinux.CAPABILITY__SETGID +CAPABILITY__SETUID = _selinux.CAPABILITY__SETUID +CAPABILITY__SETPCAP = _selinux.CAPABILITY__SETPCAP +CAPABILITY__LINUX_IMMUTABLE = _selinux.CAPABILITY__LINUX_IMMUTABLE +CAPABILITY__NET_BIND_SERVICE = _selinux.CAPABILITY__NET_BIND_SERVICE +CAPABILITY__NET_BROADCAST = _selinux.CAPABILITY__NET_BROADCAST +CAPABILITY__NET_ADMIN = _selinux.CAPABILITY__NET_ADMIN +CAPABILITY__NET_RAW = _selinux.CAPABILITY__NET_RAW +CAPABILITY__IPC_LOCK = _selinux.CAPABILITY__IPC_LOCK +CAPABILITY__IPC_OWNER = _selinux.CAPABILITY__IPC_OWNER +CAPABILITY__SYS_MODULE = _selinux.CAPABILITY__SYS_MODULE +CAPABILITY__SYS_RAWIO = _selinux.CAPABILITY__SYS_RAWIO +CAPABILITY__SYS_CHROOT = _selinux.CAPABILITY__SYS_CHROOT +CAPABILITY__SYS_PTRACE = _selinux.CAPABILITY__SYS_PTRACE +CAPABILITY__SYS_PACCT = _selinux.CAPABILITY__SYS_PACCT +CAPABILITY__SYS_ADMIN = _selinux.CAPABILITY__SYS_ADMIN +CAPABILITY__SYS_BOOT = _selinux.CAPABILITY__SYS_BOOT +CAPABILITY__SYS_NICE = _selinux.CAPABILITY__SYS_NICE +CAPABILITY__SYS_RESOURCE = _selinux.CAPABILITY__SYS_RESOURCE +CAPABILITY__SYS_TIME = _selinux.CAPABILITY__SYS_TIME +CAPABILITY__SYS_TTY_CONFIG = _selinux.CAPABILITY__SYS_TTY_CONFIG +CAPABILITY__MKNOD = _selinux.CAPABILITY__MKNOD +CAPABILITY__LEASE = _selinux.CAPABILITY__LEASE +CAPABILITY__AUDIT_WRITE = _selinux.CAPABILITY__AUDIT_WRITE +CAPABILITY__AUDIT_CONTROL = _selinux.CAPABILITY__AUDIT_CONTROL +CAPABILITY__SETFCAP = _selinux.CAPABILITY__SETFCAP +CAPABILITY2__MAC_OVERRIDE = _selinux.CAPABILITY2__MAC_OVERRIDE +CAPABILITY2__MAC_ADMIN = _selinux.CAPABILITY2__MAC_ADMIN +PASSWD__PASSWD = _selinux.PASSWD__PASSWD +PASSWD__CHFN = _selinux.PASSWD__CHFN +PASSWD__CHSH = _selinux.PASSWD__CHSH +PASSWD__ROOTOK = _selinux.PASSWD__ROOTOK +PASSWD__CRONTAB = _selinux.PASSWD__CRONTAB +X_DRAWABLE__CREATE = _selinux.X_DRAWABLE__CREATE +X_DRAWABLE__DESTROY = _selinux.X_DRAWABLE__DESTROY +X_DRAWABLE__READ = _selinux.X_DRAWABLE__READ +X_DRAWABLE__WRITE = _selinux.X_DRAWABLE__WRITE +X_DRAWABLE__BLEND = _selinux.X_DRAWABLE__BLEND +X_DRAWABLE__GETATTR = _selinux.X_DRAWABLE__GETATTR +X_DRAWABLE__SETATTR = _selinux.X_DRAWABLE__SETATTR +X_DRAWABLE__LIST_CHILD = _selinux.X_DRAWABLE__LIST_CHILD +X_DRAWABLE__ADD_CHILD = _selinux.X_DRAWABLE__ADD_CHILD +X_DRAWABLE__REMOVE_CHILD = _selinux.X_DRAWABLE__REMOVE_CHILD +X_DRAWABLE__LIST_PROPERTY = _selinux.X_DRAWABLE__LIST_PROPERTY +X_DRAWABLE__GET_PROPERTY = _selinux.X_DRAWABLE__GET_PROPERTY +X_DRAWABLE__SET_PROPERTY = _selinux.X_DRAWABLE__SET_PROPERTY +X_DRAWABLE__MANAGE = _selinux.X_DRAWABLE__MANAGE +X_DRAWABLE__OVERRIDE = _selinux.X_DRAWABLE__OVERRIDE +X_DRAWABLE__SHOW = _selinux.X_DRAWABLE__SHOW +X_DRAWABLE__HIDE = _selinux.X_DRAWABLE__HIDE +X_DRAWABLE__SEND = _selinux.X_DRAWABLE__SEND +X_DRAWABLE__RECEIVE = _selinux.X_DRAWABLE__RECEIVE +X_SCREEN__GETATTR = _selinux.X_SCREEN__GETATTR +X_SCREEN__SETATTR = _selinux.X_SCREEN__SETATTR +X_SCREEN__HIDE_CURSOR = _selinux.X_SCREEN__HIDE_CURSOR +X_SCREEN__SHOW_CURSOR = _selinux.X_SCREEN__SHOW_CURSOR +X_SCREEN__SAVER_GETATTR = _selinux.X_SCREEN__SAVER_GETATTR +X_SCREEN__SAVER_SETATTR = _selinux.X_SCREEN__SAVER_SETATTR +X_SCREEN__SAVER_HIDE = _selinux.X_SCREEN__SAVER_HIDE +X_SCREEN__SAVER_SHOW = _selinux.X_SCREEN__SAVER_SHOW +X_GC__CREATE = _selinux.X_GC__CREATE +X_GC__DESTROY = _selinux.X_GC__DESTROY +X_GC__GETATTR = _selinux.X_GC__GETATTR +X_GC__SETATTR = _selinux.X_GC__SETATTR +X_GC__USE = _selinux.X_GC__USE +X_FONT__CREATE = _selinux.X_FONT__CREATE +X_FONT__DESTROY = _selinux.X_FONT__DESTROY +X_FONT__GETATTR = _selinux.X_FONT__GETATTR +X_FONT__ADD_GLYPH = _selinux.X_FONT__ADD_GLYPH +X_FONT__REMOVE_GLYPH = _selinux.X_FONT__REMOVE_GLYPH +X_FONT__USE = _selinux.X_FONT__USE +X_COLORMAP__CREATE = _selinux.X_COLORMAP__CREATE +X_COLORMAP__DESTROY = _selinux.X_COLORMAP__DESTROY +X_COLORMAP__READ = _selinux.X_COLORMAP__READ +X_COLORMAP__WRITE = _selinux.X_COLORMAP__WRITE +X_COLORMAP__GETATTR = _selinux.X_COLORMAP__GETATTR +X_COLORMAP__ADD_COLOR = _selinux.X_COLORMAP__ADD_COLOR +X_COLORMAP__REMOVE_COLOR = _selinux.X_COLORMAP__REMOVE_COLOR +X_COLORMAP__INSTALL = _selinux.X_COLORMAP__INSTALL +X_COLORMAP__UNINSTALL = _selinux.X_COLORMAP__UNINSTALL +X_COLORMAP__USE = _selinux.X_COLORMAP__USE +X_PROPERTY__CREATE = _selinux.X_PROPERTY__CREATE +X_PROPERTY__DESTROY = _selinux.X_PROPERTY__DESTROY +X_PROPERTY__READ = _selinux.X_PROPERTY__READ +X_PROPERTY__WRITE = _selinux.X_PROPERTY__WRITE +X_PROPERTY__APPEND = _selinux.X_PROPERTY__APPEND +X_PROPERTY__GETATTR = _selinux.X_PROPERTY__GETATTR +X_PROPERTY__SETATTR = _selinux.X_PROPERTY__SETATTR +X_SELECTION__READ = _selinux.X_SELECTION__READ +X_SELECTION__WRITE = _selinux.X_SELECTION__WRITE +X_SELECTION__GETATTR = _selinux.X_SELECTION__GETATTR +X_SELECTION__SETATTR = _selinux.X_SELECTION__SETATTR +X_CURSOR__CREATE = _selinux.X_CURSOR__CREATE +X_CURSOR__DESTROY = _selinux.X_CURSOR__DESTROY +X_CURSOR__READ = _selinux.X_CURSOR__READ +X_CURSOR__WRITE = _selinux.X_CURSOR__WRITE +X_CURSOR__GETATTR = _selinux.X_CURSOR__GETATTR +X_CURSOR__SETATTR = _selinux.X_CURSOR__SETATTR +X_CURSOR__USE = _selinux.X_CURSOR__USE +X_CLIENT__DESTROY = _selinux.X_CLIENT__DESTROY +X_CLIENT__GETATTR = _selinux.X_CLIENT__GETATTR +X_CLIENT__SETATTR = _selinux.X_CLIENT__SETATTR +X_CLIENT__MANAGE = _selinux.X_CLIENT__MANAGE +X_DEVICE__GETATTR = _selinux.X_DEVICE__GETATTR +X_DEVICE__SETATTR = _selinux.X_DEVICE__SETATTR +X_DEVICE__USE = _selinux.X_DEVICE__USE +X_DEVICE__READ = _selinux.X_DEVICE__READ +X_DEVICE__WRITE = _selinux.X_DEVICE__WRITE +X_DEVICE__GETFOCUS = _selinux.X_DEVICE__GETFOCUS +X_DEVICE__SETFOCUS = _selinux.X_DEVICE__SETFOCUS +X_DEVICE__BELL = _selinux.X_DEVICE__BELL +X_DEVICE__FORCE_CURSOR = _selinux.X_DEVICE__FORCE_CURSOR +X_DEVICE__FREEZE = _selinux.X_DEVICE__FREEZE +X_DEVICE__GRAB = _selinux.X_DEVICE__GRAB +X_DEVICE__MANAGE = _selinux.X_DEVICE__MANAGE +X_SERVER__GETATTR = _selinux.X_SERVER__GETATTR +X_SERVER__SETATTR = _selinux.X_SERVER__SETATTR +X_SERVER__RECORD = _selinux.X_SERVER__RECORD +X_SERVER__DEBUG = _selinux.X_SERVER__DEBUG +X_SERVER__GRAB = _selinux.X_SERVER__GRAB +X_SERVER__MANAGE = _selinux.X_SERVER__MANAGE +X_EXTENSION__QUERY = _selinux.X_EXTENSION__QUERY +X_EXTENSION__USE = _selinux.X_EXTENSION__USE +X_RESOURCE__READ = _selinux.X_RESOURCE__READ +X_RESOURCE__WRITE = _selinux.X_RESOURCE__WRITE +X_EVENT__SEND = _selinux.X_EVENT__SEND +X_EVENT__RECEIVE = _selinux.X_EVENT__RECEIVE +X_SYNTHETIC_EVENT__SEND = _selinux.X_SYNTHETIC_EVENT__SEND +X_SYNTHETIC_EVENT__RECEIVE = _selinux.X_SYNTHETIC_EVENT__RECEIVE +NETLINK_ROUTE_SOCKET__IOCTL = _selinux.NETLINK_ROUTE_SOCKET__IOCTL +NETLINK_ROUTE_SOCKET__READ = _selinux.NETLINK_ROUTE_SOCKET__READ +NETLINK_ROUTE_SOCKET__WRITE = _selinux.NETLINK_ROUTE_SOCKET__WRITE +NETLINK_ROUTE_SOCKET__CREATE = _selinux.NETLINK_ROUTE_SOCKET__CREATE +NETLINK_ROUTE_SOCKET__GETATTR = _selinux.NETLINK_ROUTE_SOCKET__GETATTR +NETLINK_ROUTE_SOCKET__SETATTR = _selinux.NETLINK_ROUTE_SOCKET__SETATTR +NETLINK_ROUTE_SOCKET__LOCK = _selinux.NETLINK_ROUTE_SOCKET__LOCK +NETLINK_ROUTE_SOCKET__RELABELFROM = _selinux.NETLINK_ROUTE_SOCKET__RELABELFROM +NETLINK_ROUTE_SOCKET__RELABELTO = _selinux.NETLINK_ROUTE_SOCKET__RELABELTO +NETLINK_ROUTE_SOCKET__APPEND = _selinux.NETLINK_ROUTE_SOCKET__APPEND +NETLINK_ROUTE_SOCKET__BIND = _selinux.NETLINK_ROUTE_SOCKET__BIND +NETLINK_ROUTE_SOCKET__CONNECT = _selinux.NETLINK_ROUTE_SOCKET__CONNECT +NETLINK_ROUTE_SOCKET__LISTEN = _selinux.NETLINK_ROUTE_SOCKET__LISTEN +NETLINK_ROUTE_SOCKET__ACCEPT = _selinux.NETLINK_ROUTE_SOCKET__ACCEPT +NETLINK_ROUTE_SOCKET__GETOPT = _selinux.NETLINK_ROUTE_SOCKET__GETOPT +NETLINK_ROUTE_SOCKET__SETOPT = _selinux.NETLINK_ROUTE_SOCKET__SETOPT +NETLINK_ROUTE_SOCKET__SHUTDOWN = _selinux.NETLINK_ROUTE_SOCKET__SHUTDOWN +NETLINK_ROUTE_SOCKET__RECVFROM = _selinux.NETLINK_ROUTE_SOCKET__RECVFROM +NETLINK_ROUTE_SOCKET__SENDTO = _selinux.NETLINK_ROUTE_SOCKET__SENDTO +NETLINK_ROUTE_SOCKET__RECV_MSG = _selinux.NETLINK_ROUTE_SOCKET__RECV_MSG +NETLINK_ROUTE_SOCKET__SEND_MSG = _selinux.NETLINK_ROUTE_SOCKET__SEND_MSG +NETLINK_ROUTE_SOCKET__NAME_BIND = _selinux.NETLINK_ROUTE_SOCKET__NAME_BIND +NETLINK_ROUTE_SOCKET__NLMSG_READ = _selinux.NETLINK_ROUTE_SOCKET__NLMSG_READ +NETLINK_ROUTE_SOCKET__NLMSG_WRITE = _selinux.NETLINK_ROUTE_SOCKET__NLMSG_WRITE +NETLINK_FIREWALL_SOCKET__IOCTL = _selinux.NETLINK_FIREWALL_SOCKET__IOCTL +NETLINK_FIREWALL_SOCKET__READ = _selinux.NETLINK_FIREWALL_SOCKET__READ +NETLINK_FIREWALL_SOCKET__WRITE = _selinux.NETLINK_FIREWALL_SOCKET__WRITE +NETLINK_FIREWALL_SOCKET__CREATE = _selinux.NETLINK_FIREWALL_SOCKET__CREATE +NETLINK_FIREWALL_SOCKET__GETATTR = _selinux.NETLINK_FIREWALL_SOCKET__GETATTR +NETLINK_FIREWALL_SOCKET__SETATTR = _selinux.NETLINK_FIREWALL_SOCKET__SETATTR +NETLINK_FIREWALL_SOCKET__LOCK = _selinux.NETLINK_FIREWALL_SOCKET__LOCK +NETLINK_FIREWALL_SOCKET__RELABELFROM = _selinux.NETLINK_FIREWALL_SOCKET__RELABELFROM +NETLINK_FIREWALL_SOCKET__RELABELTO = _selinux.NETLINK_FIREWALL_SOCKET__RELABELTO +NETLINK_FIREWALL_SOCKET__APPEND = _selinux.NETLINK_FIREWALL_SOCKET__APPEND +NETLINK_FIREWALL_SOCKET__BIND = _selinux.NETLINK_FIREWALL_SOCKET__BIND +NETLINK_FIREWALL_SOCKET__CONNECT = _selinux.NETLINK_FIREWALL_SOCKET__CONNECT +NETLINK_FIREWALL_SOCKET__LISTEN = _selinux.NETLINK_FIREWALL_SOCKET__LISTEN +NETLINK_FIREWALL_SOCKET__ACCEPT = _selinux.NETLINK_FIREWALL_SOCKET__ACCEPT +NETLINK_FIREWALL_SOCKET__GETOPT = _selinux.NETLINK_FIREWALL_SOCKET__GETOPT +NETLINK_FIREWALL_SOCKET__SETOPT = _selinux.NETLINK_FIREWALL_SOCKET__SETOPT +NETLINK_FIREWALL_SOCKET__SHUTDOWN = _selinux.NETLINK_FIREWALL_SOCKET__SHUTDOWN +NETLINK_FIREWALL_SOCKET__RECVFROM = _selinux.NETLINK_FIREWALL_SOCKET__RECVFROM +NETLINK_FIREWALL_SOCKET__SENDTO = _selinux.NETLINK_FIREWALL_SOCKET__SENDTO +NETLINK_FIREWALL_SOCKET__RECV_MSG = _selinux.NETLINK_FIREWALL_SOCKET__RECV_MSG +NETLINK_FIREWALL_SOCKET__SEND_MSG = _selinux.NETLINK_FIREWALL_SOCKET__SEND_MSG +NETLINK_FIREWALL_SOCKET__NAME_BIND = _selinux.NETLINK_FIREWALL_SOCKET__NAME_BIND +NETLINK_FIREWALL_SOCKET__NLMSG_READ = _selinux.NETLINK_FIREWALL_SOCKET__NLMSG_READ +NETLINK_FIREWALL_SOCKET__NLMSG_WRITE = _selinux.NETLINK_FIREWALL_SOCKET__NLMSG_WRITE +NETLINK_TCPDIAG_SOCKET__IOCTL = _selinux.NETLINK_TCPDIAG_SOCKET__IOCTL +NETLINK_TCPDIAG_SOCKET__READ = _selinux.NETLINK_TCPDIAG_SOCKET__READ +NETLINK_TCPDIAG_SOCKET__WRITE = _selinux.NETLINK_TCPDIAG_SOCKET__WRITE +NETLINK_TCPDIAG_SOCKET__CREATE = _selinux.NETLINK_TCPDIAG_SOCKET__CREATE +NETLINK_TCPDIAG_SOCKET__GETATTR = _selinux.NETLINK_TCPDIAG_SOCKET__GETATTR +NETLINK_TCPDIAG_SOCKET__SETATTR = _selinux.NETLINK_TCPDIAG_SOCKET__SETATTR +NETLINK_TCPDIAG_SOCKET__LOCK = _selinux.NETLINK_TCPDIAG_SOCKET__LOCK +NETLINK_TCPDIAG_SOCKET__RELABELFROM = _selinux.NETLINK_TCPDIAG_SOCKET__RELABELFROM +NETLINK_TCPDIAG_SOCKET__RELABELTO = _selinux.NETLINK_TCPDIAG_SOCKET__RELABELTO +NETLINK_TCPDIAG_SOCKET__APPEND = _selinux.NETLINK_TCPDIAG_SOCKET__APPEND +NETLINK_TCPDIAG_SOCKET__BIND = _selinux.NETLINK_TCPDIAG_SOCKET__BIND +NETLINK_TCPDIAG_SOCKET__CONNECT = _selinux.NETLINK_TCPDIAG_SOCKET__CONNECT +NETLINK_TCPDIAG_SOCKET__LISTEN = _selinux.NETLINK_TCPDIAG_SOCKET__LISTEN +NETLINK_TCPDIAG_SOCKET__ACCEPT = _selinux.NETLINK_TCPDIAG_SOCKET__ACCEPT +NETLINK_TCPDIAG_SOCKET__GETOPT = _selinux.NETLINK_TCPDIAG_SOCKET__GETOPT +NETLINK_TCPDIAG_SOCKET__SETOPT = _selinux.NETLINK_TCPDIAG_SOCKET__SETOPT +NETLINK_TCPDIAG_SOCKET__SHUTDOWN = _selinux.NETLINK_TCPDIAG_SOCKET__SHUTDOWN +NETLINK_TCPDIAG_SOCKET__RECVFROM = _selinux.NETLINK_TCPDIAG_SOCKET__RECVFROM +NETLINK_TCPDIAG_SOCKET__SENDTO = _selinux.NETLINK_TCPDIAG_SOCKET__SENDTO +NETLINK_TCPDIAG_SOCKET__RECV_MSG = _selinux.NETLINK_TCPDIAG_SOCKET__RECV_MSG +NETLINK_TCPDIAG_SOCKET__SEND_MSG = _selinux.NETLINK_TCPDIAG_SOCKET__SEND_MSG +NETLINK_TCPDIAG_SOCKET__NAME_BIND = _selinux.NETLINK_TCPDIAG_SOCKET__NAME_BIND +NETLINK_TCPDIAG_SOCKET__NLMSG_READ = _selinux.NETLINK_TCPDIAG_SOCKET__NLMSG_READ +NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE = _selinux.NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE +NETLINK_NFLOG_SOCKET__IOCTL = _selinux.NETLINK_NFLOG_SOCKET__IOCTL +NETLINK_NFLOG_SOCKET__READ = _selinux.NETLINK_NFLOG_SOCKET__READ +NETLINK_NFLOG_SOCKET__WRITE = _selinux.NETLINK_NFLOG_SOCKET__WRITE +NETLINK_NFLOG_SOCKET__CREATE = _selinux.NETLINK_NFLOG_SOCKET__CREATE +NETLINK_NFLOG_SOCKET__GETATTR = _selinux.NETLINK_NFLOG_SOCKET__GETATTR +NETLINK_NFLOG_SOCKET__SETATTR = _selinux.NETLINK_NFLOG_SOCKET__SETATTR +NETLINK_NFLOG_SOCKET__LOCK = _selinux.NETLINK_NFLOG_SOCKET__LOCK +NETLINK_NFLOG_SOCKET__RELABELFROM = _selinux.NETLINK_NFLOG_SOCKET__RELABELFROM +NETLINK_NFLOG_SOCKET__RELABELTO = _selinux.NETLINK_NFLOG_SOCKET__RELABELTO +NETLINK_NFLOG_SOCKET__APPEND = _selinux.NETLINK_NFLOG_SOCKET__APPEND +NETLINK_NFLOG_SOCKET__BIND = _selinux.NETLINK_NFLOG_SOCKET__BIND +NETLINK_NFLOG_SOCKET__CONNECT = _selinux.NETLINK_NFLOG_SOCKET__CONNECT +NETLINK_NFLOG_SOCKET__LISTEN = _selinux.NETLINK_NFLOG_SOCKET__LISTEN +NETLINK_NFLOG_SOCKET__ACCEPT = _selinux.NETLINK_NFLOG_SOCKET__ACCEPT +NETLINK_NFLOG_SOCKET__GETOPT = _selinux.NETLINK_NFLOG_SOCKET__GETOPT +NETLINK_NFLOG_SOCKET__SETOPT = _selinux.NETLINK_NFLOG_SOCKET__SETOPT +NETLINK_NFLOG_SOCKET__SHUTDOWN = _selinux.NETLINK_NFLOG_SOCKET__SHUTDOWN +NETLINK_NFLOG_SOCKET__RECVFROM = _selinux.NETLINK_NFLOG_SOCKET__RECVFROM +NETLINK_NFLOG_SOCKET__SENDTO = _selinux.NETLINK_NFLOG_SOCKET__SENDTO +NETLINK_NFLOG_SOCKET__RECV_MSG = _selinux.NETLINK_NFLOG_SOCKET__RECV_MSG +NETLINK_NFLOG_SOCKET__SEND_MSG = _selinux.NETLINK_NFLOG_SOCKET__SEND_MSG +NETLINK_NFLOG_SOCKET__NAME_BIND = _selinux.NETLINK_NFLOG_SOCKET__NAME_BIND +NETLINK_XFRM_SOCKET__IOCTL = _selinux.NETLINK_XFRM_SOCKET__IOCTL +NETLINK_XFRM_SOCKET__READ = _selinux.NETLINK_XFRM_SOCKET__READ +NETLINK_XFRM_SOCKET__WRITE = _selinux.NETLINK_XFRM_SOCKET__WRITE +NETLINK_XFRM_SOCKET__CREATE = _selinux.NETLINK_XFRM_SOCKET__CREATE +NETLINK_XFRM_SOCKET__GETATTR = _selinux.NETLINK_XFRM_SOCKET__GETATTR +NETLINK_XFRM_SOCKET__SETATTR = _selinux.NETLINK_XFRM_SOCKET__SETATTR +NETLINK_XFRM_SOCKET__LOCK = _selinux.NETLINK_XFRM_SOCKET__LOCK +NETLINK_XFRM_SOCKET__RELABELFROM = _selinux.NETLINK_XFRM_SOCKET__RELABELFROM +NETLINK_XFRM_SOCKET__RELABELTO = _selinux.NETLINK_XFRM_SOCKET__RELABELTO +NETLINK_XFRM_SOCKET__APPEND = _selinux.NETLINK_XFRM_SOCKET__APPEND +NETLINK_XFRM_SOCKET__BIND = _selinux.NETLINK_XFRM_SOCKET__BIND +NETLINK_XFRM_SOCKET__CONNECT = _selinux.NETLINK_XFRM_SOCKET__CONNECT +NETLINK_XFRM_SOCKET__LISTEN = _selinux.NETLINK_XFRM_SOCKET__LISTEN +NETLINK_XFRM_SOCKET__ACCEPT = _selinux.NETLINK_XFRM_SOCKET__ACCEPT +NETLINK_XFRM_SOCKET__GETOPT = _selinux.NETLINK_XFRM_SOCKET__GETOPT +NETLINK_XFRM_SOCKET__SETOPT = _selinux.NETLINK_XFRM_SOCKET__SETOPT +NETLINK_XFRM_SOCKET__SHUTDOWN = _selinux.NETLINK_XFRM_SOCKET__SHUTDOWN +NETLINK_XFRM_SOCKET__RECVFROM = _selinux.NETLINK_XFRM_SOCKET__RECVFROM +NETLINK_XFRM_SOCKET__SENDTO = _selinux.NETLINK_XFRM_SOCKET__SENDTO +NETLINK_XFRM_SOCKET__RECV_MSG = _selinux.NETLINK_XFRM_SOCKET__RECV_MSG +NETLINK_XFRM_SOCKET__SEND_MSG = _selinux.NETLINK_XFRM_SOCKET__SEND_MSG +NETLINK_XFRM_SOCKET__NAME_BIND = _selinux.NETLINK_XFRM_SOCKET__NAME_BIND +NETLINK_XFRM_SOCKET__NLMSG_READ = _selinux.NETLINK_XFRM_SOCKET__NLMSG_READ +NETLINK_XFRM_SOCKET__NLMSG_WRITE = _selinux.NETLINK_XFRM_SOCKET__NLMSG_WRITE +NETLINK_SELINUX_SOCKET__IOCTL = _selinux.NETLINK_SELINUX_SOCKET__IOCTL +NETLINK_SELINUX_SOCKET__READ = _selinux.NETLINK_SELINUX_SOCKET__READ +NETLINK_SELINUX_SOCKET__WRITE = _selinux.NETLINK_SELINUX_SOCKET__WRITE +NETLINK_SELINUX_SOCKET__CREATE = _selinux.NETLINK_SELINUX_SOCKET__CREATE +NETLINK_SELINUX_SOCKET__GETATTR = _selinux.NETLINK_SELINUX_SOCKET__GETATTR +NETLINK_SELINUX_SOCKET__SETATTR = _selinux.NETLINK_SELINUX_SOCKET__SETATTR +NETLINK_SELINUX_SOCKET__LOCK = _selinux.NETLINK_SELINUX_SOCKET__LOCK +NETLINK_SELINUX_SOCKET__RELABELFROM = _selinux.NETLINK_SELINUX_SOCKET__RELABELFROM +NETLINK_SELINUX_SOCKET__RELABELTO = _selinux.NETLINK_SELINUX_SOCKET__RELABELTO +NETLINK_SELINUX_SOCKET__APPEND = _selinux.NETLINK_SELINUX_SOCKET__APPEND +NETLINK_SELINUX_SOCKET__BIND = _selinux.NETLINK_SELINUX_SOCKET__BIND +NETLINK_SELINUX_SOCKET__CONNECT = _selinux.NETLINK_SELINUX_SOCKET__CONNECT +NETLINK_SELINUX_SOCKET__LISTEN = _selinux.NETLINK_SELINUX_SOCKET__LISTEN +NETLINK_SELINUX_SOCKET__ACCEPT = _selinux.NETLINK_SELINUX_SOCKET__ACCEPT +NETLINK_SELINUX_SOCKET__GETOPT = _selinux.NETLINK_SELINUX_SOCKET__GETOPT +NETLINK_SELINUX_SOCKET__SETOPT = _selinux.NETLINK_SELINUX_SOCKET__SETOPT +NETLINK_SELINUX_SOCKET__SHUTDOWN = _selinux.NETLINK_SELINUX_SOCKET__SHUTDOWN +NETLINK_SELINUX_SOCKET__RECVFROM = _selinux.NETLINK_SELINUX_SOCKET__RECVFROM +NETLINK_SELINUX_SOCKET__SENDTO = _selinux.NETLINK_SELINUX_SOCKET__SENDTO +NETLINK_SELINUX_SOCKET__RECV_MSG = _selinux.NETLINK_SELINUX_SOCKET__RECV_MSG +NETLINK_SELINUX_SOCKET__SEND_MSG = _selinux.NETLINK_SELINUX_SOCKET__SEND_MSG +NETLINK_SELINUX_SOCKET__NAME_BIND = _selinux.NETLINK_SELINUX_SOCKET__NAME_BIND +NETLINK_AUDIT_SOCKET__IOCTL = _selinux.NETLINK_AUDIT_SOCKET__IOCTL +NETLINK_AUDIT_SOCKET__READ = _selinux.NETLINK_AUDIT_SOCKET__READ +NETLINK_AUDIT_SOCKET__WRITE = _selinux.NETLINK_AUDIT_SOCKET__WRITE +NETLINK_AUDIT_SOCKET__CREATE = _selinux.NETLINK_AUDIT_SOCKET__CREATE +NETLINK_AUDIT_SOCKET__GETATTR = _selinux.NETLINK_AUDIT_SOCKET__GETATTR +NETLINK_AUDIT_SOCKET__SETATTR = _selinux.NETLINK_AUDIT_SOCKET__SETATTR +NETLINK_AUDIT_SOCKET__LOCK = _selinux.NETLINK_AUDIT_SOCKET__LOCK +NETLINK_AUDIT_SOCKET__RELABELFROM = _selinux.NETLINK_AUDIT_SOCKET__RELABELFROM +NETLINK_AUDIT_SOCKET__RELABELTO = _selinux.NETLINK_AUDIT_SOCKET__RELABELTO +NETLINK_AUDIT_SOCKET__APPEND = _selinux.NETLINK_AUDIT_SOCKET__APPEND +NETLINK_AUDIT_SOCKET__BIND = _selinux.NETLINK_AUDIT_SOCKET__BIND +NETLINK_AUDIT_SOCKET__CONNECT = _selinux.NETLINK_AUDIT_SOCKET__CONNECT +NETLINK_AUDIT_SOCKET__LISTEN = _selinux.NETLINK_AUDIT_SOCKET__LISTEN +NETLINK_AUDIT_SOCKET__ACCEPT = _selinux.NETLINK_AUDIT_SOCKET__ACCEPT +NETLINK_AUDIT_SOCKET__GETOPT = _selinux.NETLINK_AUDIT_SOCKET__GETOPT +NETLINK_AUDIT_SOCKET__SETOPT = _selinux.NETLINK_AUDIT_SOCKET__SETOPT +NETLINK_AUDIT_SOCKET__SHUTDOWN = _selinux.NETLINK_AUDIT_SOCKET__SHUTDOWN +NETLINK_AUDIT_SOCKET__RECVFROM = _selinux.NETLINK_AUDIT_SOCKET__RECVFROM +NETLINK_AUDIT_SOCKET__SENDTO = _selinux.NETLINK_AUDIT_SOCKET__SENDTO +NETLINK_AUDIT_SOCKET__RECV_MSG = _selinux.NETLINK_AUDIT_SOCKET__RECV_MSG +NETLINK_AUDIT_SOCKET__SEND_MSG = _selinux.NETLINK_AUDIT_SOCKET__SEND_MSG +NETLINK_AUDIT_SOCKET__NAME_BIND = _selinux.NETLINK_AUDIT_SOCKET__NAME_BIND +NETLINK_AUDIT_SOCKET__NLMSG_READ = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_READ +NETLINK_AUDIT_SOCKET__NLMSG_WRITE = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_WRITE +NETLINK_AUDIT_SOCKET__NLMSG_RELAY = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_RELAY +NETLINK_AUDIT_SOCKET__NLMSG_READPRIV = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_READPRIV +NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT = _selinux.NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT +NETLINK_IP6FW_SOCKET__IOCTL = _selinux.NETLINK_IP6FW_SOCKET__IOCTL +NETLINK_IP6FW_SOCKET__READ = _selinux.NETLINK_IP6FW_SOCKET__READ +NETLINK_IP6FW_SOCKET__WRITE = _selinux.NETLINK_IP6FW_SOCKET__WRITE +NETLINK_IP6FW_SOCKET__CREATE = _selinux.NETLINK_IP6FW_SOCKET__CREATE +NETLINK_IP6FW_SOCKET__GETATTR = _selinux.NETLINK_IP6FW_SOCKET__GETATTR +NETLINK_IP6FW_SOCKET__SETATTR = _selinux.NETLINK_IP6FW_SOCKET__SETATTR +NETLINK_IP6FW_SOCKET__LOCK = _selinux.NETLINK_IP6FW_SOCKET__LOCK +NETLINK_IP6FW_SOCKET__RELABELFROM = _selinux.NETLINK_IP6FW_SOCKET__RELABELFROM +NETLINK_IP6FW_SOCKET__RELABELTO = _selinux.NETLINK_IP6FW_SOCKET__RELABELTO +NETLINK_IP6FW_SOCKET__APPEND = _selinux.NETLINK_IP6FW_SOCKET__APPEND +NETLINK_IP6FW_SOCKET__BIND = _selinux.NETLINK_IP6FW_SOCKET__BIND +NETLINK_IP6FW_SOCKET__CONNECT = _selinux.NETLINK_IP6FW_SOCKET__CONNECT +NETLINK_IP6FW_SOCKET__LISTEN = _selinux.NETLINK_IP6FW_SOCKET__LISTEN +NETLINK_IP6FW_SOCKET__ACCEPT = _selinux.NETLINK_IP6FW_SOCKET__ACCEPT +NETLINK_IP6FW_SOCKET__GETOPT = _selinux.NETLINK_IP6FW_SOCKET__GETOPT +NETLINK_IP6FW_SOCKET__SETOPT = _selinux.NETLINK_IP6FW_SOCKET__SETOPT +NETLINK_IP6FW_SOCKET__SHUTDOWN = _selinux.NETLINK_IP6FW_SOCKET__SHUTDOWN +NETLINK_IP6FW_SOCKET__RECVFROM = _selinux.NETLINK_IP6FW_SOCKET__RECVFROM +NETLINK_IP6FW_SOCKET__SENDTO = _selinux.NETLINK_IP6FW_SOCKET__SENDTO +NETLINK_IP6FW_SOCKET__RECV_MSG = _selinux.NETLINK_IP6FW_SOCKET__RECV_MSG +NETLINK_IP6FW_SOCKET__SEND_MSG = _selinux.NETLINK_IP6FW_SOCKET__SEND_MSG +NETLINK_IP6FW_SOCKET__NAME_BIND = _selinux.NETLINK_IP6FW_SOCKET__NAME_BIND +NETLINK_IP6FW_SOCKET__NLMSG_READ = _selinux.NETLINK_IP6FW_SOCKET__NLMSG_READ +NETLINK_IP6FW_SOCKET__NLMSG_WRITE = _selinux.NETLINK_IP6FW_SOCKET__NLMSG_WRITE +NETLINK_DNRT_SOCKET__IOCTL = _selinux.NETLINK_DNRT_SOCKET__IOCTL +NETLINK_DNRT_SOCKET__READ = _selinux.NETLINK_DNRT_SOCKET__READ +NETLINK_DNRT_SOCKET__WRITE = _selinux.NETLINK_DNRT_SOCKET__WRITE +NETLINK_DNRT_SOCKET__CREATE = _selinux.NETLINK_DNRT_SOCKET__CREATE +NETLINK_DNRT_SOCKET__GETATTR = _selinux.NETLINK_DNRT_SOCKET__GETATTR +NETLINK_DNRT_SOCKET__SETATTR = _selinux.NETLINK_DNRT_SOCKET__SETATTR +NETLINK_DNRT_SOCKET__LOCK = _selinux.NETLINK_DNRT_SOCKET__LOCK +NETLINK_DNRT_SOCKET__RELABELFROM = _selinux.NETLINK_DNRT_SOCKET__RELABELFROM +NETLINK_DNRT_SOCKET__RELABELTO = _selinux.NETLINK_DNRT_SOCKET__RELABELTO +NETLINK_DNRT_SOCKET__APPEND = _selinux.NETLINK_DNRT_SOCKET__APPEND +NETLINK_DNRT_SOCKET__BIND = _selinux.NETLINK_DNRT_SOCKET__BIND +NETLINK_DNRT_SOCKET__CONNECT = _selinux.NETLINK_DNRT_SOCKET__CONNECT +NETLINK_DNRT_SOCKET__LISTEN = _selinux.NETLINK_DNRT_SOCKET__LISTEN +NETLINK_DNRT_SOCKET__ACCEPT = _selinux.NETLINK_DNRT_SOCKET__ACCEPT +NETLINK_DNRT_SOCKET__GETOPT = _selinux.NETLINK_DNRT_SOCKET__GETOPT +NETLINK_DNRT_SOCKET__SETOPT = _selinux.NETLINK_DNRT_SOCKET__SETOPT +NETLINK_DNRT_SOCKET__SHUTDOWN = _selinux.NETLINK_DNRT_SOCKET__SHUTDOWN +NETLINK_DNRT_SOCKET__RECVFROM = _selinux.NETLINK_DNRT_SOCKET__RECVFROM +NETLINK_DNRT_SOCKET__SENDTO = _selinux.NETLINK_DNRT_SOCKET__SENDTO +NETLINK_DNRT_SOCKET__RECV_MSG = _selinux.NETLINK_DNRT_SOCKET__RECV_MSG +NETLINK_DNRT_SOCKET__SEND_MSG = _selinux.NETLINK_DNRT_SOCKET__SEND_MSG +NETLINK_DNRT_SOCKET__NAME_BIND = _selinux.NETLINK_DNRT_SOCKET__NAME_BIND +DBUS__ACQUIRE_SVC = _selinux.DBUS__ACQUIRE_SVC +DBUS__SEND_MSG = _selinux.DBUS__SEND_MSG +NSCD__GETPWD = _selinux.NSCD__GETPWD +NSCD__GETGRP = _selinux.NSCD__GETGRP +NSCD__GETHOST = _selinux.NSCD__GETHOST +NSCD__GETSTAT = _selinux.NSCD__GETSTAT +NSCD__ADMIN = _selinux.NSCD__ADMIN +NSCD__SHMEMPWD = _selinux.NSCD__SHMEMPWD +NSCD__SHMEMGRP = _selinux.NSCD__SHMEMGRP +NSCD__SHMEMHOST = _selinux.NSCD__SHMEMHOST +NSCD__GETSERV = _selinux.NSCD__GETSERV +NSCD__SHMEMSERV = _selinux.NSCD__SHMEMSERV +ASSOCIATION__SENDTO = _selinux.ASSOCIATION__SENDTO +ASSOCIATION__RECVFROM = _selinux.ASSOCIATION__RECVFROM +ASSOCIATION__SETCONTEXT = _selinux.ASSOCIATION__SETCONTEXT +ASSOCIATION__POLMATCH = _selinux.ASSOCIATION__POLMATCH +NETLINK_KOBJECT_UEVENT_SOCKET__IOCTL = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__IOCTL +NETLINK_KOBJECT_UEVENT_SOCKET__READ = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__READ +NETLINK_KOBJECT_UEVENT_SOCKET__WRITE = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__WRITE +NETLINK_KOBJECT_UEVENT_SOCKET__CREATE = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__CREATE +NETLINK_KOBJECT_UEVENT_SOCKET__GETATTR = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__GETATTR +NETLINK_KOBJECT_UEVENT_SOCKET__SETATTR = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SETATTR +NETLINK_KOBJECT_UEVENT_SOCKET__LOCK = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__LOCK +NETLINK_KOBJECT_UEVENT_SOCKET__RELABELFROM = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RELABELFROM +NETLINK_KOBJECT_UEVENT_SOCKET__RELABELTO = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RELABELTO +NETLINK_KOBJECT_UEVENT_SOCKET__APPEND = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__APPEND +NETLINK_KOBJECT_UEVENT_SOCKET__BIND = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__BIND +NETLINK_KOBJECT_UEVENT_SOCKET__CONNECT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__CONNECT +NETLINK_KOBJECT_UEVENT_SOCKET__LISTEN = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__LISTEN +NETLINK_KOBJECT_UEVENT_SOCKET__ACCEPT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__ACCEPT +NETLINK_KOBJECT_UEVENT_SOCKET__GETOPT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__GETOPT +NETLINK_KOBJECT_UEVENT_SOCKET__SETOPT = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SETOPT +NETLINK_KOBJECT_UEVENT_SOCKET__SHUTDOWN = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SHUTDOWN +NETLINK_KOBJECT_UEVENT_SOCKET__RECVFROM = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RECVFROM +NETLINK_KOBJECT_UEVENT_SOCKET__SENDTO = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SENDTO +NETLINK_KOBJECT_UEVENT_SOCKET__RECV_MSG = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__RECV_MSG +NETLINK_KOBJECT_UEVENT_SOCKET__SEND_MSG = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__SEND_MSG +NETLINK_KOBJECT_UEVENT_SOCKET__NAME_BIND = _selinux.NETLINK_KOBJECT_UEVENT_SOCKET__NAME_BIND +APPLETALK_SOCKET__IOCTL = _selinux.APPLETALK_SOCKET__IOCTL +APPLETALK_SOCKET__READ = _selinux.APPLETALK_SOCKET__READ +APPLETALK_SOCKET__WRITE = _selinux.APPLETALK_SOCKET__WRITE +APPLETALK_SOCKET__CREATE = _selinux.APPLETALK_SOCKET__CREATE +APPLETALK_SOCKET__GETATTR = _selinux.APPLETALK_SOCKET__GETATTR +APPLETALK_SOCKET__SETATTR = _selinux.APPLETALK_SOCKET__SETATTR +APPLETALK_SOCKET__LOCK = _selinux.APPLETALK_SOCKET__LOCK +APPLETALK_SOCKET__RELABELFROM = _selinux.APPLETALK_SOCKET__RELABELFROM +APPLETALK_SOCKET__RELABELTO = _selinux.APPLETALK_SOCKET__RELABELTO +APPLETALK_SOCKET__APPEND = _selinux.APPLETALK_SOCKET__APPEND +APPLETALK_SOCKET__BIND = _selinux.APPLETALK_SOCKET__BIND +APPLETALK_SOCKET__CONNECT = _selinux.APPLETALK_SOCKET__CONNECT +APPLETALK_SOCKET__LISTEN = _selinux.APPLETALK_SOCKET__LISTEN +APPLETALK_SOCKET__ACCEPT = _selinux.APPLETALK_SOCKET__ACCEPT +APPLETALK_SOCKET__GETOPT = _selinux.APPLETALK_SOCKET__GETOPT +APPLETALK_SOCKET__SETOPT = _selinux.APPLETALK_SOCKET__SETOPT +APPLETALK_SOCKET__SHUTDOWN = _selinux.APPLETALK_SOCKET__SHUTDOWN +APPLETALK_SOCKET__RECVFROM = _selinux.APPLETALK_SOCKET__RECVFROM +APPLETALK_SOCKET__SENDTO = _selinux.APPLETALK_SOCKET__SENDTO +APPLETALK_SOCKET__RECV_MSG = _selinux.APPLETALK_SOCKET__RECV_MSG +APPLETALK_SOCKET__SEND_MSG = _selinux.APPLETALK_SOCKET__SEND_MSG +APPLETALK_SOCKET__NAME_BIND = _selinux.APPLETALK_SOCKET__NAME_BIND +PACKET__SEND = _selinux.PACKET__SEND +PACKET__RECV = _selinux.PACKET__RECV +PACKET__RELABELTO = _selinux.PACKET__RELABELTO +PACKET__FLOW_IN = _selinux.PACKET__FLOW_IN +PACKET__FLOW_OUT = _selinux.PACKET__FLOW_OUT +PACKET__FORWARD_IN = _selinux.PACKET__FORWARD_IN +PACKET__FORWARD_OUT = _selinux.PACKET__FORWARD_OUT +KEY__VIEW = _selinux.KEY__VIEW +KEY__READ = _selinux.KEY__READ +KEY__WRITE = _selinux.KEY__WRITE +KEY__SEARCH = _selinux.KEY__SEARCH +KEY__LINK = _selinux.KEY__LINK +KEY__SETATTR = _selinux.KEY__SETATTR +KEY__CREATE = _selinux.KEY__CREATE +CONTEXT__TRANSLATE = _selinux.CONTEXT__TRANSLATE +CONTEXT__CONTAINS = _selinux.CONTEXT__CONTAINS +DCCP_SOCKET__IOCTL = _selinux.DCCP_SOCKET__IOCTL +DCCP_SOCKET__READ = _selinux.DCCP_SOCKET__READ +DCCP_SOCKET__WRITE = _selinux.DCCP_SOCKET__WRITE +DCCP_SOCKET__CREATE = _selinux.DCCP_SOCKET__CREATE +DCCP_SOCKET__GETATTR = _selinux.DCCP_SOCKET__GETATTR +DCCP_SOCKET__SETATTR = _selinux.DCCP_SOCKET__SETATTR +DCCP_SOCKET__LOCK = _selinux.DCCP_SOCKET__LOCK +DCCP_SOCKET__RELABELFROM = _selinux.DCCP_SOCKET__RELABELFROM +DCCP_SOCKET__RELABELTO = _selinux.DCCP_SOCKET__RELABELTO +DCCP_SOCKET__APPEND = _selinux.DCCP_SOCKET__APPEND +DCCP_SOCKET__BIND = _selinux.DCCP_SOCKET__BIND +DCCP_SOCKET__CONNECT = _selinux.DCCP_SOCKET__CONNECT +DCCP_SOCKET__LISTEN = _selinux.DCCP_SOCKET__LISTEN +DCCP_SOCKET__ACCEPT = _selinux.DCCP_SOCKET__ACCEPT +DCCP_SOCKET__GETOPT = _selinux.DCCP_SOCKET__GETOPT +DCCP_SOCKET__SETOPT = _selinux.DCCP_SOCKET__SETOPT +DCCP_SOCKET__SHUTDOWN = _selinux.DCCP_SOCKET__SHUTDOWN +DCCP_SOCKET__RECVFROM = _selinux.DCCP_SOCKET__RECVFROM +DCCP_SOCKET__SENDTO = _selinux.DCCP_SOCKET__SENDTO +DCCP_SOCKET__RECV_MSG = _selinux.DCCP_SOCKET__RECV_MSG +DCCP_SOCKET__SEND_MSG = _selinux.DCCP_SOCKET__SEND_MSG +DCCP_SOCKET__NAME_BIND = _selinux.DCCP_SOCKET__NAME_BIND +DCCP_SOCKET__NODE_BIND = _selinux.DCCP_SOCKET__NODE_BIND +DCCP_SOCKET__NAME_CONNECT = _selinux.DCCP_SOCKET__NAME_CONNECT +MEMPROTECT__MMAP_ZERO = _selinux.MEMPROTECT__MMAP_ZERO +DB_DATABASE__CREATE = _selinux.DB_DATABASE__CREATE +DB_DATABASE__DROP = _selinux.DB_DATABASE__DROP +DB_DATABASE__GETATTR = _selinux.DB_DATABASE__GETATTR +DB_DATABASE__SETATTR = _selinux.DB_DATABASE__SETATTR +DB_DATABASE__RELABELFROM = _selinux.DB_DATABASE__RELABELFROM +DB_DATABASE__RELABELTO = _selinux.DB_DATABASE__RELABELTO +DB_DATABASE__ACCESS = _selinux.DB_DATABASE__ACCESS +DB_DATABASE__INSTALL_MODULE = _selinux.DB_DATABASE__INSTALL_MODULE +DB_DATABASE__LOAD_MODULE = _selinux.DB_DATABASE__LOAD_MODULE +DB_DATABASE__GET_PARAM = _selinux.DB_DATABASE__GET_PARAM +DB_DATABASE__SET_PARAM = _selinux.DB_DATABASE__SET_PARAM +DB_TABLE__CREATE = _selinux.DB_TABLE__CREATE +DB_TABLE__DROP = _selinux.DB_TABLE__DROP +DB_TABLE__GETATTR = _selinux.DB_TABLE__GETATTR +DB_TABLE__SETATTR = _selinux.DB_TABLE__SETATTR +DB_TABLE__RELABELFROM = _selinux.DB_TABLE__RELABELFROM +DB_TABLE__RELABELTO = _selinux.DB_TABLE__RELABELTO +DB_TABLE__USE = _selinux.DB_TABLE__USE +DB_TABLE__SELECT = _selinux.DB_TABLE__SELECT +DB_TABLE__UPDATE = _selinux.DB_TABLE__UPDATE +DB_TABLE__INSERT = _selinux.DB_TABLE__INSERT +DB_TABLE__DELETE = _selinux.DB_TABLE__DELETE +DB_TABLE__LOCK = _selinux.DB_TABLE__LOCK +DB_PROCEDURE__CREATE = _selinux.DB_PROCEDURE__CREATE +DB_PROCEDURE__DROP = _selinux.DB_PROCEDURE__DROP +DB_PROCEDURE__GETATTR = _selinux.DB_PROCEDURE__GETATTR +DB_PROCEDURE__SETATTR = _selinux.DB_PROCEDURE__SETATTR +DB_PROCEDURE__RELABELFROM = _selinux.DB_PROCEDURE__RELABELFROM +DB_PROCEDURE__RELABELTO = _selinux.DB_PROCEDURE__RELABELTO +DB_PROCEDURE__EXECUTE = _selinux.DB_PROCEDURE__EXECUTE +DB_PROCEDURE__ENTRYPOINT = _selinux.DB_PROCEDURE__ENTRYPOINT +DB_COLUMN__CREATE = _selinux.DB_COLUMN__CREATE +DB_COLUMN__DROP = _selinux.DB_COLUMN__DROP +DB_COLUMN__GETATTR = _selinux.DB_COLUMN__GETATTR +DB_COLUMN__SETATTR = _selinux.DB_COLUMN__SETATTR +DB_COLUMN__RELABELFROM = _selinux.DB_COLUMN__RELABELFROM +DB_COLUMN__RELABELTO = _selinux.DB_COLUMN__RELABELTO +DB_COLUMN__USE = _selinux.DB_COLUMN__USE +DB_COLUMN__SELECT = _selinux.DB_COLUMN__SELECT +DB_COLUMN__UPDATE = _selinux.DB_COLUMN__UPDATE +DB_COLUMN__INSERT = _selinux.DB_COLUMN__INSERT +DB_TUPLE__RELABELFROM = _selinux.DB_TUPLE__RELABELFROM +DB_TUPLE__RELABELTO = _selinux.DB_TUPLE__RELABELTO +DB_TUPLE__USE = _selinux.DB_TUPLE__USE +DB_TUPLE__SELECT = _selinux.DB_TUPLE__SELECT +DB_TUPLE__UPDATE = _selinux.DB_TUPLE__UPDATE +DB_TUPLE__INSERT = _selinux.DB_TUPLE__INSERT +DB_TUPLE__DELETE = _selinux.DB_TUPLE__DELETE +DB_BLOB__CREATE = _selinux.DB_BLOB__CREATE +DB_BLOB__DROP = _selinux.DB_BLOB__DROP +DB_BLOB__GETATTR = _selinux.DB_BLOB__GETATTR +DB_BLOB__SETATTR = _selinux.DB_BLOB__SETATTR +DB_BLOB__RELABELFROM = _selinux.DB_BLOB__RELABELFROM +DB_BLOB__RELABELTO = _selinux.DB_BLOB__RELABELTO +DB_BLOB__READ = _selinux.DB_BLOB__READ +DB_BLOB__WRITE = _selinux.DB_BLOB__WRITE +DB_BLOB__IMPORT = _selinux.DB_BLOB__IMPORT +DB_BLOB__EXPORT = _selinux.DB_BLOB__EXPORT +PEER__RECV = _selinux.PEER__RECV +X_APPLICATION_DATA__PASTE = _selinux.X_APPLICATION_DATA__PASTE +X_APPLICATION_DATA__PASTE_AFTER_CONFIRM = _selinux.X_APPLICATION_DATA__PASTE_AFTER_CONFIRM +X_APPLICATION_DATA__COPY = _selinux.X_APPLICATION_DATA__COPY +class context_s_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, context_s_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, context_s_t, name) + __repr__ = _swig_repr + __swig_setmethods__["ptr"] = _selinux.context_s_t_ptr_set + __swig_getmethods__["ptr"] = _selinux.context_s_t_ptr_get + if _newclass:ptr = _swig_property(_selinux.context_s_t_ptr_get, _selinux.context_s_t_ptr_set) + def __init__(self): + this = _selinux.new_context_s_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_context_s_t + __del__ = lambda self : None; +context_s_t_swigregister = _selinux.context_s_t_swigregister +context_s_t_swigregister(context_s_t) + + +def context_new(*args): + return _selinux.context_new(*args) +context_new = _selinux.context_new + +def context_str(*args): + return _selinux.context_str(*args) +context_str = _selinux.context_str + +def context_free(*args): + return _selinux.context_free(*args) +context_free = _selinux.context_free + +def context_type_get(*args): + return _selinux.context_type_get(*args) +context_type_get = _selinux.context_type_get + +def context_range_get(*args): + return _selinux.context_range_get(*args) +context_range_get = _selinux.context_range_get + +def context_role_get(*args): + return _selinux.context_role_get(*args) +context_role_get = _selinux.context_role_get + +def context_user_get(*args): + return _selinux.context_user_get(*args) +context_user_get = _selinux.context_user_get + +def context_type_set(*args): + return _selinux.context_type_set(*args) +context_type_set = _selinux.context_type_set + +def context_range_set(*args): + return _selinux.context_range_set(*args) +context_range_set = _selinux.context_range_set + +def context_role_set(*args): + return _selinux.context_role_set(*args) +context_role_set = _selinux.context_role_set + +def context_user_set(*args): + return _selinux.context_user_set(*args) +context_user_set = _selinux.context_user_set +SECCLASS_SECURITY = _selinux.SECCLASS_SECURITY +SECCLASS_PROCESS = _selinux.SECCLASS_PROCESS +SECCLASS_SYSTEM = _selinux.SECCLASS_SYSTEM +SECCLASS_CAPABILITY = _selinux.SECCLASS_CAPABILITY +SECCLASS_FILESYSTEM = _selinux.SECCLASS_FILESYSTEM +SECCLASS_FILE = _selinux.SECCLASS_FILE +SECCLASS_DIR = _selinux.SECCLASS_DIR +SECCLASS_FD = _selinux.SECCLASS_FD +SECCLASS_LNK_FILE = _selinux.SECCLASS_LNK_FILE +SECCLASS_CHR_FILE = _selinux.SECCLASS_CHR_FILE +SECCLASS_BLK_FILE = _selinux.SECCLASS_BLK_FILE +SECCLASS_SOCK_FILE = _selinux.SECCLASS_SOCK_FILE +SECCLASS_FIFO_FILE = _selinux.SECCLASS_FIFO_FILE +SECCLASS_SOCKET = _selinux.SECCLASS_SOCKET +SECCLASS_TCP_SOCKET = _selinux.SECCLASS_TCP_SOCKET +SECCLASS_UDP_SOCKET = _selinux.SECCLASS_UDP_SOCKET +SECCLASS_RAWIP_SOCKET = _selinux.SECCLASS_RAWIP_SOCKET +SECCLASS_NODE = _selinux.SECCLASS_NODE +SECCLASS_NETIF = _selinux.SECCLASS_NETIF +SECCLASS_NETLINK_SOCKET = _selinux.SECCLASS_NETLINK_SOCKET +SECCLASS_PACKET_SOCKET = _selinux.SECCLASS_PACKET_SOCKET +SECCLASS_KEY_SOCKET = _selinux.SECCLASS_KEY_SOCKET +SECCLASS_UNIX_STREAM_SOCKET = _selinux.SECCLASS_UNIX_STREAM_SOCKET +SECCLASS_UNIX_DGRAM_SOCKET = _selinux.SECCLASS_UNIX_DGRAM_SOCKET +SECCLASS_SEM = _selinux.SECCLASS_SEM +SECCLASS_MSG = _selinux.SECCLASS_MSG +SECCLASS_MSGQ = _selinux.SECCLASS_MSGQ +SECCLASS_SHM = _selinux.SECCLASS_SHM +SECCLASS_IPC = _selinux.SECCLASS_IPC +SECCLASS_PASSWD = _selinux.SECCLASS_PASSWD +SECCLASS_X_DRAWABLE = _selinux.SECCLASS_X_DRAWABLE +SECCLASS_X_SCREEN = _selinux.SECCLASS_X_SCREEN +SECCLASS_X_GC = _selinux.SECCLASS_X_GC +SECCLASS_X_FONT = _selinux.SECCLASS_X_FONT +SECCLASS_X_COLORMAP = _selinux.SECCLASS_X_COLORMAP +SECCLASS_X_PROPERTY = _selinux.SECCLASS_X_PROPERTY +SECCLASS_X_SELECTION = _selinux.SECCLASS_X_SELECTION +SECCLASS_X_CURSOR = _selinux.SECCLASS_X_CURSOR +SECCLASS_X_CLIENT = _selinux.SECCLASS_X_CLIENT +SECCLASS_X_DEVICE = _selinux.SECCLASS_X_DEVICE +SECCLASS_X_SERVER = _selinux.SECCLASS_X_SERVER +SECCLASS_X_EXTENSION = _selinux.SECCLASS_X_EXTENSION +SECCLASS_NETLINK_ROUTE_SOCKET = _selinux.SECCLASS_NETLINK_ROUTE_SOCKET +SECCLASS_NETLINK_FIREWALL_SOCKET = _selinux.SECCLASS_NETLINK_FIREWALL_SOCKET +SECCLASS_NETLINK_TCPDIAG_SOCKET = _selinux.SECCLASS_NETLINK_TCPDIAG_SOCKET +SECCLASS_NETLINK_NFLOG_SOCKET = _selinux.SECCLASS_NETLINK_NFLOG_SOCKET +SECCLASS_NETLINK_XFRM_SOCKET = _selinux.SECCLASS_NETLINK_XFRM_SOCKET +SECCLASS_NETLINK_SELINUX_SOCKET = _selinux.SECCLASS_NETLINK_SELINUX_SOCKET +SECCLASS_NETLINK_AUDIT_SOCKET = _selinux.SECCLASS_NETLINK_AUDIT_SOCKET +SECCLASS_NETLINK_IP6FW_SOCKET = _selinux.SECCLASS_NETLINK_IP6FW_SOCKET +SECCLASS_NETLINK_DNRT_SOCKET = _selinux.SECCLASS_NETLINK_DNRT_SOCKET +SECCLASS_DBUS = _selinux.SECCLASS_DBUS +SECCLASS_NSCD = _selinux.SECCLASS_NSCD +SECCLASS_ASSOCIATION = _selinux.SECCLASS_ASSOCIATION +SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET = _selinux.SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET +SECCLASS_APPLETALK_SOCKET = _selinux.SECCLASS_APPLETALK_SOCKET +SECCLASS_PACKET = _selinux.SECCLASS_PACKET +SECCLASS_KEY = _selinux.SECCLASS_KEY +SECCLASS_CONTEXT = _selinux.SECCLASS_CONTEXT +SECCLASS_DCCP_SOCKET = _selinux.SECCLASS_DCCP_SOCKET +SECCLASS_MEMPROTECT = _selinux.SECCLASS_MEMPROTECT +SECCLASS_DB_DATABASE = _selinux.SECCLASS_DB_DATABASE +SECCLASS_DB_TABLE = _selinux.SECCLASS_DB_TABLE +SECCLASS_DB_PROCEDURE = _selinux.SECCLASS_DB_PROCEDURE +SECCLASS_DB_COLUMN = _selinux.SECCLASS_DB_COLUMN +SECCLASS_DB_TUPLE = _selinux.SECCLASS_DB_TUPLE +SECCLASS_DB_BLOB = _selinux.SECCLASS_DB_BLOB +SECCLASS_PEER = _selinux.SECCLASS_PEER +SECCLASS_CAPABILITY2 = _selinux.SECCLASS_CAPABILITY2 +SECCLASS_X_RESOURCE = _selinux.SECCLASS_X_RESOURCE +SECCLASS_X_EVENT = _selinux.SECCLASS_X_EVENT +SECCLASS_X_SYNTHETIC_EVENT = _selinux.SECCLASS_X_SYNTHETIC_EVENT +SECCLASS_X_APPLICATION_DATA = _selinux.SECCLASS_X_APPLICATION_DATA +SECINITSID_KERNEL = _selinux.SECINITSID_KERNEL +SECINITSID_SECURITY = _selinux.SECINITSID_SECURITY +SECINITSID_UNLABELED = _selinux.SECINITSID_UNLABELED +SECINITSID_FS = _selinux.SECINITSID_FS +SECINITSID_FILE = _selinux.SECINITSID_FILE +SECINITSID_FILE_LABELS = _selinux.SECINITSID_FILE_LABELS +SECINITSID_INIT = _selinux.SECINITSID_INIT +SECINITSID_ANY_SOCKET = _selinux.SECINITSID_ANY_SOCKET +SECINITSID_PORT = _selinux.SECINITSID_PORT +SECINITSID_NETIF = _selinux.SECINITSID_NETIF +SECINITSID_NETMSG = _selinux.SECINITSID_NETMSG +SECINITSID_NODE = _selinux.SECINITSID_NODE +SECINITSID_IGMP_PACKET = _selinux.SECINITSID_IGMP_PACKET +SECINITSID_ICMP_SOCKET = _selinux.SECINITSID_ICMP_SOCKET +SECINITSID_TCP_SOCKET = _selinux.SECINITSID_TCP_SOCKET +SECINITSID_SYSCTL_MODPROBE = _selinux.SECINITSID_SYSCTL_MODPROBE +SECINITSID_SYSCTL = _selinux.SECINITSID_SYSCTL +SECINITSID_SYSCTL_FS = _selinux.SECINITSID_SYSCTL_FS +SECINITSID_SYSCTL_KERNEL = _selinux.SECINITSID_SYSCTL_KERNEL +SECINITSID_SYSCTL_NET = _selinux.SECINITSID_SYSCTL_NET +SECINITSID_SYSCTL_NET_UNIX = _selinux.SECINITSID_SYSCTL_NET_UNIX +SECINITSID_SYSCTL_VM = _selinux.SECINITSID_SYSCTL_VM +SECINITSID_SYSCTL_DEV = _selinux.SECINITSID_SYSCTL_DEV +SECINITSID_KMOD = _selinux.SECINITSID_KMOD +SECINITSID_POLICY = _selinux.SECINITSID_POLICY +SECINITSID_SCMP_PACKET = _selinux.SECINITSID_SCMP_PACKET +SECINITSID_DEVNULL = _selinux.SECINITSID_DEVNULL +SECINITSID_NUM = _selinux.SECINITSID_NUM +SELINUX_DEFAULTUSER = _selinux.SELINUX_DEFAULTUSER + +def get_ordered_context_list(*args): + return _selinux.get_ordered_context_list(*args) +get_ordered_context_list = _selinux.get_ordered_context_list + +def get_ordered_context_list_with_level(*args): + return _selinux.get_ordered_context_list_with_level(*args) +get_ordered_context_list_with_level = _selinux.get_ordered_context_list_with_level + +def get_default_context(*args): + return _selinux.get_default_context(*args) +get_default_context = _selinux.get_default_context + +def get_default_context_with_level(*args): + return _selinux.get_default_context_with_level(*args) +get_default_context_with_level = _selinux.get_default_context_with_level + +def get_default_context_with_role(*args): + return _selinux.get_default_context_with_role(*args) +get_default_context_with_role = _selinux.get_default_context_with_role + +def get_default_context_with_rolelevel(*args): + return _selinux.get_default_context_with_rolelevel(*args) +get_default_context_with_rolelevel = _selinux.get_default_context_with_rolelevel + +def query_user_context(): + return _selinux.query_user_context() +query_user_context = _selinux.query_user_context + +def manual_user_enter_context(*args): + return _selinux.manual_user_enter_context(*args) +manual_user_enter_context = _selinux.manual_user_enter_context + +def selinux_default_type_path(): + return _selinux.selinux_default_type_path() +selinux_default_type_path = _selinux.selinux_default_type_path + +def get_default_type(*args): + return _selinux.get_default_type(*args) +get_default_type = _selinux.get_default_type +SELABEL_CTX_FILE = _selinux.SELABEL_CTX_FILE +SELABEL_CTX_MEDIA = _selinux.SELABEL_CTX_MEDIA +SELABEL_CTX_X = _selinux.SELABEL_CTX_X +SELABEL_CTX_DB = _selinux.SELABEL_CTX_DB +SELABEL_CTX_ANDROID_PROP = _selinux.SELABEL_CTX_ANDROID_PROP +SELABEL_OPT_UNUSED = _selinux.SELABEL_OPT_UNUSED +SELABEL_OPT_VALIDATE = _selinux.SELABEL_OPT_VALIDATE +SELABEL_OPT_BASEONLY = _selinux.SELABEL_OPT_BASEONLY +SELABEL_OPT_PATH = _selinux.SELABEL_OPT_PATH +SELABEL_OPT_SUBSET = _selinux.SELABEL_OPT_SUBSET +SELABEL_NOPT = _selinux.SELABEL_NOPT + +def selabel_open(*args): + return _selinux.selabel_open(*args) +selabel_open = _selinux.selabel_open + +def selabel_close(*args): + return _selinux.selabel_close(*args) +selabel_close = _selinux.selabel_close + +def selabel_lookup(*args): + return _selinux.selabel_lookup(*args) +selabel_lookup = _selinux.selabel_lookup + +def selabel_lookup_raw(*args): + return _selinux.selabel_lookup_raw(*args) +selabel_lookup_raw = _selinux.selabel_lookup_raw + +def selabel_partial_match(*args): + return _selinux.selabel_partial_match(*args) +selabel_partial_match = _selinux.selabel_partial_match + +def selabel_lookup_best_match(*args): + return _selinux.selabel_lookup_best_match(*args) +selabel_lookup_best_match = _selinux.selabel_lookup_best_match + +def selabel_lookup_best_match_raw(*args): + return _selinux.selabel_lookup_best_match_raw(*args) +selabel_lookup_best_match_raw = _selinux.selabel_lookup_best_match_raw + +def selabel_stats(*args): + return _selinux.selabel_stats(*args) +selabel_stats = _selinux.selabel_stats +SELABEL_X_PROP = _selinux.SELABEL_X_PROP +SELABEL_X_EXT = _selinux.SELABEL_X_EXT +SELABEL_X_CLIENT = _selinux.SELABEL_X_CLIENT +SELABEL_X_EVENT = _selinux.SELABEL_X_EVENT +SELABEL_X_SELN = _selinux.SELABEL_X_SELN +SELABEL_X_POLYPROP = _selinux.SELABEL_X_POLYPROP +SELABEL_X_POLYSELN = _selinux.SELABEL_X_POLYSELN +SELABEL_DB_DATABASE = _selinux.SELABEL_DB_DATABASE +SELABEL_DB_SCHEMA = _selinux.SELABEL_DB_SCHEMA +SELABEL_DB_TABLE = _selinux.SELABEL_DB_TABLE +SELABEL_DB_COLUMN = _selinux.SELABEL_DB_COLUMN +SELABEL_DB_SEQUENCE = _selinux.SELABEL_DB_SEQUENCE +SELABEL_DB_VIEW = _selinux.SELABEL_DB_VIEW +SELABEL_DB_PROCEDURE = _selinux.SELABEL_DB_PROCEDURE +SELABEL_DB_BLOB = _selinux.SELABEL_DB_BLOB +SELABEL_DB_TUPLE = _selinux.SELABEL_DB_TUPLE +SELABEL_DB_LANGUAGE = _selinux.SELABEL_DB_LANGUAGE +SELABEL_DB_EXCEPTION = _selinux.SELABEL_DB_EXCEPTION +SELABEL_DB_DATATYPE = _selinux.SELABEL_DB_DATATYPE + +def is_selinux_enabled(): + return _selinux.is_selinux_enabled() +is_selinux_enabled = _selinux.is_selinux_enabled + +def is_selinux_mls_enabled(): + return _selinux.is_selinux_mls_enabled() +is_selinux_mls_enabled = _selinux.is_selinux_mls_enabled + +def getcon(): + return _selinux.getcon() +getcon = _selinux.getcon + +def getcon_raw(): + return _selinux.getcon_raw() +getcon_raw = _selinux.getcon_raw + +def setcon(*args): + return _selinux.setcon(*args) +setcon = _selinux.setcon + +def setcon_raw(*args): + return _selinux.setcon_raw(*args) +setcon_raw = _selinux.setcon_raw + +def getpidcon(*args): + return _selinux.getpidcon(*args) +getpidcon = _selinux.getpidcon + +def getpidcon_raw(*args): + return _selinux.getpidcon_raw(*args) +getpidcon_raw = _selinux.getpidcon_raw + +def getprevcon(): + return _selinux.getprevcon() +getprevcon = _selinux.getprevcon + +def getprevcon_raw(): + return _selinux.getprevcon_raw() +getprevcon_raw = _selinux.getprevcon_raw + +def getexeccon(): + return _selinux.getexeccon() +getexeccon = _selinux.getexeccon + +def getexeccon_raw(): + return _selinux.getexeccon_raw() +getexeccon_raw = _selinux.getexeccon_raw + +def setexeccon(*args): + return _selinux.setexeccon(*args) +setexeccon = _selinux.setexeccon + +def setexeccon_raw(*args): + return _selinux.setexeccon_raw(*args) +setexeccon_raw = _selinux.setexeccon_raw + +def getfscreatecon(): + return _selinux.getfscreatecon() +getfscreatecon = _selinux.getfscreatecon + +def getfscreatecon_raw(): + return _selinux.getfscreatecon_raw() +getfscreatecon_raw = _selinux.getfscreatecon_raw + +def setfscreatecon(*args): + return _selinux.setfscreatecon(*args) +setfscreatecon = _selinux.setfscreatecon + +def setfscreatecon_raw(*args): + return _selinux.setfscreatecon_raw(*args) +setfscreatecon_raw = _selinux.setfscreatecon_raw + +def getkeycreatecon(): + return _selinux.getkeycreatecon() +getkeycreatecon = _selinux.getkeycreatecon + +def getkeycreatecon_raw(): + return _selinux.getkeycreatecon_raw() +getkeycreatecon_raw = _selinux.getkeycreatecon_raw + +def setkeycreatecon(*args): + return _selinux.setkeycreatecon(*args) +setkeycreatecon = _selinux.setkeycreatecon + +def setkeycreatecon_raw(*args): + return _selinux.setkeycreatecon_raw(*args) +setkeycreatecon_raw = _selinux.setkeycreatecon_raw + +def getsockcreatecon(): + return _selinux.getsockcreatecon() +getsockcreatecon = _selinux.getsockcreatecon + +def getsockcreatecon_raw(): + return _selinux.getsockcreatecon_raw() +getsockcreatecon_raw = _selinux.getsockcreatecon_raw + +def setsockcreatecon(*args): + return _selinux.setsockcreatecon(*args) +setsockcreatecon = _selinux.setsockcreatecon + +def setsockcreatecon_raw(*args): + return _selinux.setsockcreatecon_raw(*args) +setsockcreatecon_raw = _selinux.setsockcreatecon_raw + +def getfilecon(*args): + return _selinux.getfilecon(*args) +getfilecon = _selinux.getfilecon + +def getfilecon_raw(*args): + return _selinux.getfilecon_raw(*args) +getfilecon_raw = _selinux.getfilecon_raw + +def lgetfilecon(*args): + return _selinux.lgetfilecon(*args) +lgetfilecon = _selinux.lgetfilecon + +def lgetfilecon_raw(*args): + return _selinux.lgetfilecon_raw(*args) +lgetfilecon_raw = _selinux.lgetfilecon_raw + +def fgetfilecon(*args): + return _selinux.fgetfilecon(*args) +fgetfilecon = _selinux.fgetfilecon + +def fgetfilecon_raw(*args): + return _selinux.fgetfilecon_raw(*args) +fgetfilecon_raw = _selinux.fgetfilecon_raw + +def setfilecon(*args): + return _selinux.setfilecon(*args) +setfilecon = _selinux.setfilecon + +def setfilecon_raw(*args): + return _selinux.setfilecon_raw(*args) +setfilecon_raw = _selinux.setfilecon_raw + +def lsetfilecon(*args): + return _selinux.lsetfilecon(*args) +lsetfilecon = _selinux.lsetfilecon + +def lsetfilecon_raw(*args): + return _selinux.lsetfilecon_raw(*args) +lsetfilecon_raw = _selinux.lsetfilecon_raw + +def fsetfilecon(*args): + return _selinux.fsetfilecon(*args) +fsetfilecon = _selinux.fsetfilecon + +def fsetfilecon_raw(*args): + return _selinux.fsetfilecon_raw(*args) +fsetfilecon_raw = _selinux.fsetfilecon_raw + +def getpeercon(*args): + return _selinux.getpeercon(*args) +getpeercon = _selinux.getpeercon + +def getpeercon_raw(*args): + return _selinux.getpeercon_raw(*args) +getpeercon_raw = _selinux.getpeercon_raw +class av_decision(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, av_decision, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, av_decision, name) + __repr__ = _swig_repr + __swig_setmethods__["allowed"] = _selinux.av_decision_allowed_set + __swig_getmethods__["allowed"] = _selinux.av_decision_allowed_get + if _newclass:allowed = _swig_property(_selinux.av_decision_allowed_get, _selinux.av_decision_allowed_set) + __swig_setmethods__["decided"] = _selinux.av_decision_decided_set + __swig_getmethods__["decided"] = _selinux.av_decision_decided_get + if _newclass:decided = _swig_property(_selinux.av_decision_decided_get, _selinux.av_decision_decided_set) + __swig_setmethods__["auditallow"] = _selinux.av_decision_auditallow_set + __swig_getmethods__["auditallow"] = _selinux.av_decision_auditallow_get + if _newclass:auditallow = _swig_property(_selinux.av_decision_auditallow_get, _selinux.av_decision_auditallow_set) + __swig_setmethods__["auditdeny"] = _selinux.av_decision_auditdeny_set + __swig_getmethods__["auditdeny"] = _selinux.av_decision_auditdeny_get + if _newclass:auditdeny = _swig_property(_selinux.av_decision_auditdeny_get, _selinux.av_decision_auditdeny_set) + __swig_setmethods__["seqno"] = _selinux.av_decision_seqno_set + __swig_getmethods__["seqno"] = _selinux.av_decision_seqno_get + if _newclass:seqno = _swig_property(_selinux.av_decision_seqno_get, _selinux.av_decision_seqno_set) + __swig_setmethods__["flags"] = _selinux.av_decision_flags_set + __swig_getmethods__["flags"] = _selinux.av_decision_flags_get + if _newclass:flags = _swig_property(_selinux.av_decision_flags_get, _selinux.av_decision_flags_set) + def __init__(self): + this = _selinux.new_av_decision() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_av_decision + __del__ = lambda self : None; +av_decision_swigregister = _selinux.av_decision_swigregister +av_decision_swigregister(av_decision) + +SELINUX_AVD_FLAGS_PERMISSIVE = _selinux.SELINUX_AVD_FLAGS_PERMISSIVE +class selinux_opt(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, selinux_opt, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, selinux_opt, name) + __repr__ = _swig_repr + __swig_setmethods__["type"] = _selinux.selinux_opt_type_set + __swig_getmethods__["type"] = _selinux.selinux_opt_type_get + if _newclass:type = _swig_property(_selinux.selinux_opt_type_get, _selinux.selinux_opt_type_set) + __swig_setmethods__["value"] = _selinux.selinux_opt_value_set + __swig_getmethods__["value"] = _selinux.selinux_opt_value_get + if _newclass:value = _swig_property(_selinux.selinux_opt_value_get, _selinux.selinux_opt_value_set) + def __init__(self): + this = _selinux.new_selinux_opt() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_selinux_opt + __del__ = lambda self : None; +selinux_opt_swigregister = _selinux.selinux_opt_swigregister +selinux_opt_swigregister(selinux_opt) + +class selinux_callback(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, selinux_callback, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, selinux_callback, name) + __repr__ = _swig_repr + __swig_setmethods__["func_log"] = _selinux.selinux_callback_func_log_set + __swig_getmethods__["func_log"] = _selinux.selinux_callback_func_log_get + if _newclass:func_log = _swig_property(_selinux.selinux_callback_func_log_get, _selinux.selinux_callback_func_log_set) + __swig_setmethods__["func_audit"] = _selinux.selinux_callback_func_audit_set + __swig_getmethods__["func_audit"] = _selinux.selinux_callback_func_audit_get + if _newclass:func_audit = _swig_property(_selinux.selinux_callback_func_audit_get, _selinux.selinux_callback_func_audit_set) + __swig_setmethods__["func_validate"] = _selinux.selinux_callback_func_validate_set + __swig_getmethods__["func_validate"] = _selinux.selinux_callback_func_validate_get + if _newclass:func_validate = _swig_property(_selinux.selinux_callback_func_validate_get, _selinux.selinux_callback_func_validate_set) + __swig_setmethods__["func_setenforce"] = _selinux.selinux_callback_func_setenforce_set + __swig_getmethods__["func_setenforce"] = _selinux.selinux_callback_func_setenforce_get + if _newclass:func_setenforce = _swig_property(_selinux.selinux_callback_func_setenforce_get, _selinux.selinux_callback_func_setenforce_set) + __swig_setmethods__["func_policyload"] = _selinux.selinux_callback_func_policyload_set + __swig_getmethods__["func_policyload"] = _selinux.selinux_callback_func_policyload_get + if _newclass:func_policyload = _swig_property(_selinux.selinux_callback_func_policyload_get, _selinux.selinux_callback_func_policyload_set) + def __init__(self): + this = _selinux.new_selinux_callback() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_selinux_callback + __del__ = lambda self : None; +selinux_callback_swigregister = _selinux.selinux_callback_swigregister +selinux_callback_swigregister(selinux_callback) + +SELINUX_CB_LOG = _selinux.SELINUX_CB_LOG +SELINUX_CB_AUDIT = _selinux.SELINUX_CB_AUDIT +SELINUX_CB_VALIDATE = _selinux.SELINUX_CB_VALIDATE +SELINUX_CB_SETENFORCE = _selinux.SELINUX_CB_SETENFORCE +SELINUX_CB_POLICYLOAD = _selinux.SELINUX_CB_POLICYLOAD + +def selinux_get_callback(*args): + return _selinux.selinux_get_callback(*args) +selinux_get_callback = _selinux.selinux_get_callback + +def selinux_set_callback(*args): + return _selinux.selinux_set_callback(*args) +selinux_set_callback = _selinux.selinux_set_callback +SELINUX_ERROR = _selinux.SELINUX_ERROR +SELINUX_WARNING = _selinux.SELINUX_WARNING +SELINUX_INFO = _selinux.SELINUX_INFO +SELINUX_AVC = _selinux.SELINUX_AVC +SELINUX_TRANS_DIR = _selinux.SELINUX_TRANS_DIR + +def security_compute_av(*args): + return _selinux.security_compute_av(*args) +security_compute_av = _selinux.security_compute_av + +def security_compute_av_raw(*args): + return _selinux.security_compute_av_raw(*args) +security_compute_av_raw = _selinux.security_compute_av_raw + +def security_compute_av_flags(*args): + return _selinux.security_compute_av_flags(*args) +security_compute_av_flags = _selinux.security_compute_av_flags + +def security_compute_av_flags_raw(*args): + return _selinux.security_compute_av_flags_raw(*args) +security_compute_av_flags_raw = _selinux.security_compute_av_flags_raw + +def security_compute_create(*args): + return _selinux.security_compute_create(*args) +security_compute_create = _selinux.security_compute_create + +def security_compute_create_raw(*args): + return _selinux.security_compute_create_raw(*args) +security_compute_create_raw = _selinux.security_compute_create_raw + +def security_compute_create_name(*args): + return _selinux.security_compute_create_name(*args) +security_compute_create_name = _selinux.security_compute_create_name + +def security_compute_create_name_raw(*args): + return _selinux.security_compute_create_name_raw(*args) +security_compute_create_name_raw = _selinux.security_compute_create_name_raw + +def security_compute_relabel(*args): + return _selinux.security_compute_relabel(*args) +security_compute_relabel = _selinux.security_compute_relabel + +def security_compute_relabel_raw(*args): + return _selinux.security_compute_relabel_raw(*args) +security_compute_relabel_raw = _selinux.security_compute_relabel_raw + +def security_compute_member(*args): + return _selinux.security_compute_member(*args) +security_compute_member = _selinux.security_compute_member + +def security_compute_member_raw(*args): + return _selinux.security_compute_member_raw(*args) +security_compute_member_raw = _selinux.security_compute_member_raw + +def security_compute_user(*args): + return _selinux.security_compute_user(*args) +security_compute_user = _selinux.security_compute_user + +def security_compute_user_raw(*args): + return _selinux.security_compute_user_raw(*args) +security_compute_user_raw = _selinux.security_compute_user_raw + +def security_load_policy(*args): + return _selinux.security_load_policy(*args) +security_load_policy = _selinux.security_load_policy + +def security_get_initial_context(*args): + return _selinux.security_get_initial_context(*args) +security_get_initial_context = _selinux.security_get_initial_context + +def security_get_initial_context_raw(*args): + return _selinux.security_get_initial_context_raw(*args) +security_get_initial_context_raw = _selinux.security_get_initial_context_raw + +def selinux_mkload_policy(*args): + return _selinux.selinux_mkload_policy(*args) +selinux_mkload_policy = _selinux.selinux_mkload_policy + +def selinux_init_load_policy(): + return _selinux.selinux_init_load_policy() +selinux_init_load_policy = _selinux.selinux_init_load_policy +class SELboolean(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, SELboolean, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, SELboolean, name) + __repr__ = _swig_repr + __swig_setmethods__["name"] = _selinux.SELboolean_name_set + __swig_getmethods__["name"] = _selinux.SELboolean_name_get + if _newclass:name = _swig_property(_selinux.SELboolean_name_get, _selinux.SELboolean_name_set) + __swig_setmethods__["value"] = _selinux.SELboolean_value_set + __swig_getmethods__["value"] = _selinux.SELboolean_value_get + if _newclass:value = _swig_property(_selinux.SELboolean_value_get, _selinux.SELboolean_value_set) + def __init__(self): + this = _selinux.new_SELboolean() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_SELboolean + __del__ = lambda self : None; +SELboolean_swigregister = _selinux.SELboolean_swigregister +SELboolean_swigregister(SELboolean) + + +def security_set_boolean_list(*args): + return _selinux.security_set_boolean_list(*args) +security_set_boolean_list = _selinux.security_set_boolean_list + +def security_load_booleans(*args): + return _selinux.security_load_booleans(*args) +security_load_booleans = _selinux.security_load_booleans + +def security_check_context(*args): + return _selinux.security_check_context(*args) +security_check_context = _selinux.security_check_context + +def security_check_context_raw(*args): + return _selinux.security_check_context_raw(*args) +security_check_context_raw = _selinux.security_check_context_raw + +def security_canonicalize_context(*args): + return _selinux.security_canonicalize_context(*args) +security_canonicalize_context = _selinux.security_canonicalize_context + +def security_canonicalize_context_raw(*args): + return _selinux.security_canonicalize_context_raw(*args) +security_canonicalize_context_raw = _selinux.security_canonicalize_context_raw + +def security_getenforce(): + return _selinux.security_getenforce() +security_getenforce = _selinux.security_getenforce + +def security_setenforce(*args): + return _selinux.security_setenforce(*args) +security_setenforce = _selinux.security_setenforce + +def security_deny_unknown(): + return _selinux.security_deny_unknown() +security_deny_unknown = _selinux.security_deny_unknown + +def security_disable(): + return _selinux.security_disable() +security_disable = _selinux.security_disable + +def security_policyvers(): + return _selinux.security_policyvers() +security_policyvers = _selinux.security_policyvers + +def security_get_boolean_names(): + return _selinux.security_get_boolean_names() +security_get_boolean_names = _selinux.security_get_boolean_names + +def security_get_boolean_pending(*args): + return _selinux.security_get_boolean_pending(*args) +security_get_boolean_pending = _selinux.security_get_boolean_pending + +def security_get_boolean_active(*args): + return _selinux.security_get_boolean_active(*args) +security_get_boolean_active = _selinux.security_get_boolean_active + +def security_set_boolean(*args): + return _selinux.security_set_boolean(*args) +security_set_boolean = _selinux.security_set_boolean + +def security_commit_booleans(): + return _selinux.security_commit_booleans() +security_commit_booleans = _selinux.security_commit_booleans +class security_class_mapping(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, security_class_mapping, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, security_class_mapping, name) + __repr__ = _swig_repr + __swig_setmethods__["name"] = _selinux.security_class_mapping_name_set + __swig_getmethods__["name"] = _selinux.security_class_mapping_name_get + if _newclass:name = _swig_property(_selinux.security_class_mapping_name_get, _selinux.security_class_mapping_name_set) + __swig_setmethods__["perms"] = _selinux.security_class_mapping_perms_set + __swig_getmethods__["perms"] = _selinux.security_class_mapping_perms_get + if _newclass:perms = _swig_property(_selinux.security_class_mapping_perms_get, _selinux.security_class_mapping_perms_set) + def __init__(self): + this = _selinux.new_security_class_mapping() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _selinux.delete_security_class_mapping + __del__ = lambda self : None; +security_class_mapping_swigregister = _selinux.security_class_mapping_swigregister +security_class_mapping_swigregister(security_class_mapping) + + +def selinux_set_mapping(*args): + return _selinux.selinux_set_mapping(*args) +selinux_set_mapping = _selinux.selinux_set_mapping + +def mode_to_security_class(*args): + return _selinux.mode_to_security_class(*args) +mode_to_security_class = _selinux.mode_to_security_class + +def string_to_security_class(*args): + return _selinux.string_to_security_class(*args) +string_to_security_class = _selinux.string_to_security_class + +def security_class_to_string(*args): + return _selinux.security_class_to_string(*args) +security_class_to_string = _selinux.security_class_to_string + +def security_av_perm_to_string(*args): + return _selinux.security_av_perm_to_string(*args) +security_av_perm_to_string = _selinux.security_av_perm_to_string + +def string_to_av_perm(*args): + return _selinux.string_to_av_perm(*args) +string_to_av_perm = _selinux.string_to_av_perm + +def security_av_string(*args): + return _selinux.security_av_string(*args) +security_av_string = _selinux.security_av_string + +def print_access_vector(*args): + return _selinux.print_access_vector(*args) +print_access_vector = _selinux.print_access_vector +MATCHPATHCON_BASEONLY = _selinux.MATCHPATHCON_BASEONLY +MATCHPATHCON_NOTRANS = _selinux.MATCHPATHCON_NOTRANS +MATCHPATHCON_VALIDATE = _selinux.MATCHPATHCON_VALIDATE + +def set_matchpathcon_flags(*args): + return _selinux.set_matchpathcon_flags(*args) +set_matchpathcon_flags = _selinux.set_matchpathcon_flags + +def matchpathcon_init(*args): + return _selinux.matchpathcon_init(*args) +matchpathcon_init = _selinux.matchpathcon_init + +def matchpathcon_init_prefix(*args): + return _selinux.matchpathcon_init_prefix(*args) +matchpathcon_init_prefix = _selinux.matchpathcon_init_prefix + +def matchpathcon_fini(): + return _selinux.matchpathcon_fini() +matchpathcon_fini = _selinux.matchpathcon_fini + +def realpath_not_final(*args): + return _selinux.realpath_not_final(*args) +realpath_not_final = _selinux.realpath_not_final + +def matchpathcon(*args): + return _selinux.matchpathcon(*args) +matchpathcon = _selinux.matchpathcon + +def matchpathcon_index(*args): + return _selinux.matchpathcon_index(*args) +matchpathcon_index = _selinux.matchpathcon_index + +def matchpathcon_filespec_add(*args): + return _selinux.matchpathcon_filespec_add(*args) +matchpathcon_filespec_add = _selinux.matchpathcon_filespec_add + +def matchpathcon_filespec_destroy(): + return _selinux.matchpathcon_filespec_destroy() +matchpathcon_filespec_destroy = _selinux.matchpathcon_filespec_destroy + +def matchpathcon_filespec_eval(): + return _selinux.matchpathcon_filespec_eval() +matchpathcon_filespec_eval = _selinux.matchpathcon_filespec_eval + +def matchpathcon_checkmatches(*args): + return _selinux.matchpathcon_checkmatches(*args) +matchpathcon_checkmatches = _selinux.matchpathcon_checkmatches + +def matchmediacon(*args): + return _selinux.matchmediacon(*args) +matchmediacon = _selinux.matchmediacon + +def selinux_getenforcemode(): + return _selinux.selinux_getenforcemode() +selinux_getenforcemode = _selinux.selinux_getenforcemode + +def selinux_boolean_sub(*args): + return _selinux.selinux_boolean_sub(*args) +selinux_boolean_sub = _selinux.selinux_boolean_sub + +def selinux_getpolicytype(): + return _selinux.selinux_getpolicytype() +selinux_getpolicytype = _selinux.selinux_getpolicytype + +def selinux_policy_root(): + return _selinux.selinux_policy_root() +selinux_policy_root = _selinux.selinux_policy_root + +def selinux_set_policy_root(*args): + return _selinux.selinux_set_policy_root(*args) +selinux_set_policy_root = _selinux.selinux_set_policy_root + +def selinux_current_policy_path(): + return _selinux.selinux_current_policy_path() +selinux_current_policy_path = _selinux.selinux_current_policy_path + +def selinux_binary_policy_path(): + return _selinux.selinux_binary_policy_path() +selinux_binary_policy_path = _selinux.selinux_binary_policy_path + +def selinux_failsafe_context_path(): + return _selinux.selinux_failsafe_context_path() +selinux_failsafe_context_path = _selinux.selinux_failsafe_context_path + +def selinux_removable_context_path(): + return _selinux.selinux_removable_context_path() +selinux_removable_context_path = _selinux.selinux_removable_context_path + +def selinux_default_context_path(): + return _selinux.selinux_default_context_path() +selinux_default_context_path = _selinux.selinux_default_context_path + +def selinux_user_contexts_path(): + return _selinux.selinux_user_contexts_path() +selinux_user_contexts_path = _selinux.selinux_user_contexts_path + +def selinux_file_context_path(): + return _selinux.selinux_file_context_path() +selinux_file_context_path = _selinux.selinux_file_context_path + +def selinux_file_context_homedir_path(): + return _selinux.selinux_file_context_homedir_path() +selinux_file_context_homedir_path = _selinux.selinux_file_context_homedir_path + +def selinux_file_context_local_path(): + return _selinux.selinux_file_context_local_path() +selinux_file_context_local_path = _selinux.selinux_file_context_local_path + +def selinux_file_context_subs_path(): + return _selinux.selinux_file_context_subs_path() +selinux_file_context_subs_path = _selinux.selinux_file_context_subs_path + +def selinux_file_context_subs_dist_path(): + return _selinux.selinux_file_context_subs_dist_path() +selinux_file_context_subs_dist_path = _selinux.selinux_file_context_subs_dist_path + +def selinux_homedir_context_path(): + return _selinux.selinux_homedir_context_path() +selinux_homedir_context_path = _selinux.selinux_homedir_context_path + +def selinux_media_context_path(): + return _selinux.selinux_media_context_path() +selinux_media_context_path = _selinux.selinux_media_context_path + +def selinux_virtual_domain_context_path(): + return _selinux.selinux_virtual_domain_context_path() +selinux_virtual_domain_context_path = _selinux.selinux_virtual_domain_context_path + +def selinux_virtual_image_context_path(): + return _selinux.selinux_virtual_image_context_path() +selinux_virtual_image_context_path = _selinux.selinux_virtual_image_context_path + +def selinux_lxc_contexts_path(): + return _selinux.selinux_lxc_contexts_path() +selinux_lxc_contexts_path = _selinux.selinux_lxc_contexts_path + +def selinux_x_context_path(): + return _selinux.selinux_x_context_path() +selinux_x_context_path = _selinux.selinux_x_context_path + +def selinux_sepgsql_context_path(): + return _selinux.selinux_sepgsql_context_path() +selinux_sepgsql_context_path = _selinux.selinux_sepgsql_context_path + +def selinux_systemd_contexts_path(): + return _selinux.selinux_systemd_contexts_path() +selinux_systemd_contexts_path = _selinux.selinux_systemd_contexts_path + +def selinux_contexts_path(): + return _selinux.selinux_contexts_path() +selinux_contexts_path = _selinux.selinux_contexts_path + +def selinux_securetty_types_path(): + return _selinux.selinux_securetty_types_path() +selinux_securetty_types_path = _selinux.selinux_securetty_types_path + +def selinux_booleans_subs_path(): + return _selinux.selinux_booleans_subs_path() +selinux_booleans_subs_path = _selinux.selinux_booleans_subs_path + +def selinux_booleans_path(): + return _selinux.selinux_booleans_path() +selinux_booleans_path = _selinux.selinux_booleans_path + +def selinux_customizable_types_path(): + return _selinux.selinux_customizable_types_path() +selinux_customizable_types_path = _selinux.selinux_customizable_types_path + +def selinux_users_path(): + return _selinux.selinux_users_path() +selinux_users_path = _selinux.selinux_users_path + +def selinux_usersconf_path(): + return _selinux.selinux_usersconf_path() +selinux_usersconf_path = _selinux.selinux_usersconf_path + +def selinux_translations_path(): + return _selinux.selinux_translations_path() +selinux_translations_path = _selinux.selinux_translations_path + +def selinux_colors_path(): + return _selinux.selinux_colors_path() +selinux_colors_path = _selinux.selinux_colors_path + +def selinux_netfilter_context_path(): + return _selinux.selinux_netfilter_context_path() +selinux_netfilter_context_path = _selinux.selinux_netfilter_context_path + +def selinux_path(): + return _selinux.selinux_path() +selinux_path = _selinux.selinux_path + +def selinux_check_access(*args): + return _selinux.selinux_check_access(*args) +selinux_check_access = _selinux.selinux_check_access + +def selinux_check_passwd_access(*args): + return _selinux.selinux_check_passwd_access(*args) +selinux_check_passwd_access = _selinux.selinux_check_passwd_access + +def checkPasswdAccess(*args): + return _selinux.checkPasswdAccess(*args) +checkPasswdAccess = _selinux.checkPasswdAccess + +def selinux_check_securetty_context(*args): + return _selinux.selinux_check_securetty_context(*args) +selinux_check_securetty_context = _selinux.selinux_check_securetty_context + +def set_selinuxmnt(*args): + return _selinux.set_selinuxmnt(*args) +set_selinuxmnt = _selinux.set_selinuxmnt + +def selinuxfs_exists(): + return _selinux.selinuxfs_exists() +selinuxfs_exists = _selinux.selinuxfs_exists + +def fini_selinuxmnt(): + return _selinux.fini_selinuxmnt() +fini_selinuxmnt = _selinux.fini_selinuxmnt + +def setexecfilecon(*args): + return _selinux.setexecfilecon(*args) +setexecfilecon = _selinux.setexecfilecon + +def rpm_execcon(*args): + return _selinux.rpm_execcon(*args) +rpm_execcon = _selinux.rpm_execcon + +def is_context_customizable(*args): + return _selinux.is_context_customizable(*args) +is_context_customizable = _selinux.is_context_customizable + +def selinux_trans_to_raw_context(*args): + return _selinux.selinux_trans_to_raw_context(*args) +selinux_trans_to_raw_context = _selinux.selinux_trans_to_raw_context + +def selinux_raw_to_trans_context(*args): + return _selinux.selinux_raw_to_trans_context(*args) +selinux_raw_to_trans_context = _selinux.selinux_raw_to_trans_context + +def selinux_raw_context_to_color(*args): + return _selinux.selinux_raw_context_to_color(*args) +selinux_raw_context_to_color = _selinux.selinux_raw_context_to_color + +def getseuserbyname(*args): + return _selinux.getseuserbyname(*args) +getseuserbyname = _selinux.getseuserbyname + +def getseuser(*args): + return _selinux.getseuser(*args) +getseuser = _selinux.getseuser + +def selinux_file_context_cmp(*args): + return _selinux.selinux_file_context_cmp(*args) +selinux_file_context_cmp = _selinux.selinux_file_context_cmp + +def selinux_file_context_verify(*args): + return _selinux.selinux_file_context_verify(*args) +selinux_file_context_verify = _selinux.selinux_file_context_verify + +def selinux_lsetfilecon_default(*args): + return _selinux.selinux_lsetfilecon_default(*args) +selinux_lsetfilecon_default = _selinux.selinux_lsetfilecon_default + +def selinux_reset_config(): + return _selinux.selinux_reset_config() +selinux_reset_config = _selinux.selinux_reset_config +# This file is compatible with both classic and new-style classes. + + diff --git a/lib/python2.7/site-packages/setoolsgui/selinux/_selinux.so b/lib/python2.7/site-packages/setoolsgui/selinux/_selinux.so new file mode 100755 index 0000000000000000000000000000000000000000..f4a045f31dd8fa1c83b213d2134fa277fc220b64 GIT binary patch literal 333725 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 literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/__init__.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/access.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/access.py new file mode 100644 index 0000000..cf13210 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/access.py @@ -0,0 +1,331 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes representing basic access. + +SELinux - at the most basic level - represents access as +the 4-tuple subject (type or context), target (type or context), +object class, permission. The policy language elaborates this basic +access to faciliate more concise rules (e.g., allow rules can have multiple +source or target types - see refpolicy for more information). + +This module has objects for representing the most basic access (AccessVector) +and sets of that access (AccessVectorSet). These objects are used in Madison +in a variety of ways, but they are the fundamental representation of access. +""" + +import refpolicy +from selinux import audit2why + +def is_idparam(id): + """Determine if an id is a paramater in the form $N, where N is + an integer. + + Returns: + True if the id is a paramater + False if the id is not a paramater + """ + if len(id) > 1 and id[0] == '$': + try: + int(id[1:]) + except ValueError: + return False + return True + else: + return False + +class AccessVector: + """ + An access vector is the basic unit of access in SELinux. + + Access vectors are the most basic representation of access within + SELinux. It represents the access a source type has to a target + type in terms of an object class and a set of permissions. + + Access vectors are distinct from AVRules in that they can only + store a single source type, target type, and object class. The + simplicity of AccessVectors makes them useful for storing access + in a form that is easy to search and compare. + + The source, target, and object are stored as string. No checking + done to verify that the strings are valid SELinux identifiers. + Identifiers in the form $N (where N is an integer) are reserved as + interface parameters and are treated as wild cards in many + circumstances. + + Properties: + .src_type - The source type allowed access. [String or None] + .tgt_type - The target type to which access is allowed. [String or None] + .obj_class - The object class to which access is allowed. [String or None] + .perms - The permissions allowed to the object class. [IdSet] + .audit_msgs - The audit messages that generated this access vector [List of strings] + """ + def __init__(self, init_list=None): + if init_list: + self.from_list(init_list) + else: + self.src_type = None + self.tgt_type = None + self.obj_class = None + self.perms = refpolicy.IdSet() + self.audit_msgs = [] + self.type = audit2why.TERULE + self.data = [] + + # The direction of the information flow represented by this + # access vector - used for matching + self.info_flow_dir = None + + def from_list(self, list): + """Initialize an access vector from a list. + + Initialize an access vector from a list treating the list as + positional arguments - i.e., 0 = src_type, 1 = tgt_type, etc. + All of the list elements 3 and greater are treated as perms. + For example, the list ['foo_t', 'bar_t', 'file', 'read', 'write'] + would create an access vector list with the source type 'foo_t', + target type 'bar_t', object class 'file', and permissions 'read' + and 'write'. + + This format is useful for very simple storage to strings or disc + (see to_list) and for initializing access vectors. + """ + if len(list) < 4: + raise ValueError("List must contain at least four elements %s" % str(list)) + self.src_type = list[0] + self.tgt_type = list[1] + self.obj_class = list[2] + self.perms = refpolicy.IdSet(list[3:]) + + def to_list(self): + """ + Convert an access vector to a list. + + Convert an access vector to a list treating the list as positional + values. See from_list for more information on how an access vector + is represented in a list. + """ + l = [self.src_type, self.tgt_type, self.obj_class] + l.extend(self.perms) + return l + + def __str__(self): + return self.to_string() + + def to_string(self): + return "allow %s %s:%s %s;" % (self.src_type, self.tgt_type, + self.obj_class, self.perms.to_space_str()) + + def __cmp__(self, other): + if self.src_type != other.src_type: + return cmp(self.src_type, other.src_type) + if self.tgt_type != other.tgt_type: + return cmp(self.tgt_type, other.tgt_type) + if self.obj_class != self.obj_class: + return cmp(self.obj_class, other.obj_class) + if len(self.perms) != len(other.perms): + return cmp(len(self.perms), len(other.perms)) + x = list(self.perms) + x.sort() + y = list(other.perms) + y.sort() + for pa, pb in zip(x, y): + if pa != pb: + return cmp(pa, pb) + return 0 + +def avrule_to_access_vectors(avrule): + """Convert an avrule into a list of access vectors. + + AccessVectors and AVRules are similary, but differ in that + an AVRule can more than one source type, target type, and + object class. This function expands a single avrule into a + list of one or more AccessVectors representing the access + defined in the AVRule. + + + """ + if isinstance(avrule, AccessVector): + return [avrule] + a = [] + for src_type in avrule.src_types: + for tgt_type in avrule.tgt_types: + for obj_class in avrule.obj_classes: + access = AccessVector() + access.src_type = src_type + access.tgt_type = tgt_type + access.obj_class = obj_class + access.perms = avrule.perms.copy() + a.append(access) + return a + +class AccessVectorSet: + """A non-overlapping set of access vectors. + + An AccessVectorSet is designed to store one or more access vectors + that are non-overlapping. Access can be added to the set + incrementally and access vectors will be added or merged as + necessary. For example, adding the following access vectors using + add_av: + allow $1 etc_t : read; + allow $1 etc_t : write; + allow $1 var_log_t : read; + Would result in an access vector set with the access vectors: + allow $1 etc_t : { read write}; + allow $1 var_log_t : read; + """ + def __init__(self): + """Initialize an access vector set. + """ + self.src = {} + # The information flow direction of this access vector + # set - see objectmodel.py for more information. This + # stored here to speed up searching - see matching.py. + self.info_dir = None + + def __iter__(self): + """Iterate over all of the unique access vectors in the set.""" + for tgts in self.src.values(): + for objs in tgts.values(): + for av in objs.values(): + yield av + + def __len__(self): + """Return the number of unique access vectors in the set. + + Because of the inernal representation of the access vector set, + __len__ is not a constant time operation. Worst case is O(N) + where N is the number of unique access vectors, but the common + case is probably better. + """ + l = 0 + for tgts in self.src.values(): + for objs in tgts.values(): + l += len(objs) + return l + + def to_list(self): + """Return the unique access vectors in the set as a list. + + The format of the returned list is a set of nested lists, + each access vector represented by a list. This format is + designed to be simply serializable to a file. + + For example, consider an access vector set with the following + access vectors: + allow $1 user_t : file read; + allow $1 etc_t : file { read write}; + to_list would return the following: + [[$1, user_t, file, read] + [$1, etc_t, file, read, write]] + + See AccessVector.to_list for more information. + """ + l = [] + for av in self: + l.append(av.to_list()) + + return l + + def from_list(self, l): + """Add access vectors stored in a list. + + See to list for more information on the list format that this + method accepts. + + This will add all of the access from the list. Any existing + access vectors in the set will be retained. + """ + for av in l: + self.add_av(AccessVector(av)) + + def add(self, src_type, tgt_type, obj_class, perms, audit_msg=None, avc_type=audit2why.TERULE, data=[]): + """Add an access vector to the set. + """ + tgt = self.src.setdefault(src_type, { }) + cls = tgt.setdefault(tgt_type, { }) + + if cls.has_key((obj_class, avc_type)): + access = cls[obj_class, avc_type] + else: + access = AccessVector() + access.src_type = src_type + access.tgt_type = tgt_type + access.obj_class = obj_class + access.data = data + access.type = avc_type + cls[obj_class, avc_type] = access + + access.perms.update(perms) + if audit_msg: + access.audit_msgs.append(audit_msg) + + def add_av(self, av, audit_msg=None): + """Add an access vector to the set.""" + self.add(av.src_type, av.tgt_type, av.obj_class, av.perms) + + +def avs_extract_types(avs): + types = refpolicy.IdSet() + for av in avs: + types.add(av.src_type) + types.add(av.tgt_type) + + return types + +def avs_extract_obj_perms(avs): + perms = { } + for av in avs: + if perms.has_key(av.obj_class): + s = perms[av.obj_class] + else: + s = refpolicy.IdSet() + perms[av.obj_class] = s + s.update(av.perms) + return perms + +class RoleTypeSet: + """A non-overlapping set of role type statements. + + This clas allows the incremental addition of role type statements and + maintains a non-overlapping list of statements. + """ + def __init__(self): + """Initialize an access vector set.""" + self.role_types = {} + + def __iter__(self): + """Iterate over all of the unique role allows statements in the set.""" + for role_type in self.role_types.values(): + yield role_type + + def __len__(self): + """Return the unique number of role allow statements.""" + return len(self.role_types.keys()) + + def add(self, role, type): + if self.role_types.has_key(role): + role_type = self.role_types[role] + else: + role_type = refpolicy.RoleType() + role_type.role = role + self.role_types[role] = role_type + + role_type.types.add(type) diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/audit.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/audit.py new file mode 100644 index 0000000..56919be --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/audit.py @@ -0,0 +1,549 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +import refpolicy +import access +import re +import sys + +# Convenience functions + +def get_audit_boot_msgs(): + """Obtain all of the avc and policy load messages from the audit + log. This function uses ausearch and requires that the current + process have sufficient rights to run ausearch. + + Returns: + string contain all of the audit messages returned by ausearch. + """ + import subprocess + import time + fd=open("/proc/uptime", "r") + off=float(fd.read().split()[0]) + fd.close + s = time.localtime(time.time() - off) + bootdate = time.strftime("%x", s) + boottime = time.strftime("%X", s) + output = subprocess.Popen(["/sbin/ausearch", "-m", "AVC,USER_AVC,MAC_POLICY_LOAD,DAEMON_START,SELINUX_ERR", "-ts", bootdate, boottime], + stdout=subprocess.PIPE).communicate()[0] + return output + +def get_audit_msgs(): + """Obtain all of the avc and policy load messages from the audit + log. This function uses ausearch and requires that the current + process have sufficient rights to run ausearch. + + Returns: + string contain all of the audit messages returned by ausearch. + """ + import subprocess + output = subprocess.Popen(["/sbin/ausearch", "-m", "AVC,USER_AVC,MAC_POLICY_LOAD,DAEMON_START,SELINUX_ERR"], + stdout=subprocess.PIPE).communicate()[0] + return output + +def get_dmesg_msgs(): + """Obtain all of the avc and policy load messages from /bin/dmesg. + + Returns: + string contain all of the audit messages returned by dmesg. + """ + import subprocess + output = subprocess.Popen(["/bin/dmesg"], + stdout=subprocess.PIPE).communicate()[0] + return output + +# Classes representing audit messages + +class AuditMessage: + """Base class for all objects representing audit messages. + + AuditMessage is a base class for all audit messages and only + provides storage for the raw message (as a string) and a + parsing function that does nothing. + """ + def __init__(self, message): + self.message = message + self.header = "" + + def from_split_string(self, recs): + """Parse a string that has been split into records by space into + an audit message. + + This method should be overridden by subclasses. Error reporting + should be done by raise ValueError exceptions. + """ + for msg in recs: + fields = msg.split("=") + if len(fields) != 2: + if msg[:6] == "audit(": + self.header = msg + return + else: + continue + + if fields[0] == "msg": + self.header = fields[1] + return + + +class InvalidMessage(AuditMessage): + """Class representing invalid audit messages. This is used to differentiate + between audit messages that aren't recognized (that should return None from + the audit message parser) and a message that is recognized but is malformed + in some way. + """ + def __init__(self, message): + AuditMessage.__init__(self, message) + +class PathMessage(AuditMessage): + """Class representing a path message""" + def __init__(self, message): + AuditMessage.__init__(self, message) + self.path = "" + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + + for msg in recs: + fields = msg.split("=") + if len(fields) != 2: + continue + if fields[0] == "path": + self.path = fields[1][1:-1] + return +import selinux.audit2why as audit2why + +avcdict = {} + +class AVCMessage(AuditMessage): + """AVC message representing an access denial or granted message. + + This is a very basic class and does not represent all possible fields + in an avc message. Currently the fields are: + scontext - context for the source (process) that generated the message + tcontext - context for the target + tclass - object class for the target (only one) + comm - the process name + exe - the on-disc binary + path - the path of the target + access - list of accesses that were allowed or denied + denial - boolean indicating whether this was a denial (True) or granted + (False) message. + + An example audit message generated from the audit daemon looks like (line breaks + added): + 'type=AVC msg=audit(1155568085.407:10877): avc: denied { search } for + pid=677 comm="python" name="modules" dev=dm-0 ino=13716388 + scontext=user_u:system_r:setroubleshootd_t:s0 + tcontext=system_u:object_r:modules_object_t:s0 tclass=dir' + + An example audit message stored in syslog (not processed by the audit daemon - line + breaks added): + 'Sep 12 08:26:43 dhcp83-5 kernel: audit(1158064002.046:4): avc: denied { read } + for pid=2 496 comm="bluez-pin" name=".gdm1K3IFT" dev=dm-0 ino=3601333 + scontext=user_u:system_r:bluetooth_helper_t:s0-s0:c0 + tcontext=system_u:object_r:xdm_tmp_t:s0 tclass=file + """ + def __init__(self, message): + AuditMessage.__init__(self, message) + self.scontext = refpolicy.SecurityContext() + self.tcontext = refpolicy.SecurityContext() + self.tclass = "" + self.comm = "" + self.exe = "" + self.path = "" + self.name = "" + self.accesses = [] + self.denial = True + self.type = audit2why.TERULE + + def __parse_access(self, recs, start): + # This is kind of sucky - the access that is in a space separated + # list like '{ read write }'. This doesn't fit particularly well with splitting + # the string on spaces. This function takes the list of recs and a starting + # position one beyond the open brace. It then adds the accesses until it finds + # the close brace or the end of the list (which is an error if reached without + # seeing a close brace). + found_close = False + i = start + if i == (len(recs) - 1): + raise ValueError("AVC message in invalid format [%s]\n" % self.message) + while i < len(recs): + if recs[i] == "}": + found_close = True + break + self.accesses.append(recs[i]) + i = i + 1 + if not found_close: + raise ValueError("AVC message in invalid format [%s]\n" % self.message) + return i + 1 + + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + # FUTURE - fully parse avc messages and store all possible fields + # Required fields + found_src = False + found_tgt = False + found_class = False + found_access = False + + for i in range(len(recs)): + if recs[i] == "{": + i = self.__parse_access(recs, i + 1) + found_access = True + continue + elif recs[i] == "granted": + self.denial = False + + fields = recs[i].split("=") + if len(fields) != 2: + continue + if fields[0] == "scontext": + self.scontext = refpolicy.SecurityContext(fields[1]) + found_src = True + elif fields[0] == "tcontext": + self.tcontext = refpolicy.SecurityContext(fields[1]) + found_tgt = True + elif fields[0] == "tclass": + self.tclass = fields[1] + found_class = True + elif fields[0] == "comm": + self.comm = fields[1][1:-1] + elif fields[0] == "exe": + self.exe = fields[1][1:-1] + elif fields[0] == "name": + self.name = fields[1][1:-1] + + if not found_src or not found_tgt or not found_class or not found_access: + raise ValueError("AVC message in invalid format [%s]\n" % self.message) + self.analyze() + + def analyze(self): + tcontext = self.tcontext.to_string() + scontext = self.scontext.to_string() + access_tuple = tuple( self.accesses) + self.data = [] + + if (scontext, tcontext, self.tclass, access_tuple) in avcdict.keys(): + self.type, self.data = avcdict[(scontext, tcontext, self.tclass, access_tuple)] + else: + self.type, self.data = audit2why.analyze(scontext, tcontext, self.tclass, self.accesses); + if self.type == audit2why.NOPOLICY: + self.type = audit2why.TERULE + if self.type == audit2why.BADTCON: + raise ValueError("Invalid Target Context %s\n" % tcontext) + if self.type == audit2why.BADSCON: + raise ValueError("Invalid Source Context %s\n" % scontext) + if self.type == audit2why.BADSCON: + raise ValueError("Invalid Type Class %s\n" % self.tclass) + if self.type == audit2why.BADPERM: + raise ValueError("Invalid permission %s\n" % " ".join(self.accesses)) + if self.type == audit2why.BADCOMPUTE: + raise ValueError("Error during access vector computation") + + if self.type == audit2why.CONSTRAINT: + self.data = [ self.data ] + if self.scontext.user != self.tcontext.user: + self.data.append(("user (%s)" % self.scontext.user, 'user (%s)' % self.tcontext.user)) + if self.scontext.role != self.tcontext.role and self.tcontext.role != "object_r": + self.data.append(("role (%s)" % self.scontext.role, 'role (%s)' % self.tcontext.role)) + if self.scontext.level != self.tcontext.level: + self.data.append(("level (%s)" % self.scontext.level, 'level (%s)' % self.tcontext.level)) + + avcdict[(scontext, tcontext, self.tclass, access_tuple)] = (self.type, self.data) + +class PolicyLoadMessage(AuditMessage): + """Audit message indicating that the policy was reloaded.""" + def __init__(self, message): + AuditMessage.__init__(self, message) + +class DaemonStartMessage(AuditMessage): + """Audit message indicating that a daemon was started.""" + def __init__(self, message): + AuditMessage.__init__(self, message) + self.auditd = False + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + if "auditd" in recs: + self.auditd = True + + +class ComputeSidMessage(AuditMessage): + """Audit message indicating that a sid was not valid. + + Compute sid messages are generated on attempting to create a security + context that is not valid. Security contexts are invalid if the role is + not authorized for the user or the type is not authorized for the role. + + This class does not store all of the fields from the compute sid message - + just the type and role. + """ + def __init__(self, message): + AuditMessage.__init__(self, message) + self.invalid_context = refpolicy.SecurityContext() + self.scontext = refpolicy.SecurityContext() + self.tcontext = refpolicy.SecurityContext() + self.tclass = "" + + def from_split_string(self, recs): + AuditMessage.from_split_string(self, recs) + if len(recs) < 10: + raise ValueError("Split string does not represent a valid compute sid message") + + try: + self.invalid_context = refpolicy.SecurityContext(recs[5]) + self.scontext = refpolicy.SecurityContext(recs[7].split("=")[1]) + self.tcontext = refpolicy.SecurityContext(recs[8].split("=")[1]) + self.tclass = recs[9].split("=")[1] + except: + raise ValueError("Split string does not represent a valid compute sid message") + def output(self): + return "role %s types %s;\n" % (self.role, self.type) + +# Parser for audit messages + +class AuditParser: + """Parser for audit messages. + + This class parses audit messages and stores them according to their message + type. This is not a general purpose audit message parser - it only extracts + selinux related messages. + + Each audit messages are stored in one of four lists: + avc_msgs - avc denial or granted messages. Messages are stored in + AVCMessage objects. + comput_sid_messages - invalid sid messages. Messages are stored in + ComputSidMessage objects. + invalid_msgs - selinux related messages that are not valid. Messages + are stored in InvalidMessageObjects. + policy_load_messages - policy load messages. Messages are stored in + PolicyLoadMessage objects. + + These lists will be reset when a policy load message is seen if + AuditParser.last_load_only is set to true. It is assumed that messages + are fed to the parser in chronological order - time stamps are not + parsed. + """ + def __init__(self, last_load_only=False): + self.__initialize() + self.last_load_only = last_load_only + + def __initialize(self): + self.avc_msgs = [] + self.compute_sid_msgs = [] + self.invalid_msgs = [] + self.policy_load_msgs = [] + self.path_msgs = [] + self.by_header = { } + self.check_input_file = False + + # Low-level parsing function - tries to determine if this audit + # message is an SELinux related message and then parses it into + # the appropriate AuditMessage subclass. This function deliberately + # does not impose policy (e.g., on policy load message) or store + # messages to make as simple and reusable as possible. + # + # Return values: + # None - no recognized audit message found in this line + # + # InvalidMessage - a recognized but invalid message was found. + # + # AuditMessage (or subclass) - object representing a parsed + # and valid audit message. + def __parse_line(self, line): + rec = line.split() + for i in rec: + found = False + if i == "avc:" or i == "message=avc:" or i == "msg='avc:": + msg = AVCMessage(line) + found = True + elif i == "security_compute_sid:": + msg = ComputeSidMessage(line) + found = True + elif i == "type=MAC_POLICY_LOAD" or i == "type=1403": + msg = PolicyLoadMessage(line) + found = True + elif i == "type=AVC_PATH": + msg = PathMessage(line) + found = True + elif i == "type=DAEMON_START": + msg = DaemonStartMessage(list) + found = True + + if found: + self.check_input_file = True + try: + msg.from_split_string(rec) + except ValueError: + msg = InvalidMessage(line) + return msg + return None + + # Higher-level parse function - take a line, parse it into an + # AuditMessage object, and store it in the appropriate list. + # This function will optionally reset all of the lists when + # it sees a load policy message depending on the value of + # self.last_load_only. + def __parse(self, line): + msg = self.__parse_line(line) + if msg is None: + return + + # Append to the correct list + if isinstance(msg, PolicyLoadMessage): + if self.last_load_only: + self.__initialize() + elif isinstance(msg, DaemonStartMessage): + # We initialize every time the auditd is started. This + # is less than ideal, but unfortunately it is the only + # way to catch reboots since the initial policy load + # by init is not stored in the audit log. + if msg.auditd and self.last_load_only: + self.__initialize() + self.policy_load_msgs.append(msg) + elif isinstance(msg, AVCMessage): + self.avc_msgs.append(msg) + elif isinstance(msg, ComputeSidMessage): + self.compute_sid_msgs.append(msg) + elif isinstance(msg, InvalidMessage): + self.invalid_msgs.append(msg) + elif isinstance(msg, PathMessage): + self.path_msgs.append(msg) + + # Group by audit header + if msg.header != "": + if self.by_header.has_key(msg.header): + self.by_header[msg.header].append(msg) + else: + self.by_header[msg.header] = [msg] + + + # Post processing will add additional information from AVC messages + # from related messages - only works on messages generated by + # the audit system. + def __post_process(self): + for value in self.by_header.values(): + avc = [] + path = None + for msg in value: + if isinstance(msg, PathMessage): + path = msg + elif isinstance(msg, AVCMessage): + avc.append(msg) + if len(avc) > 0 and path: + for a in avc: + a.path = path.path + + def parse_file(self, input): + """Parse the contents of a file object. This method can be called + multiple times (along with parse_string).""" + line = input.readline() + while line: + self.__parse(line) + line = input.readline() + if not self.check_input_file: + sys.stderr.write("Nothing to do\n") + sys.exit(0) + self.__post_process() + + def parse_string(self, input): + """Parse a string containing audit messages - messages should + be separated by new lines. This method can be called multiple + times (along with parse_file).""" + lines = input.split('\n') + for l in lines: + self.__parse(l) + self.__post_process() + + def to_role(self, role_filter=None): + """Return RoleAllowSet statements matching the specified filter + + Filter out types that match the filer, or all roles + + Params: + role_filter - [optional] Filter object used to filter the + output. + Returns: + Access vector set representing the denied access in the + audit logs parsed by this object. + """ + role_types = access.RoleTypeSet() + for cs in self.compute_sid_msgs: + if not role_filter or role_filter.filter(cs): + role_types.add(cs.invalid_context.role, cs.invalid_context.type) + + return role_types + + def to_access(self, avc_filter=None, only_denials=True): + """Convert the audit logs access into a an access vector set. + + Convert the audit logs into an access vector set, optionally + filtering the restults with the passed in filter object. + + Filter objects are object instances with a .filter method + that takes and access vector and returns True if the message + should be included in the final output and False otherwise. + + Params: + avc_filter - [optional] Filter object used to filter the + output. + Returns: + Access vector set representing the denied access in the + audit logs parsed by this object. + """ + av_set = access.AccessVectorSet() + for avc in self.avc_msgs: + if avc.denial != True and only_denials: + continue + if avc_filter: + if avc_filter.filter(avc): + av_set.add(avc.scontext.type, avc.tcontext.type, avc.tclass, + avc.accesses, avc, avc_type=avc.type, data=avc.data) + else: + av_set.add(avc.scontext.type, avc.tcontext.type, avc.tclass, + avc.accesses, avc, avc_type=avc.type, data=avc.data) + return av_set + +class AVCTypeFilter: + def __init__(self, regex): + self.regex = re.compile(regex) + + def filter(self, avc): + if self.regex.match(avc.scontext.type): + return True + if self.regex.match(avc.tcontext.type): + return True + return False + +class ComputeSidTypeFilter: + def __init__(self, regex): + self.regex = re.compile(regex) + + def filter(self, avc): + if self.regex.match(avc.invalid_context.type): + return True + if self.regex.match(avc.scontext.type): + return True + if self.regex.match(avc.tcontext.type): + return True + return False + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/classperms.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/classperms.py new file mode 100644 index 0000000..c925dee --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/classperms.py @@ -0,0 +1,116 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# +import sys + +tokens = ('DEFINE', + 'NAME', + 'TICK', + 'SQUOTE', + 'OBRACE', + 'CBRACE', + 'SEMI', + 'OPAREN', + 'CPAREN', + 'COMMA') + +reserved = { + 'define' : 'DEFINE' } + +t_TICK = r'\`' +t_SQUOTE = r'\'' +t_OBRACE = r'\{' +t_CBRACE = r'\}' +t_SEMI = r'\;' +t_OPAREN = r'\(' +t_CPAREN = r'\)' +t_COMMA = r'\,' + +t_ignore = " \t\n" + +def t_NAME(t): + r'[a-zA-Z_][a-zA-Z0-9_]*' + t.type = reserved.get(t.value,'NAME') + return t + +def t_error(t): + print "Illegal character '%s'" % t.value[0] + t.skip(1) + +import lex +lex.lex() + +def p_statements(p): + '''statements : define_stmt + | define_stmt statements + ''' + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = [p[1]] + [p[2]] + +def p_define_stmt(p): + # This sucks - corresponds to 'define(`foo',`{ read write }') + '''define_stmt : DEFINE OPAREN TICK NAME SQUOTE COMMA TICK list SQUOTE CPAREN + ''' + + p[0] = [p[4], p[8]] + +def p_list(p): + '''list : NAME + | OBRACE names CBRACE + ''' + if p[1] == "{": + p[0] = p[2] + else: + p[0] = [p[1]] + +def p_names(p): + '''names : NAME + | NAME names + ''' + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = [p[1]] + p[2] + +def p_error(p): + print "Syntax error on line %d %s [type=%s]" % (p.lineno, p.value, p.type) + +import yacc +yacc.yacc() + + +f = open("all_perms.spt") +txt = f.read() +f.close() + +#lex.input(txt) +#while 1: +# tok = lex.token() +# if not tok: +# break +# print tok + +test = "define(`foo',`{ read write append }')" +test2 = """define(`all_filesystem_perms',`{ mount remount unmount getattr relabelfrom relabelto transition associate quotamod quotaget }') +define(`all_security_perms',`{ compute_av compute_create compute_member check_context load_policy compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot }') +""" +result = yacc.parse(txt) +print result + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/defaults.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/defaults.py new file mode 100644 index 0000000..218bc7c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/defaults.py @@ -0,0 +1,77 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +import os +import re + +# Select the correct location for the development files based on a +# path variable (optionally read from a configuration file) +class PathChoooser(object): + def __init__(self, pathname): + self.config = dict() + if not os.path.exists(pathname): + self.config_pathname = "(defaults)" + self.config["SELINUX_DEVEL_PATH"] = "/usr/share/selinux/default:/usr/share/selinux/mls:/usr/share/selinux/devel" + return + self.config_pathname = pathname + ignore = re.compile(r"^\s*(?:#.+)?$") + consider = re.compile(r"^\s*(\w+)\s*=\s*(.+?)\s*$") + for lineno, line in enumerate(open(pathname)): + if ignore.match(line): continue + mo = consider.match(line) + if not mo: + raise ValueError, "%s:%d: line is not in key = value format" % (pathname, lineno+1) + self.config[mo.group(1)] = mo.group(2) + + # We're only exporting one useful function, so why not be a function + def __call__(self, testfilename, pathset="SELINUX_DEVEL_PATH"): + paths = self.config.get(pathset, None) + if paths is None: + raise ValueError, "%s was not in %s" % (pathset, self.config_pathname) + paths = paths.split(":") + for p in paths: + target = os.path.join(p, testfilename) + if os.path.exists(target): return target + return os.path.join(paths[0], testfilename) + + +""" +Various default settings, including file and directory locations. +""" + +def data_dir(): + return "/var/lib/sepolgen" + +def perm_map(): + return data_dir() + "/perm_map" + +def interface_info(): + return data_dir() + "/interface_info" + +def attribute_info(): + return data_dir() + "/attribute_info" + +def refpolicy_makefile(): + chooser = PathChoooser("/etc/selinux/sepolgen.conf") + return chooser("Makefile") + +def headers(): + chooser = PathChoooser("/etc/selinux/sepolgen.conf") + return chooser("include") + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/interfaces.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/interfaces.py new file mode 100644 index 0000000..88a6dc3 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/interfaces.py @@ -0,0 +1,509 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes for representing and manipulating interfaces. +""" + +import access +import refpolicy +import itertools +import objectmodel +import matching + +from sepolgeni18n import _ + +import copy + +class Param: + """ + Object representing a paramater for an interface. + """ + def __init__(self): + self.__name = "" + self.type = refpolicy.SRC_TYPE + self.obj_classes = refpolicy.IdSet() + self.required = True + + def set_name(self, name): + if not access.is_idparam(name): + raise ValueError("Name [%s] is not a param" % name) + self.__name = name + + def get_name(self): + return self.__name + + name = property(get_name, set_name) + + num = property(fget=lambda self: int(self.name[1:])) + + def __repr__(self): + return "" % \ + (self.name, refpolicy.field_to_str[self.type], " ".join(self.obj_classes)) + + +# Helper for extract perms +def __param_insert(name, type, av, params): + ret = 0 + if name in params: + p = params[name] + # The entries are identical - we're done + if type == p.type: + return + # Hanldle implicitly typed objects (like process) + if (type == refpolicy.SRC_TYPE or type == refpolicy.TGT_TYPE) and \ + (p.type == refpolicy.TGT_TYPE or p.type == refpolicy.SRC_TYPE): + #print name, refpolicy.field_to_str[p.type] + # If the object is not implicitly typed, tell the + # caller there is a likely conflict. + ret = 1 + if av: + avobjs = [av.obj_class] + else: + avobjs = [] + for obj in itertools.chain(p.obj_classes, avobjs): + if obj in objectmodel.implicitly_typed_objects: + ret = 0 + break + # "Promote" to a SRC_TYPE as this is the likely usage. + # We do this even if the above test fails on purpose + # as there is really no sane way to resolve the conflict + # here. The caller can take other actions if needed. + p.type = refpolicy.SRC_TYPE + else: + # There is some conflict - no way to resolve it really + # so we just leave the first entry and tell the caller + # there was a conflict. + ret = 1 + else: + p = Param() + p.name = name + p.type = type + params[p.name] = p + + if av: + p.obj_classes.add(av.obj_class) + return ret + + + +def av_extract_params(av, params): + """Extract the paramaters from an access vector. + + Extract the paramaters (in the form $N) from an access + vector, storing them as Param objects in a dictionary. + Some attempt is made at resolving conflicts with other + entries in the dict, but if an unresolvable conflict is + found it is reported to the caller. + + The goal here is to figure out how interface paramaters are + actually used in the interface - e.g., that $1 is a domain used as + a SRC_TYPE. In general an interface will look like this: + + interface(`foo', ` + allow $1 foo : file read; + ') + + This is simple to figure out - $1 is a SRC_TYPE. A few interfaces + are more complex, for example: + + interface(`foo_trans',` + domain_auto_trans($1,fingerd_exec_t,fingerd_t) + + allow $1 fingerd_t:fd use; + allow fingerd_t $1:fd use; + allow fingerd_t $1:fifo_file rw_file_perms; + allow fingerd_t $1:process sigchld; + ') + + Here the usage seems ambigious, but it is not. $1 is still domain + and therefore should be returned as a SRC_TYPE. + + Returns: + 0 - success + 1 - conflict found + """ + ret = 0 + found_src = False + if access.is_idparam(av.src_type): + if __param_insert(av.src_type, refpolicy.SRC_TYPE, av, params) == 1: + ret = 1 + + if access.is_idparam(av.tgt_type): + if __param_insert(av.tgt_type, refpolicy.TGT_TYPE, av, params) == 1: + ret = 1 + + if access.is_idparam(av.obj_class): + if __param_insert(av.obj_class, refpolicy.OBJ_CLASS, av, params) == 1: + ret = 1 + + for perm in av.perms: + if access.is_idparam(perm): + if __param_insert(perm, PERM) == 1: + ret = 1 + + return ret + +def role_extract_params(role, params): + if access.is_idparam(role.role): + return __param_insert(role.role, refpolicy.ROLE, None, params) + +def type_rule_extract_params(rule, params): + def extract_from_set(set, type): + ret = 0 + for x in set: + if access.is_idparam(x): + if __param_insert(x, type, None, params): + ret = 1 + return ret + + ret = 0 + if extract_from_set(rule.src_types, refpolicy.SRC_TYPE): + ret = 1 + + if extract_from_set(rule.tgt_types, refpolicy.TGT_TYPE): + ret = 1 + + if extract_from_set(rule.obj_classes, refpolicy.OBJ_CLASS): + ret = 1 + + if access.is_idparam(rule.dest_type): + if __param_insert(rule.dest_type, refpolicy.DEST_TYPE, None, params): + ret = 1 + + return ret + +def ifcall_extract_params(ifcall, params): + ret = 0 + for arg in ifcall.args: + if access.is_idparam(arg): + # Assume interface arguments are source types. Fairly safe + # assumption for most interfaces + if __param_insert(arg, refpolicy.SRC_TYPE, None, params): + ret = 1 + + return ret + +class AttributeVector: + def __init__(self): + self.name = "" + self.access = access.AccessVectorSet() + + def add_av(self, av): + self.access.add_av(av) + +class AttributeSet: + def __init__(self): + self.attributes = { } + + def add_attr(self, attr): + self.attributes[attr.name] = attr + + def from_file(self, fd): + def parse_attr(line): + fields = line[1:-1].split() + if len(fields) != 2 or fields[0] != "Attribute": + raise SyntaxError("Syntax error Attribute statement %s" % line) + a = AttributeVector() + a.name = fields[1] + + return a + + a = None + for line in fd: + line = line[:-1] + if line[0] == "[": + if a: + self.add_attr(a) + a = parse_attr(line) + elif a: + l = line.split(",") + av = access.AccessVector(l) + a.add_av(av) + if a: + self.add_attr(a) + +class InterfaceVector: + def __init__(self, interface=None, attributes={}): + # Enabled is a loose concept currently - we are essentially + # not enabling interfaces that we can't handle currently. + # See InterfaceVector.add_ifv for more information. + self.enabled = True + self.name = "" + # The access that is enabled by this interface - eventually + # this will include indirect access from typeattribute + # statements. + self.access = access.AccessVectorSet() + # Paramaters are stored in a dictionary (key: param name + # value: Param object). + self.params = { } + if interface: + self.from_interface(interface, attributes) + self.expanded = False + + def from_interface(self, interface, attributes={}): + self.name = interface.name + + # Add allow rules + for avrule in interface.avrules(): + if avrule.rule_type != refpolicy.AVRule.ALLOW: + continue + # Handle some policy bugs + if "dontaudit" in interface.name: + #print "allow rule in interface: %s" % interface + continue + avs = access.avrule_to_access_vectors(avrule) + for av in avs: + self.add_av(av) + + # Add typeattribute access + if attributes: + for typeattribute in interface.typeattributes(): + for attr in typeattribute.attributes: + if not attributes.attributes.has_key(attr): + # print "missing attribute " + attr + continue + attr_vec = attributes.attributes[attr] + for a in attr_vec.access: + av = copy.copy(a) + if av.src_type == attr_vec.name: + av.src_type = typeattribute.type + if av.tgt_type == attr_vec.name: + av.tgt_type = typeattribute.type + self.add_av(av) + + + # Extract paramaters from roles + for role in interface.roles(): + if role_extract_params(role, self.params): + pass + #print "found conflicting role param %s for interface %s" % \ + # (role.name, interface.name) + # Extract paramaters from type rules + for rule in interface.typerules(): + if type_rule_extract_params(rule, self.params): + pass + #print "found conflicting params in rule %s in interface %s" % \ + # (str(rule), interface.name) + + for ifcall in interface.interface_calls(): + if ifcall_extract_params(ifcall, self.params): + pass + #print "found conflicting params in ifcall %s in interface %s" % \ + # (str(ifcall), interface.name) + + + def add_av(self, av): + if av_extract_params(av, self.params) == 1: + pass + #print "found conflicting perms [%s]" % str(av) + self.access.add_av(av) + + def to_string(self): + s = [] + s.append("[InterfaceVector %s]" % self.name) + for av in self.access: + s.append(str(av)) + return "\n".join(s) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "" % (self.name, self.enabled) + + +class InterfaceSet: + def __init__(self, output=None): + self.interfaces = { } + self.tgt_type_map = { } + self.tgt_type_all = [] + self.output = output + + def o(self, str): + if self.output: + self.output.write(str + "\n") + + def to_file(self, fd): + for iv in self.interfaces.values(): + fd.write("[InterfaceVector %s " % iv.name) + for param in iv.params.values(): + fd.write("%s:%s " % (param.name, refpolicy.field_to_str[param.type])) + fd.write("]\n") + avl = iv.access.to_list() + for av in avl: + fd.write(",".join(av)) + fd.write("\n") + + def from_file(self, fd): + def parse_ifv(line): + fields = line[1:-1].split() + if len(fields) < 2 or fields[0] != "InterfaceVector": + raise SyntaxError("Syntax error InterfaceVector statement %s" % line) + ifv = InterfaceVector() + ifv.name = fields[1] + if len(fields) == 2: + return + for field in fields[2:]: + p = field.split(":") + if len(p) != 2: + raise SyntaxError("Invalid param in InterfaceVector statement %s" % line) + param = Param() + param.name = p[0] + param.type = refpolicy.str_to_field[p[1]] + ifv.params[param.name] = param + return ifv + + ifv = None + for line in fd: + line = line[:-1] + if line[0] == "[": + if ifv: + self.add_ifv(ifv) + ifv = parse_ifv(line) + elif ifv: + l = line.split(",") + av = access.AccessVector(l) + ifv.add_av(av) + if ifv: + self.add_ifv(ifv) + + self.index() + + def add_ifv(self, ifv): + self.interfaces[ifv.name] = ifv + + def index(self): + for ifv in self.interfaces.values(): + tgt_types = set() + for av in ifv.access: + if access.is_idparam(av.tgt_type): + self.tgt_type_all.append(ifv) + tgt_types = set() + break + tgt_types.add(av.tgt_type) + + for type in tgt_types: + l = self.tgt_type_map.setdefault(type, []) + l.append(ifv) + + def add(self, interface, attributes={}): + ifv = InterfaceVector(interface, attributes) + self.add_ifv(ifv) + + def add_headers(self, headers, output=None, attributes={}): + for i in itertools.chain(headers.interfaces(), headers.templates()): + self.add(i, attributes) + + self.expand_ifcalls(headers) + self.index() + + def map_param(self, id, ifcall): + if access.is_idparam(id): + num = int(id[1:]) + if num > len(ifcall.args): + # Tell caller to drop this because it must have + # been generated from an optional param. + return None + else: + arg = ifcall.args[num - 1] + if isinstance(arg, list): + return arg + else: + return [arg] + else: + return [id] + + def map_add_av(self, ifv, av, ifcall): + src_types = self.map_param(av.src_type, ifcall) + if src_types is None: + return + + tgt_types = self.map_param(av.tgt_type, ifcall) + if tgt_types is None: + return + + obj_classes = self.map_param(av.obj_class, ifcall) + if obj_classes is None: + return + + new_perms = refpolicy.IdSet() + for perm in av.perms: + p = self.map_param(perm, ifcall) + if p is None: + continue + else: + new_perms.update(p) + if len(new_perms) == 0: + return + + for src_type in src_types: + for tgt_type in tgt_types: + for obj_class in obj_classes: + ifv.access.add(src_type, tgt_type, obj_class, new_perms) + + def do_expand_ifcalls(self, interface, if_by_name): + # Descend an interface call tree adding the access + # from each interface. This is a depth first walk + # of the tree. + + stack = [(interface, None)] + ifv = self.interfaces[interface.name] + ifv.expanded = True + + while len(stack) > 0: + cur, cur_ifcall = stack.pop(-1) + + cur_ifv = self.interfaces[cur.name] + if cur != interface: + + for av in cur_ifv.access: + self.map_add_av(ifv, av, cur_ifcall) + + # If we have already fully expanded this interface + # there is no reason to descend further. + if cur_ifv.expanded: + continue + + for ifcall in cur.interface_calls(): + if ifcall.ifname == interface.name: + self.o(_("Found circular interface class")) + return + try: + newif = if_by_name[ifcall.ifname] + except KeyError: + self.o(_("Missing interface definition for %s" % ifcall.ifname)) + continue + + stack.append((newif, ifcall)) + + + def expand_ifcalls(self, headers): + # Create a map of interface names to interfaces - + # this mirrors the interface vector map we already + # have. + if_by_name = { } + + for i in itertools.chain(headers.interfaces(), headers.templates()): + if_by_name[i.name] = i + + + for interface in itertools.chain(headers.interfaces(), headers.templates()): + self.do_expand_ifcalls(interface, if_by_name) + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/lex.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/lex.py new file mode 100644 index 0000000..c149366 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/lex.py @@ -0,0 +1,866 @@ +#----------------------------------------------------------------------------- +# ply: lex.py +# +# Author: David M. Beazley (dave@dabeaz.com) +# +# Copyright (C) 2001-2006, David M. Beazley +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See the file COPYING for a complete copy of the LGPL. +#----------------------------------------------------------------------------- + +__version__ = "2.2" + +import re, sys, types + +# Regular expression used to match valid token names +_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') + +# Available instance types. This is used when lexers are defined by a class. +# It's a little funky because I want to preserve backwards compatibility +# with Python 2.0 where types.ObjectType is undefined. + +try: + _INSTANCETYPE = (types.InstanceType, types.ObjectType) +except AttributeError: + _INSTANCETYPE = types.InstanceType + class object: pass # Note: needed if no new-style classes present + +# Exception thrown when invalid token encountered and no default error +# handler is defined. +class LexError(Exception): + def __init__(self,message,s): + self.args = (message,) + self.text = s + +# Token class +class LexToken(object): + def __str__(self): + return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos) + def __repr__(self): + return str(self) + def skip(self,n): + self.lexer.skip(n) + +# ----------------------------------------------------------------------------- +# Lexer class +# +# This class encapsulates all of the methods and data associated with a lexer. +# +# input() - Store a new string in the lexer +# token() - Get the next token +# ----------------------------------------------------------------------------- + +class Lexer: + def __init__(self): + self.lexre = None # Master regular expression. This is a list of + # tuples (re,findex) where re is a compiled + # regular expression and findex is a list + # mapping regex group numbers to rules + self.lexretext = None # Current regular expression strings + self.lexstatere = {} # Dictionary mapping lexer states to master regexs + self.lexstateretext = {} # Dictionary mapping lexer states to regex strings + self.lexstate = "INITIAL" # Current lexer state + self.lexstatestack = [] # Stack of lexer states + self.lexstateinfo = None # State information + self.lexstateignore = {} # Dictionary of ignored characters for each state + self.lexstateerrorf = {} # Dictionary of error functions for each state + self.lexreflags = 0 # Optional re compile flags + self.lexdata = None # Actual input data (as a string) + self.lexpos = 0 # Current position in input text + self.lexlen = 0 # Length of the input text + self.lexerrorf = None # Error rule (if any) + self.lextokens = None # List of valid tokens + self.lexignore = "" # Ignored characters + self.lexliterals = "" # Literal characters that can be passed through + self.lexmodule = None # Module + self.lineno = 1 # Current line number + self.lexdebug = 0 # Debugging mode + self.lexoptimize = 0 # Optimized mode + + def clone(self,object=None): + c = Lexer() + c.lexstatere = self.lexstatere + c.lexstateinfo = self.lexstateinfo + c.lexstateretext = self.lexstateretext + c.lexstate = self.lexstate + c.lexstatestack = self.lexstatestack + c.lexstateignore = self.lexstateignore + c.lexstateerrorf = self.lexstateerrorf + c.lexreflags = self.lexreflags + c.lexdata = self.lexdata + c.lexpos = self.lexpos + c.lexlen = self.lexlen + c.lextokens = self.lextokens + c.lexdebug = self.lexdebug + c.lineno = self.lineno + c.lexoptimize = self.lexoptimize + c.lexliterals = self.lexliterals + c.lexmodule = self.lexmodule + + # If the object parameter has been supplied, it means we are attaching the + # lexer to a new object. In this case, we have to rebind all methods in + # the lexstatere and lexstateerrorf tables. + + if object: + newtab = { } + for key, ritem in self.lexstatere.items(): + newre = [] + for cre, findex in ritem: + newfindex = [] + for f in findex: + if not f or not f[0]: + newfindex.append(f) + continue + newfindex.append((getattr(object,f[0].__name__),f[1])) + newre.append((cre,newfindex)) + newtab[key] = newre + c.lexstatere = newtab + c.lexstateerrorf = { } + for key, ef in self.lexstateerrorf.items(): + c.lexstateerrorf[key] = getattr(object,ef.__name__) + c.lexmodule = object + + # Set up other attributes + c.begin(c.lexstate) + return c + + # ------------------------------------------------------------ + # writetab() - Write lexer information to a table file + # ------------------------------------------------------------ + def writetab(self,tabfile): + tf = open(tabfile+".py","w") + tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__)) + tf.write("_lextokens = %s\n" % repr(self.lextokens)) + tf.write("_lexreflags = %s\n" % repr(self.lexreflags)) + tf.write("_lexliterals = %s\n" % repr(self.lexliterals)) + tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo)) + + tabre = { } + for key, lre in self.lexstatere.items(): + titem = [] + for i in range(len(lre)): + titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1]))) + tabre[key] = titem + + tf.write("_lexstatere = %s\n" % repr(tabre)) + tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore)) + + taberr = { } + for key, ef in self.lexstateerrorf.items(): + if ef: + taberr[key] = ef.__name__ + else: + taberr[key] = None + tf.write("_lexstateerrorf = %s\n" % repr(taberr)) + tf.close() + + # ------------------------------------------------------------ + # readtab() - Read lexer information from a tab file + # ------------------------------------------------------------ + def readtab(self,tabfile,fdict): + exec "import %s as lextab" % tabfile + self.lextokens = lextab._lextokens + self.lexreflags = lextab._lexreflags + self.lexliterals = lextab._lexliterals + self.lexstateinfo = lextab._lexstateinfo + self.lexstateignore = lextab._lexstateignore + self.lexstatere = { } + self.lexstateretext = { } + for key,lre in lextab._lexstatere.items(): + titem = [] + txtitem = [] + for i in range(len(lre)): + titem.append((re.compile(lre[i][0],lextab._lexreflags),_names_to_funcs(lre[i][1],fdict))) + txtitem.append(lre[i][0]) + self.lexstatere[key] = titem + self.lexstateretext[key] = txtitem + self.lexstateerrorf = { } + for key,ef in lextab._lexstateerrorf.items(): + self.lexstateerrorf[key] = fdict[ef] + self.begin('INITIAL') + + # ------------------------------------------------------------ + # input() - Push a new string into the lexer + # ------------------------------------------------------------ + def input(self,s): + if not (isinstance(s,types.StringType) or isinstance(s,types.UnicodeType)): + raise ValueError, "Expected a string" + self.lexdata = s + self.lexpos = 0 + self.lexlen = len(s) + + # ------------------------------------------------------------ + # begin() - Changes the lexing state + # ------------------------------------------------------------ + def begin(self,state): + if not self.lexstatere.has_key(state): + raise ValueError, "Undefined state" + self.lexre = self.lexstatere[state] + self.lexretext = self.lexstateretext[state] + self.lexignore = self.lexstateignore.get(state,"") + self.lexerrorf = self.lexstateerrorf.get(state,None) + self.lexstate = state + + # ------------------------------------------------------------ + # push_state() - Changes the lexing state and saves old on stack + # ------------------------------------------------------------ + def push_state(self,state): + self.lexstatestack.append(self.lexstate) + self.begin(state) + + # ------------------------------------------------------------ + # pop_state() - Restores the previous state + # ------------------------------------------------------------ + def pop_state(self): + self.begin(self.lexstatestack.pop()) + + # ------------------------------------------------------------ + # current_state() - Returns the current lexing state + # ------------------------------------------------------------ + def current_state(self): + return self.lexstate + + # ------------------------------------------------------------ + # skip() - Skip ahead n characters + # ------------------------------------------------------------ + def skip(self,n): + self.lexpos += n + + # ------------------------------------------------------------ + # token() - Return the next token from the Lexer + # + # Note: This function has been carefully implemented to be as fast + # as possible. Don't make changes unless you really know what + # you are doing + # ------------------------------------------------------------ + def token(self): + # Make local copies of frequently referenced attributes + lexpos = self.lexpos + lexlen = self.lexlen + lexignore = self.lexignore + lexdata = self.lexdata + + while lexpos < lexlen: + # This code provides some short-circuit code for whitespace, tabs, and other ignored characters + if lexdata[lexpos] in lexignore: + lexpos += 1 + continue + + # Look for a regular expression match + for lexre,lexindexfunc in self.lexre: + m = lexre.match(lexdata,lexpos) + if not m: continue + + # Set last match in lexer so that rules can access it if they want + self.lexmatch = m + + # Create a token for return + tok = LexToken() + tok.value = m.group() + tok.lineno = self.lineno + tok.lexpos = lexpos + tok.lexer = self + + lexpos = m.end() + i = m.lastindex + func,tok.type = lexindexfunc[i] + self.lexpos = lexpos + + if not func: + # If no token type was set, it's an ignored token + if tok.type: return tok + break + + # if func not callable, it means it's an ignored token + if not callable(func): + break + + # If token is processed by a function, call it + newtok = func(tok) + + # Every function must return a token, if nothing, we just move to next token + if not newtok: + lexpos = self.lexpos # This is here in case user has updated lexpos. + break + + # Verify type of the token. If not in the token map, raise an error + if not self.lexoptimize: + if not self.lextokens.has_key(newtok.type): + raise LexError, ("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( + func.func_code.co_filename, func.func_code.co_firstlineno, + func.__name__, newtok.type),lexdata[lexpos:]) + + return newtok + else: + # No match, see if in literals + if lexdata[lexpos] in self.lexliterals: + tok = LexToken() + tok.value = lexdata[lexpos] + tok.lineno = self.lineno + tok.lexer = self + tok.type = tok.value + tok.lexpos = lexpos + self.lexpos = lexpos + 1 + return tok + + # No match. Call t_error() if defined. + if self.lexerrorf: + tok = LexToken() + tok.value = self.lexdata[lexpos:] + tok.lineno = self.lineno + tok.type = "error" + tok.lexer = self + tok.lexpos = lexpos + self.lexpos = lexpos + newtok = self.lexerrorf(tok) + if lexpos == self.lexpos: + # Error method didn't change text position at all. This is an error. + raise LexError, ("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + lexpos = self.lexpos + if not newtok: continue + return newtok + + self.lexpos = lexpos + raise LexError, ("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:]) + + self.lexpos = lexpos + 1 + if self.lexdata is None: + raise RuntimeError, "No input string given with input()" + return None + +# ----------------------------------------------------------------------------- +# _validate_file() +# +# This checks to see if there are duplicated t_rulename() functions or strings +# in the parser input file. This is done using a simple regular expression +# match on each line in the filename. +# ----------------------------------------------------------------------------- + +def _validate_file(filename): + import os.path + base,ext = os.path.splitext(filename) + if ext != '.py': return 1 # No idea what the file is. Return OK + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + return 1 # Oh well + + fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') + sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') + counthash = { } + linen = 1 + noerror = 1 + for l in lines: + m = fre.match(l) + if not m: + m = sre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + print "%s:%d: Rule %s redefined. Previously defined on line %d" % (filename,linen,name,prev) + noerror = 0 + linen += 1 + return noerror + +# ----------------------------------------------------------------------------- +# _funcs_to_names() +# +# Given a list of regular expression functions, this converts it to a list +# suitable for output to a table file +# ----------------------------------------------------------------------------- + +def _funcs_to_names(funclist): + result = [] + for f in funclist: + if f and f[0]: + result.append((f[0].__name__,f[1])) + else: + result.append(f) + return result + +# ----------------------------------------------------------------------------- +# _names_to_funcs() +# +# Given a list of regular expression function names, this converts it back to +# functions. +# ----------------------------------------------------------------------------- + +def _names_to_funcs(namelist,fdict): + result = [] + for n in namelist: + if n and n[0]: + result.append((fdict[n[0]],n[1])) + else: + result.append(n) + return result + +# ----------------------------------------------------------------------------- +# _form_master_re() +# +# This function takes a list of all of the regex components and attempts to +# form the master regular expression. Given limitations in the Python re +# module, it may be necessary to break the master regex into separate expressions. +# ----------------------------------------------------------------------------- + +def _form_master_re(relist,reflags,ldict): + if not relist: return [] + regex = "|".join(relist) + try: + lexre = re.compile(regex,re.VERBOSE | reflags) + + # Build the index to function map for the matching engine + lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1) + for f,i in lexre.groupindex.items(): + handle = ldict.get(f,None) + if type(handle) in (types.FunctionType, types.MethodType): + lexindexfunc[i] = (handle,handle.__name__[2:]) + elif handle is not None: + # If rule was specified as a string, we build an anonymous + # callback function to carry out the action + if f.find("ignore_") > 0: + lexindexfunc[i] = (None,None) + print "IGNORE", f + else: + lexindexfunc[i] = (None, f[2:]) + + return [(lexre,lexindexfunc)],[regex] + except Exception,e: + m = int(len(relist)/2) + if m == 0: m = 1 + llist, lre = _form_master_re(relist[:m],reflags,ldict) + rlist, rre = _form_master_re(relist[m:],reflags,ldict) + return llist+rlist, lre+rre + +# ----------------------------------------------------------------------------- +# def _statetoken(s,names) +# +# Given a declaration name s of the form "t_" and a dictionary whose keys are +# state names, this function returns a tuple (states,tokenname) where states +# is a tuple of state names and tokenname is the name of the token. For example, +# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') +# ----------------------------------------------------------------------------- + +def _statetoken(s,names): + nonstate = 1 + parts = s.split("_") + for i in range(1,len(parts)): + if not names.has_key(parts[i]) and parts[i] != 'ANY': break + if i > 1: + states = tuple(parts[1:i]) + else: + states = ('INITIAL',) + + if 'ANY' in states: + states = tuple(names.keys()) + + tokenname = "_".join(parts[i:]) + return (states,tokenname) + +# ----------------------------------------------------------------------------- +# lex(module) +# +# Build all of the regular expression rules from definitions in the supplied module +# ----------------------------------------------------------------------------- +def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0): + global lexer + ldict = None + stateinfo = { 'INITIAL' : 'inclusive'} + error = 0 + files = { } + lexobj = Lexer() + lexobj.lexdebug = debug + lexobj.lexoptimize = optimize + global token,input + + if nowarn: warn = 0 + else: warn = 1 + + if object: module = object + + if module: + # User supplied a module object. + if isinstance(module, types.ModuleType): + ldict = module.__dict__ + elif isinstance(module, _INSTANCETYPE): + _items = [(k,getattr(module,k)) for k in dir(module)] + ldict = { } + for (i,v) in _items: + ldict[i] = v + else: + raise ValueError,"Expected a module or instance" + lexobj.lexmodule = module + + else: + # No module given. We might be able to get information from the caller. + try: + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + f = f.f_back # Walk out to our calling function + ldict = f.f_globals # Grab its globals dictionary + + if optimize and lextab: + try: + lexobj.readtab(lextab,ldict) + token = lexobj.token + input = lexobj.input + lexer = lexobj + return lexobj + + except ImportError: + pass + + # Get the tokens, states, and literals variables (if any) + if (module and isinstance(module,_INSTANCETYPE)): + tokens = getattr(module,"tokens",None) + states = getattr(module,"states",None) + literals = getattr(module,"literals","") + else: + tokens = ldict.get("tokens",None) + states = ldict.get("states",None) + literals = ldict.get("literals","") + + if not tokens: + raise SyntaxError,"lex: module does not define 'tokens'" + if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)): + raise SyntaxError,"lex: tokens must be a list or tuple." + + # Build a dictionary of valid token names + lexobj.lextokens = { } + if not optimize: + for n in tokens: + if not _is_identifier.match(n): + print "lex: Bad token name '%s'" % n + error = 1 + if warn and lexobj.lextokens.has_key(n): + print "lex: Warning. Token '%s' multiply defined." % n + lexobj.lextokens[n] = None + else: + for n in tokens: lexobj.lextokens[n] = None + + if debug: + print "lex: tokens = '%s'" % lexobj.lextokens.keys() + + try: + for c in literals: + if not (isinstance(c,types.StringType) or isinstance(c,types.UnicodeType)) or len(c) > 1: + print "lex: Invalid literal %s. Must be a single character" % repr(c) + error = 1 + continue + + except TypeError: + print "lex: Invalid literals specification. literals must be a sequence of characters." + error = 1 + + lexobj.lexliterals = literals + + # Build statemap + if states: + if not (isinstance(states,types.TupleType) or isinstance(states,types.ListType)): + print "lex: states must be defined as a tuple or list." + error = 1 + else: + for s in states: + if not isinstance(s,types.TupleType) or len(s) != 2: + print "lex: invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')" % repr(s) + error = 1 + continue + name, statetype = s + if not isinstance(name,types.StringType): + print "lex: state name %s must be a string" % repr(name) + error = 1 + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + print "lex: state type for state %s must be 'inclusive' or 'exclusive'" % name + error = 1 + continue + if stateinfo.has_key(name): + print "lex: state '%s' already defined." % name + error = 1 + continue + stateinfo[name] = statetype + + # Get a list of symbols with the t_ or s_ prefix + tsymbols = [f for f in ldict.keys() if f[:2] == 't_' ] + + # Now build up a list of functions and a list of strings + + funcsym = { } # Symbols defined as functions + strsym = { } # Symbols defined as strings + toknames = { } # Mapping of symbols to token names + + for s in stateinfo.keys(): + funcsym[s] = [] + strsym[s] = [] + + ignore = { } # Ignore strings by state + errorf = { } # Error functions by state + + if len(tsymbols) == 0: + raise SyntaxError,"lex: no rules of the form t_rulename are defined." + + for f in tsymbols: + t = ldict[f] + states, tokname = _statetoken(f,stateinfo) + toknames[f] = tokname + + if callable(t): + for s in states: funcsym[s].append((f,t)) + elif (isinstance(t, types.StringType) or isinstance(t,types.UnicodeType)): + for s in states: strsym[s].append((f,t)) + else: + print "lex: %s not defined as a function or string" % f + error = 1 + + # Sort the functions by line number + for f in funcsym.values(): + f.sort(lambda x,y: cmp(x[1].func_code.co_firstlineno,y[1].func_code.co_firstlineno)) + + # Sort the strings by regular expression length + for s in strsym.values(): + s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) + + regexs = { } + + # Build the master regular expressions + for state in stateinfo.keys(): + regex_list = [] + + # Add rules defined by functions first + for fname, f in funcsym[state]: + line = f.func_code.co_firstlineno + file = f.func_code.co_filename + files[file] = None + tokname = toknames[fname] + + ismethod = isinstance(f, types.MethodType) + + if not optimize: + nargs = f.func_code.co_argcount + if ismethod: + reqargs = 2 + else: + reqargs = 1 + if nargs > reqargs: + print "%s:%d: Rule '%s' has too many arguments." % (file,line,f.__name__) + error = 1 + continue + + if nargs < reqargs: + print "%s:%d: Rule '%s' requires an argument." % (file,line,f.__name__) + error = 1 + continue + + if tokname == 'ignore': + print "%s:%d: Rule '%s' must be defined as a string." % (file,line,f.__name__) + error = 1 + continue + + if tokname == 'error': + errorf[state] = f + continue + + if f.__doc__: + if not optimize: + try: + c = re.compile("(?P<%s>%s)" % (f.__name__,f.__doc__), re.VERBOSE | reflags) + if c.match(""): + print "%s:%d: Regular expression for rule '%s' matches empty string." % (file,line,f.__name__) + error = 1 + continue + except re.error,e: + print "%s:%d: Invalid regular expression for rule '%s'. %s" % (file,line,f.__name__,e) + if '#' in f.__doc__: + print "%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'." % (file,line, f.__name__) + error = 1 + continue + + if debug: + print "lex: Adding rule %s -> '%s' (state '%s')" % (f.__name__,f.__doc__, state) + + # Okay. The regular expression seemed okay. Let's append it to the master regular + # expression we're building + + regex_list.append("(?P<%s>%s)" % (f.__name__,f.__doc__)) + else: + print "%s:%d: No regular expression defined for rule '%s'" % (file,line,f.__name__) + + # Now add all of the simple rules + for name,r in strsym[state]: + tokname = toknames[name] + + if tokname == 'ignore': + ignore[state] = r + continue + + if not optimize: + if tokname == 'error': + raise SyntaxError,"lex: Rule '%s' must be defined as a function" % name + error = 1 + continue + + if not lexobj.lextokens.has_key(tokname) and tokname.find("ignore_") < 0: + print "lex: Rule '%s' defined for an unspecified token %s." % (name,tokname) + error = 1 + continue + try: + c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | reflags) + if (c.match("")): + print "lex: Regular expression for rule '%s' matches empty string." % name + error = 1 + continue + except re.error,e: + print "lex: Invalid regular expression for rule '%s'. %s" % (name,e) + if '#' in r: + print "lex: Make sure '#' in rule '%s' is escaped with '\\#'." % name + + error = 1 + continue + if debug: + print "lex: Adding rule %s -> '%s' (state '%s')" % (name,r,state) + + regex_list.append("(?P<%s>%s)" % (name,r)) + + if not regex_list: + print "lex: No rules defined for state '%s'" % state + error = 1 + + regexs[state] = regex_list + + + if not optimize: + for f in files.keys(): + if not _validate_file(f): + error = 1 + + if error: + raise SyntaxError,"lex: Unable to build lexer." + + # From this point forward, we're reasonably confident that we can build the lexer. + # No more errors will be generated, but there might be some warning messages. + + # Build the master regular expressions + + for state in regexs.keys(): + lexre, re_text = _form_master_re(regexs[state],reflags,ldict) + lexobj.lexstatere[state] = lexre + lexobj.lexstateretext[state] = re_text + if debug: + for i in range(len(re_text)): + print "lex: state '%s'. regex[%d] = '%s'" % (state, i, re_text[i]) + + # For inclusive states, we need to add the INITIAL state + for state,type in stateinfo.items(): + if state != "INITIAL" and type == 'inclusive': + lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) + lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) + + lexobj.lexstateinfo = stateinfo + lexobj.lexre = lexobj.lexstatere["INITIAL"] + lexobj.lexretext = lexobj.lexstateretext["INITIAL"] + + # Set up ignore variables + lexobj.lexstateignore = ignore + lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","") + + # Set up error functions + lexobj.lexstateerrorf = errorf + lexobj.lexerrorf = errorf.get("INITIAL",None) + if warn and not lexobj.lexerrorf: + print "lex: Warning. no t_error rule is defined." + + # Check state information for ignore and error rules + for s,stype in stateinfo.items(): + if stype == 'exclusive': + if warn and not errorf.has_key(s): + print "lex: Warning. no error rule is defined for exclusive state '%s'" % s + if warn and not ignore.has_key(s) and lexobj.lexignore: + print "lex: Warning. no ignore rule is defined for exclusive state '%s'" % s + elif stype == 'inclusive': + if not errorf.has_key(s): + errorf[s] = errorf.get("INITIAL",None) + if not ignore.has_key(s): + ignore[s] = ignore.get("INITIAL","") + + + # Create global versions of the token() and input() functions + token = lexobj.token + input = lexobj.input + lexer = lexobj + + # If in optimize mode, we write the lextab + if lextab and optimize: + lexobj.writetab(lextab) + + return lexobj + +# ----------------------------------------------------------------------------- +# runmain() +# +# This runs the lexer as a main program +# ----------------------------------------------------------------------------- + +def runmain(lexer=None,data=None): + if not data: + try: + filename = sys.argv[1] + f = open(filename) + data = f.read() + f.close() + except IndexError: + print "Reading from standard input (type EOF to end):" + data = sys.stdin.read() + + if lexer: + _input = lexer.input + else: + _input = input + _input(data) + if lexer: + _token = lexer.token + else: + _token = token + + while 1: + tok = _token() + if not tok: break + print "(%s,%r,%d,%d)" % (tok.type, tok.value, tok.lineno,tok.lexpos) + + +# ----------------------------------------------------------------------------- +# @TOKEN(regex) +# +# This decorator function can be used to set the regex expression on a function +# when its docstring might need to be set in an alternative way +# ----------------------------------------------------------------------------- + +def TOKEN(r): + def set_doc(f): + f.__doc__ = r + return f + return set_doc + +# Alternative spelling of the TOKEN decorator +Token = TOKEN + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/matching.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/matching.py new file mode 100644 index 0000000..d56dd92 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/matching.py @@ -0,0 +1,255 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes and algorithms for matching requested access to access vectors. +""" + +import access +import objectmodel +import itertools + +class Match: + def __init__(self, interface=None, dist=0): + self.interface = interface + self.dist = dist + self.info_dir_change = False + + def __cmp__(self, other): + if self.dist == other.dist: + if self.info_dir_change: + if other.info_dir_change: + return 0 + else: + return 1 + else: + if other.info_dir_change: + return -1 + else: + return 0 + else: + if self.dist < other.dist: + return -1 + else: + return 1 + +class MatchList: + DEFAULT_THRESHOLD = 150 + def __init__(self): + # Match objects that pass the threshold + self.children = [] + # Match objects over the threshold + self.bastards = [] + self.threshold = self.DEFAULT_THRESHOLD + self.allow_info_dir_change = False + self.av = None + + def best(self): + if len(self.children): + return self.children[0] + if len(self.bastards): + return self.bastards[0] + return None + + def __len__(self): + # Only return the length of the matches so + # that this can be used to test if there is + # a match. + return len(self.children) + len(self.bastards) + + def __iter__(self): + return iter(self.children) + + def all(self): + return itertools.chain(self.children, self.bastards) + + def append(self, match): + if match.dist <= self.threshold: + if not match.info_dir_change or self.allow_info_dir_change: + self.children.append(match) + else: + self.bastards.append(match) + else: + self.bastards.append(match) + + def sort(self): + self.children.sort() + self.bastards.sort() + + +class AccessMatcher: + def __init__(self, perm_maps=None): + self.type_penalty = 10 + self.obj_penalty = 10 + if perm_maps: + self.perm_maps = perm_maps + else: + self.perm_maps = objectmodel.PermMappings() + # We want a change in the information flow direction + # to be a strong penalty - stronger than access to + # a few unrelated types. + self.info_dir_penalty = 100 + + def type_distance(self, a, b): + if a == b or access.is_idparam(b): + return 0 + else: + return -self.type_penalty + + + def perm_distance(self, av_req, av_prov): + # First check that we have enough perms + diff = av_req.perms.difference(av_prov.perms) + + if len(diff) != 0: + total = self.perm_maps.getdefault_distance(av_req.obj_class, diff) + return -total + else: + diff = av_prov.perms.difference(av_req.perms) + return self.perm_maps.getdefault_distance(av_req.obj_class, diff) + + def av_distance(self, req, prov): + """Determine the 'distance' between 2 access vectors. + + This function is used to find an access vector that matches + a 'required' access. To do this we comput a signed numeric + value that indicates how close the req access is to the + 'provided' access vector. The closer the value is to 0 + the closer the match, with 0 being an exact match. + + A value over 0 indicates that the prov access vector provides more + access than the req (in practice, this means that the source type, + target type, and object class is the same and the perms in prov is + a superset of those in req. + + A value under 0 indicates that the prov access less - or unrelated + - access to the req access. A different type or object class will + result in a very low value. + + The values other than 0 should only be interpreted relative to + one another - they have no exact meaning and are likely to + change. + + Params: + req - [AccessVector] The access that is required. This is the + access being matched. + prov - [AccessVector] The access provided. This is the potential + match that is being evaluated for req. + Returns: + 0 : Exact match between the acess vectors. + + < 0 : The prov av does not provide all of the access in req. + A smaller value indicates that the access is further. + + > 0 : The prov av provides more access than req. The larger + the value the more access over req. + """ + # FUTURE - this is _very_ expensive and probably needs some + # thorough performance work. This version is meant to give + # meaningful results relatively simply. + dist = 0 + + # Get the difference between the types. The addition is safe + # here because type_distance only returns 0 or negative. + dist += self.type_distance(req.src_type, prov.src_type) + dist += self.type_distance(req.tgt_type, prov.tgt_type) + + # Object class distance + if req.obj_class != prov.obj_class and not access.is_idparam(prov.obj_class): + dist -= self.obj_penalty + + # Permission distance + + # If this av doesn't have a matching source type, target type, and object class + # count all of the permissions against it. Otherwise determine the perm + # distance and dir. + if dist < 0: + pdist = self.perm_maps.getdefault_distance(prov.obj_class, prov.perms) + else: + pdist = self.perm_distance(req, prov) + + # Combine the perm and other distance + if dist < 0: + if pdist < 0: + return dist + pdist + else: + return dist - pdist + elif dist >= 0: + if pdist < 0: + return pdist - dist + else: + return dist + pdist + + def av_set_match(self, av_set, av): + """ + + """ + dist = None + + # Get the distance for each access vector + for x in av_set: + tmp = self.av_distance(av, x) + if dist is None: + dist = tmp + elif tmp >= 0: + if dist >= 0: + dist += tmp + else: + dist = tmp + -dist + else: + if dist < 0: + dist += tmp + else: + dist -= tmp + + # Penalize for information flow - we want to prevent the + # addition of a write if the requested is read none. We are + # much less concerned about the reverse. + av_dir = self.perm_maps.getdefault_direction(av.obj_class, av.perms) + + if av_set.info_dir is None: + av_set.info_dir = objectmodel.FLOW_NONE + for x in av_set: + av_set.info_dir = av_set.info_dir | \ + self.perm_maps.getdefault_direction(x.obj_class, x.perms) + if (av_dir & objectmodel.FLOW_WRITE == 0) and (av_set.info_dir & objectmodel.FLOW_WRITE): + if dist < 0: + dist -= self.info_dir_penalty + else: + dist += self.info_dir_penalty + + return dist + + def search_ifs(self, ifset, av, match_list): + match_list.av = av + for iv in itertools.chain(ifset.tgt_type_all, + ifset.tgt_type_map.get(av.tgt_type, [])): + if not iv.enabled: + #print "iv %s not enabled" % iv.name + continue + + dist = self.av_set_match(iv.access, av) + if dist >= 0: + m = Match(iv, dist) + match_list.append(m) + + + match_list.sort() + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/module.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/module.py new file mode 100644 index 0000000..7fc9443 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/module.py @@ -0,0 +1,213 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Utilities for dealing with the compilation of modules and creation +of module tress. +""" + +import defaults + +import selinux + +import re +import tempfile +import commands +import os +import os.path +import subprocess +import shutil + +def is_valid_name(modname): + """Check that a module name is valid. + """ + m = re.findall("[^a-zA-Z0-9_\-\.]", modname) + if len(m) == 0 and modname[0].isalpha(): + return True + else: + return False + +class ModuleTree: + def __init__(self, modname): + self.modname = modname + self.dirname = None + + def dir_name(self): + return self.dirname + + def te_name(self): + return self.dirname + "/" + self.modname + ".te" + + def fc_name(self): + return self.dirname + "/" + self.modname + ".fc" + + def if_name(self): + return self.dirname + "/" + self.modname + ".if" + + def package_name(self): + return self.dirname + "/" + self.modname + ".pp" + + def makefile_name(self): + return self.dirname + "/Makefile" + + def create(self, parent_dirname, makefile_include=None): + self.dirname = parent_dirname + "/" + self.modname + os.mkdir(self.dirname) + fd = open(self.makefile_name(), "w") + if makefile_include: + fd.write("include " + makefile_include) + else: + fd.write("include " + defaults.refpolicy_makefile()) + fd.close() + + # Create empty files for the standard refpolicy + # module files + open(self.te_name(), "w").close() + open(self.fc_name(), "w").close() + open(self.if_name(), "w").close() + +def modname_from_sourcename(sourcename): + return os.path.splitext(os.path.split(sourcename)[1])[0] + +class ModuleCompiler: + """ModuleCompiler eases running of the module compiler. + + The ModuleCompiler class encapsulates running the commandline + module compiler (checkmodule) and module packager (semodule_package). + You are likely interested in the create_module_package method. + + Several options are controlled via paramaters (only effects the + non-refpol builds): + + .mls [boolean] Generate an MLS module (by passed -M to + checkmodule). True to generate an MLS module, false + otherwise. + + .module [boolean] Generate a module instead of a base module. + True to generate a module, false to generate a base. + + .checkmodule [string] Fully qualified path to the module compiler. + Default is /usr/bin/checkmodule. + + .semodule_package [string] Fully qualified path to the module + packager. Defaults to /usr/bin/semodule_package. + .output [file object] File object used to write verbose + output of the compililation and packaging process. + """ + def __init__(self, output=None): + """Create a ModuleCompiler instance, optionally with an + output file object for verbose output of the compilation process. + """ + self.mls = selinux.is_selinux_mls_enabled() + self.module = True + self.checkmodule = "/usr/bin/checkmodule" + self.semodule_package = "/usr/bin/semodule_package" + self.output = output + self.last_output = "" + self.refpol_makefile = defaults.refpolicy_makefile() + self.make = "/usr/bin/make" + + def o(self, str): + if self.output: + self.output.write(str + "\n") + self.last_output = str + + def run(self, command): + self.o(command) + rc, output = commands.getstatusoutput(command) + self.o(output) + + return rc + + def gen_filenames(self, sourcename): + """Generate the module and policy package filenames from + a source file name. The source file must be in the form + of "foo.te". This will generate "foo.mod" and "foo.pp". + + Returns a tuple with (modname, policypackage). + """ + splitname = sourcename.split(".") + if len(splitname) < 2: + raise RuntimeError("invalid sourcefile name %s (must end in .te)", sourcename) + # Handle other periods in the filename correctly + basename = ".".join(splitname[0:-1]) + modname = basename + ".mod" + packagename = basename + ".pp" + + return (modname, packagename) + + def create_module_package(self, sourcename, refpolicy=True): + """Create a module package saved in a packagename from a + sourcename. + + The create_module_package creates a module package saved in a + file named sourcename (.pp is the standard extension) from a + source file (.te is the standard extension). The source file + should contain SELinux policy statements appropriate for a + base or non-base module (depending on the setting of .module). + + Only file names are accepted, not open file objects or + descriptors because the command line SELinux tools are used. + + On error a RuntimeError will be raised with a descriptive + error message. + """ + if refpolicy: + self.refpol_build(sourcename) + else: + modname, packagename = self.gen_filenames(sourcename) + self.compile(sourcename, modname) + self.package(modname, packagename) + os.unlink(modname) + + def refpol_build(self, sourcename): + # Compile + command = self.make + " -f " + self.refpol_makefile + rc = self.run(command) + + # Raise an error if the process failed + if rc != 0: + raise RuntimeError("compilation failed:\n%s" % self.last_output) + + def compile(self, sourcename, modname): + s = [self.checkmodule] + if self.mls: + s.append("-M") + if self.module: + s.append("-m") + s.append("-o") + s.append(modname) + s.append(sourcename) + + rc = self.run(" ".join(s)) + if rc != 0: + raise RuntimeError("compilation failed:\n%s" % self.last_output) + + def package(self, modname, packagename): + s = [self.semodule_package] + s.append("-o") + s.append(packagename) + s.append("-m") + s.append(modname) + + rc = self.run(" ".join(s)) + if rc != 0: + raise RuntimeError("packaging failed [%s]" % self.last_output) + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/objectmodel.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/objectmodel.py new file mode 100644 index 0000000..88c8a1f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/objectmodel.py @@ -0,0 +1,172 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +This module provides knowledge object classes and permissions. It should +be used to keep this knowledge from leaking into the more generic parts of +the policy generation. +""" + +# Objects that can be implicitly typed - these objects do +# not _have_ to be implicitly typed (e.g., sockets can be +# explicitly labeled), but they often are. +# +# File is in this list for /proc/self +# +# This list is useful when dealing with rules that have a +# type (or param) used as both a subject and object. For +# example: +# +# allow httpd_t httpd_t : socket read; +# +# This rule makes sense because the socket was (presumably) created +# by a process with the type httpd_t. +implicitly_typed_objects = ["socket", "fd", "process", "file", "lnk_file", "fifo_file", + "dbus", "capability", "unix_stream_socket"] + +#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +# +#Information Flow +# +# All of the permissions in SELinux can be described in terms of +# information flow. For example, a read of a file is a flow of +# information from that file to the process reading. Viewing +# permissions in these terms can be used to model a varity of +# security properties. +# +# Here we have some infrastructure for understanding permissions +# in terms of information flow +# +#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +# Information flow deals with information either flowing from a subject +# to and object ("write") or to a subject from an object ("read"). Read +# or write is described from the subject point-of-view. It is also possible +# for a permission to represent both a read and write (though the flow is +# typical asymettric in terms of bandwidth). It is also possible for +# permission to not flow information (meaning that the result is pure +# side-effect). +# +# The following constants are for representing the directionality +# of information flow. +FLOW_NONE = 0 +FLOW_READ = 1 +FLOW_WRITE = 2 +FLOW_BOTH = FLOW_READ | FLOW_WRITE + +# These are used by the parser and for nice disply of the directions +str_to_dir = { "n" : FLOW_NONE, "r" : FLOW_READ, "w" : FLOW_WRITE, "b" : FLOW_BOTH } +dir_to_str = { FLOW_NONE : "n", FLOW_READ : "r", FLOW_WRITE : "w", FLOW_BOTH : "b" } + +class PermMap: + """A mapping between a permission and its information flow properties. + + PermMap represents the information flow properties of a single permission + including the direction (read, write, etc.) and an abstract representation + of the bandwidth of the flow (weight). + """ + def __init__(self, perm, dir, weight): + self.perm = perm + self.dir = dir + self.weight = weight + + def __repr__(self): + return "" % (self.perm, + dir_to_str[self.dir], + self.weight) + +class PermMappings: + """The information flow properties of a set of object classes and permissions. + + PermMappings maps one or more classes and permissions to their PermMap objects + describing their information flow charecteristics. + """ + def __init__(self): + self.classes = { } + self.default_weight = 5 + self.default_dir = FLOW_BOTH + + def from_file(self, fd): + """Read the permission mappings from a file. This reads the format used + by Apol in the setools suite. + """ + # This parsing is deliberitely picky and bails at the least error. It + # is assumed that the permission map file will be shipped as part + # of sepolgen and not user modified, so this is a reasonable design + # choice. If user supplied permission mappings are needed the parser + # should be made a little more robust and give better error messages. + cur = None + for line in fd: + fields = line.split() + if len(fields) == 0 or len(fields) == 1 or fields[0] == "#": + continue + if fields[0] == "class": + c = fields[1] + if self.classes.has_key(c): + raise ValueError("duplicate class in perm map") + self.classes[c] = { } + cur = self.classes[c] + else: + if len(fields) != 3: + raise ValueError("error in object classs permissions") + if cur is None: + raise ValueError("permission outside of class") + pm = PermMap(fields[0], str_to_dir[fields[1]], int(fields[2])) + cur[pm.perm] = pm + + def get(self, obj, perm): + """Get the permission map for the object permission. + + Returns: + PermMap representing the permission + Raises: + KeyError if the object or permission is not defined + """ + return self.classes[obj][perm] + + def getdefault(self, obj, perm): + """Get the permission map for the object permission or a default. + + getdefault is the same as get except that a default PermMap is + returned if the object class or permission is not defined. The + default is FLOW_BOTH with a weight of 5. + """ + try: + pm = self.classes[obj][perm] + except KeyError: + return PermMap(perm, self.default_dir, self.default_weight) + return pm + + def getdefault_direction(self, obj, perms): + dir = FLOW_NONE + for perm in perms: + pm = self.getdefault(obj, perm) + dir = dir | pm.dir + return dir + + def getdefault_distance(self, obj, perms): + total = 0 + for perm in perms: + pm = self.getdefault(obj, perm) + total += pm.weight + + return total + + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/output.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/output.py new file mode 100644 index 0000000..739452d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/output.py @@ -0,0 +1,173 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +Classes and functions for the output of reference policy modules. + +This module takes a refpolicy.Module object and formats it for +output using the ModuleWriter object. By separating the output +in this way the other parts of Madison can focus solely on +generating policy. This keeps the semantic / syntactic issues +cleanly separated from the formatting issues. +""" + +import refpolicy +import util + +class ModuleWriter: + def __init__(self): + self.fd = None + self.module = None + self.sort = True + self.requires = True + + def write(self, module, fd): + self.module = module + + if self.sort: + sort_filter(self.module) + + # FIXME - make this handle nesting + for node, depth in refpolicy.walktree(self.module, showdepth=True): + fd.write("%s\n" % str(node)) + +# Helper functions for sort_filter - this is all done old school +# C style rather than with polymorphic methods because this sorting +# is specific to output. It is not necessarily the comparison you +# want generally. + +# Compare two IdSets - we could probably do something clever +# with different here, but this works. +def id_set_cmp(x, y): + xl = util.set_to_list(x) + xl.sort() + yl = util.set_to_list(y) + yl.sort() + + if len(xl) != len(yl): + return cmp(xl[0], yl[0]) + for v in zip(xl, yl): + if v[0] != v[1]: + return cmp(v[0], v[1]) + return 0 + +# Compare two avrules +def avrule_cmp(a, b): + ret = id_set_cmp(a.src_types, b.src_types) + if ret is not 0: + return ret + ret = id_set_cmp(a.tgt_types, b.tgt_types) + if ret is not 0: + return ret + ret = id_set_cmp(a.obj_classes, b.obj_classes) + if ret is not 0: + return ret + + # At this point, who cares - just return something + return cmp(len(a.perms), len(b.perms)) + +# Compare two interface calls +def ifcall_cmp(a, b): + if a.args[0] != b.args[0]: + return cmp(a.args[0], b.args[0]) + return cmp(a.ifname, b.ifname) + +# Compare an two avrules or interface calls +def rule_cmp(a, b): + if isinstance(a, refpolicy.InterfaceCall): + if isinstance(b, refpolicy.InterfaceCall): + return ifcall_cmp(a, b) + else: + return id_set_cmp([a.args[0]], b.src_types) + else: + if isinstance(b, refpolicy.AVRule): + return avrule_cmp(a,b) + else: + return id_set_cmp(a.src_types, [b.args[0]]) + +def role_type_cmp(a, b): + return cmp(a.role, b.role) + +def sort_filter(module): + """Sort and group the output for readability. + """ + def sort_node(node): + c = [] + + # Module statement + for mod in node.module_declarations(): + c.append(mod) + c.append(refpolicy.Comment()) + + # Requires + for require in node.requires(): + c.append(require) + c.append(refpolicy.Comment()) + + # Rules + # + # We are going to group output by source type (which + # we assume is the first argument for interfaces). + rules = [] + rules.extend(node.avrules()) + rules.extend(node.interface_calls()) + rules.sort(rule_cmp) + + cur = None + sep_rules = [] + for rule in rules: + if isinstance(rule, refpolicy.InterfaceCall): + x = rule.args[0] + else: + x = util.first(rule.src_types) + + if cur != x: + if cur: + sep_rules.append(refpolicy.Comment()) + cur = x + comment = refpolicy.Comment() + comment.lines.append("============= %s ==============" % cur) + sep_rules.append(comment) + sep_rules.append(rule) + + c.extend(sep_rules) + + + ras = [] + ras.extend(node.role_types()) + ras.sort(role_type_cmp) + if len(ras): + comment = refpolicy.Comment() + comment.lines.append("============= ROLES ==============") + c.append(comment) + + + c.extend(ras) + + # Everything else + for child in node.children: + if child not in c: + c.append(child) + + node.children = c + + for node in module.nodes(): + sort_node(node) + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/policygen.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/policygen.py new file mode 100644 index 0000000..5f38577 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/policygen.py @@ -0,0 +1,402 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +""" +classes and algorithms for the generation of SELinux policy. +""" + +import itertools +import textwrap + +import refpolicy +import objectmodel +import access +import interfaces +import matching +import selinux.audit2why as audit2why +try: + from setools import * +except: + pass + +# Constants for the level of explanation from the generation +# routines +NO_EXPLANATION = 0 +SHORT_EXPLANATION = 1 +LONG_EXPLANATION = 2 + +class PolicyGenerator: + """Generate a reference policy module from access vectors. + + PolicyGenerator generates a new reference policy module + or updates an existing module based on requested access + in the form of access vectors. + + It generates allow rules and optionally module require + statements and reference policy interfaces. By default + only allow rules are generated. The methods .set_gen_refpol + and .set_gen_requires turns on interface generation and + requires generation respectively. + + PolicyGenerator can also optionally add comments explaining + why a particular access was allowed based on the audit + messages that generated the access. The access vectors + passed in must have the .audit_msgs field set correctly + and .explain set to SHORT|LONG_EXPLANATION to enable this + feature. + + The module created by PolicyGenerator can be passed to + output.ModuleWriter to output a text representation. + """ + def __init__(self, module=None): + """Initialize a PolicyGenerator with an optional + existing module. + + If the module paramater is not None then access + will be added to the passed in module. Otherwise + a new reference policy module will be created. + """ + self.ifgen = None + self.explain = NO_EXPLANATION + self.gen_requires = False + if module: + self.moduel = module + else: + self.module = refpolicy.Module() + + self.dontaudit = False + + self.domains = None + def set_gen_refpol(self, if_set=None, perm_maps=None): + """Set whether reference policy interfaces are generated. + + To turn on interface generation pass in an interface set + to use for interface generation. To turn off interface + generation pass in None. + + If interface generation is enabled requires generation + will also be enabled. + """ + if if_set: + self.ifgen = InterfaceGenerator(if_set, perm_maps) + self.gen_requires = True + else: + self.ifgen = None + self.__set_module_style() + + + def set_gen_requires(self, status=True): + """Set whether module requires are generated. + + Passing in true will turn on requires generation and + False will disable generation. If requires generation is + disabled interface generation will also be disabled and + can only be re-enabled via .set_gen_refpol. + """ + self.gen_requires = status + + def set_gen_explain(self, explain=SHORT_EXPLANATION): + """Set whether access is explained. + """ + self.explain = explain + + def set_gen_dontaudit(self, dontaudit): + self.dontaudit = dontaudit + + def __set_module_style(self): + if self.ifgen: + refpolicy = True + else: + refpolicy = False + for mod in self.module.module_declarations(): + mod.refpolicy = refpolicy + + def set_module_name(self, name, version="1.0"): + """Set the name of the module and optionally the version. + """ + # find an existing module declaration + m = None + for mod in self.module.module_declarations(): + m = mod + if not m: + m = refpolicy.ModuleDeclaration() + self.module.children.insert(0, m) + m.name = name + m.version = version + if self.ifgen: + m.refpolicy = True + else: + m.refpolicy = False + + def get_module(self): + # Generate the requires + if self.gen_requires: + gen_requires(self.module) + + """Return the generated module""" + return self.module + + def __add_allow_rules(self, avs): + for av in avs: + rule = refpolicy.AVRule(av) + if self.dontaudit: + rule.rule_type = rule.DONTAUDIT + rule.comment = "" + if self.explain: + rule.comment = str(refpolicy.Comment(explain_access(av, verbosity=self.explain))) + if av.type == audit2why.ALLOW: + rule.comment += "\n#!!!! This avc is allowed in the current policy" + if av.type == audit2why.DONTAUDIT: + rule.comment += "\n#!!!! This avc has a dontaudit rule in the current policy" + + if av.type == audit2why.BOOLEAN: + if len(av.data) > 1: + rule.comment += "\n#!!!! This avc can be allowed using one of the these booleans:\n# %s" % ", ".join(map(lambda x: x[0], av.data)) + else: + rule.comment += "\n#!!!! This avc can be allowed using the boolean '%s'" % av.data[0][0] + + if av.type == audit2why.CONSTRAINT: + rule.comment += "\n#!!!! This avc is a constraint violation. You would need to modify the attributes of either the source or target types to allow this access." + rule.comment += "\n#Constraint rule: " + rule.comment += "\n\t" + av.data[0] + for reason in av.data[1:]: + rule.comment += "\n#\tPossible cause is the source %s and target %s are different." % reason + + try: + if ( av.type == audit2why.TERULE and + "write" in av.perms and + ( "dir" in av.obj_class or "open" in av.perms )): + if not self.domains: + self.domains = seinfo(ATTRIBUTE, name="domain")[0]["types"] + types=[] + + for i in map(lambda x: x[TCONTEXT], sesearch([ALLOW], {SCONTEXT: av.src_type, CLASS: av.obj_class, PERMS: av.perms})): + if i not in self.domains: + types.append(i) + if len(types) == 1: + rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following type:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types)) + elif len(types) >= 1: + rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following types:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types)) + except: + pass + self.module.children.append(rule) + + + def add_access(self, av_set): + """Add the access from the access vector set to this + module. + """ + # Use the interface generator to split the access + # into raw allow rules and interfaces. After this + # a will contain a list of access that should be + # used as raw allow rules and the interfaces will + # be added to the module. + if self.ifgen: + raw_allow, ifcalls = self.ifgen.gen(av_set, self.explain) + self.module.children.extend(ifcalls) + else: + raw_allow = av_set + + # Generate the raw allow rules from the filtered list + self.__add_allow_rules(raw_allow) + + def add_role_types(self, role_type_set): + for role_type in role_type_set: + self.module.children.append(role_type) + +def explain_access(av, ml=None, verbosity=SHORT_EXPLANATION): + """Explain why a policy statement was generated. + + Return a string containing a text explanation of + why a policy statement was generated. The string is + commented and wrapped and can be directly inserted + into a policy. + + Params: + av - access vector representing the access. Should + have .audit_msgs set appropriately. + verbosity - the amount of explanation provided. Should + be set to NO_EXPLANATION, SHORT_EXPLANATION, or + LONG_EXPLANATION. + Returns: + list of strings - strings explaining the access or an empty + string if verbosity=NO_EXPLANATION or there is not sufficient + information to provide an explanation. + """ + s = [] + + def explain_interfaces(): + if not ml: + return + s.append(" Interface options:") + for match in ml.all(): + ifcall = call_interface(match.interface, ml.av) + s.append(' %s # [%d]' % (ifcall.to_string(), match.dist)) + + + # Format the raw audit data to explain why the + # access was requested - either long or short. + if verbosity == LONG_EXPLANATION: + for msg in av.audit_msgs: + s.append(' %s' % msg.header) + s.append(' scontext="%s" tcontext="%s"' % + (str(msg.scontext), str(msg.tcontext))) + s.append(' class="%s" perms="%s"' % + (msg.tclass, refpolicy.list_to_space_str(msg.accesses))) + s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) + s.extend(textwrap.wrap('message="' + msg.message + '"', 80, initial_indent=" ", + subsequent_indent=" ")) + explain_interfaces() + elif verbosity: + s.append(' src="%s" tgt="%s" class="%s", perms="%s"' % + (av.src_type, av.tgt_type, av.obj_class, av.perms.to_space_str())) + # For the short display we are only going to use the additional information + # from the first audit message. For the vast majority of cases this info + # will always be the same anyway. + if len(av.audit_msgs) > 0: + msg = av.audit_msgs[0] + s.append(' comm="%s" exe="%s" path="%s"' % (msg.comm, msg.exe, msg.path)) + explain_interfaces() + return s + +def param_comp(a, b): + return cmp(b.num, a.num) + +def call_interface(interface, av): + params = [] + args = [] + + params.extend(interface.params.values()) + params.sort(param_comp) + + ifcall = refpolicy.InterfaceCall() + ifcall.ifname = interface.name + + for i in range(len(params)): + if params[i].type == refpolicy.SRC_TYPE: + ifcall.args.append(av.src_type) + elif params[i].type == refpolicy.TGT_TYPE: + ifcall.args.append(av.tgt_type) + elif params[i].type == refpolicy.OBJ_CLASS: + ifcall.args.append(av.obj_class) + else: + print params[i].type + assert(0) + + assert(len(ifcall.args) > 0) + + return ifcall + +class InterfaceGenerator: + def __init__(self, ifs, perm_maps=None): + self.ifs = ifs + self.hack_check_ifs(ifs) + self.matcher = matching.AccessMatcher(perm_maps) + self.calls = [] + + def hack_check_ifs(self, ifs): + # FIXME: Disable interfaces we can't call - this is a hack. + # Because we don't handle roles, multiple paramaters, etc., + # etc., we must make certain we can actually use a returned + # interface. + for x in ifs.interfaces.values(): + params = [] + params.extend(x.params.values()) + params.sort(param_comp) + for i in range(len(params)): + # Check that the paramater position matches + # the number (e.g., $1 is the first arg). This + # will fail if the parser missed something. + if (i + 1) != params[i].num: + x.enabled = False + break + # Check that we can handle the param type (currently excludes + # roles. + if params[i].type not in [refpolicy.SRC_TYPE, refpolicy.TGT_TYPE, + refpolicy.OBJ_CLASS]: + x.enabled = False + break + + def gen(self, avs, verbosity): + raw_av = self.match(avs) + ifcalls = [] + for ml in self.calls: + ifcall = call_interface(ml.best().interface, ml.av) + if verbosity: + ifcall.comment = refpolicy.Comment(explain_access(ml.av, ml, verbosity)) + ifcalls.append((ifcall, ml)) + + d = [] + for ifcall, ifs in ifcalls: + found = False + for o_ifcall in d: + if o_ifcall.matches(ifcall): + if o_ifcall.comment and ifcall.comment: + o_ifcall.comment.merge(ifcall.comment) + found = True + if not found: + d.append(ifcall) + + return (raw_av, d) + + + def match(self, avs): + raw_av = [] + for av in avs: + ans = matching.MatchList() + self.matcher.search_ifs(self.ifs, av, ans) + if len(ans): + self.calls.append(ans) + else: + raw_av.append(av) + + return raw_av + + +def gen_requires(module): + """Add require statements to the module. + """ + def collect_requires(node): + r = refpolicy.Require() + for avrule in node.avrules(): + r.types.update(avrule.src_types) + r.types.update(avrule.tgt_types) + for obj in avrule.obj_classes: + r.add_obj_class(obj, avrule.perms) + + for ifcall in node.interface_calls(): + for arg in ifcall.args: + # FIXME - handle non-type arguments when we + # can actually figure those out. + r.types.add(arg) + + for role_type in node.role_types(): + r.roles.add(role_type.role) + r.types.update(role_type.types) + + r.types.discard("self") + + node.children.insert(0, r) + + # FUTURE - this is untested on modules with any sort of + # nesting + for node in module.nodes(): + collect_requires(node) + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/refparser.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/refparser.py new file mode 100644 index 0000000..83542d3 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/refparser.py @@ -0,0 +1,1128 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006-2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +# OVERVIEW +# +# +# This is a parser for the refpolicy policy "language" - i.e., the +# normal SELinux policy language plus the refpolicy style M4 macro +# constructs on top of that base language. This parser is primarily +# aimed at parsing the policy headers in order to create an abstract +# policy representation suitable for generating policy. +# +# Both the lexer and parser are included in this file. The are implemented +# using the Ply library (included with sepolgen). + +import sys +import os +import re +import traceback + +import refpolicy +import access +import defaults + +import lex +import yacc + +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +# +# lexer +# +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +tokens = ( + # basic tokens, punctuation + 'TICK', + 'SQUOTE', + 'OBRACE', + 'CBRACE', + 'SEMI', + 'COLON', + 'OPAREN', + 'CPAREN', + 'COMMA', + 'MINUS', + 'TILDE', + 'ASTERISK', + 'AMP', + 'BAR', + 'EXPL', + 'EQUAL', + 'FILENAME', + 'IDENTIFIER', + 'NUMBER', + 'PATH', + 'IPV6_ADDR', + # reserved words + # module + 'MODULE', + 'POLICY_MODULE', + 'REQUIRE', + # flask + 'SID', + 'GENFSCON', + 'FS_USE_XATTR', + 'FS_USE_TRANS', + 'FS_USE_TASK', + 'PORTCON', + 'NODECON', + 'NETIFCON', + 'PIRQCON', + 'IOMEMCON', + 'IOPORTCON', + 'PCIDEVICECON', + 'DEVICETREECON', + # object classes + 'CLASS', + # types and attributes + 'TYPEATTRIBUTE', + 'ROLEATTRIBUTE', + 'TYPE', + 'ATTRIBUTE', + 'ATTRIBUTE_ROLE', + 'ALIAS', + 'TYPEALIAS', + # conditional policy + 'BOOL', + 'TRUE', + 'FALSE', + 'IF', + 'ELSE', + # users and roles + 'ROLE', + 'TYPES', + # rules + 'ALLOW', + 'DONTAUDIT', + 'AUDITALLOW', + 'NEVERALLOW', + 'PERMISSIVE', + 'TYPE_TRANSITION', + 'TYPE_CHANGE', + 'TYPE_MEMBER', + 'RANGE_TRANSITION', + 'ROLE_TRANSITION', + # refpolicy keywords + 'OPT_POLICY', + 'INTERFACE', + 'TUNABLE_POLICY', + 'GEN_REQ', + 'TEMPLATE', + 'GEN_CONTEXT', + # m4 + 'IFELSE', + 'IFDEF', + 'IFNDEF', + 'DEFINE' + ) + +# All reserved keywords - see t_IDENTIFIER for how these are matched in +# the lexer. +reserved = { + # module + 'module' : 'MODULE', + 'policy_module' : 'POLICY_MODULE', + 'require' : 'REQUIRE', + # flask + 'sid' : 'SID', + 'genfscon' : 'GENFSCON', + 'fs_use_xattr' : 'FS_USE_XATTR', + 'fs_use_trans' : 'FS_USE_TRANS', + 'fs_use_task' : 'FS_USE_TASK', + 'portcon' : 'PORTCON', + 'nodecon' : 'NODECON', + 'netifcon' : 'NETIFCON', + 'pirqcon' : 'PIRQCON', + 'iomemcon' : 'IOMEMCON', + 'ioportcon' : 'IOPORTCON', + 'pcidevicecon' : 'PCIDEVICECON', + 'devicetreecon' : 'DEVICETREECON', + # object classes + 'class' : 'CLASS', + # types and attributes + 'typeattribute' : 'TYPEATTRIBUTE', + 'roleattribute' : 'ROLEATTRIBUTE', + 'type' : 'TYPE', + 'attribute' : 'ATTRIBUTE', + 'attribute_role' : 'ATTRIBUTE_ROLE', + 'alias' : 'ALIAS', + 'typealias' : 'TYPEALIAS', + # conditional policy + 'bool' : 'BOOL', + 'true' : 'TRUE', + 'false' : 'FALSE', + 'if' : 'IF', + 'else' : 'ELSE', + # users and roles + 'role' : 'ROLE', + 'types' : 'TYPES', + # rules + 'allow' : 'ALLOW', + 'dontaudit' : 'DONTAUDIT', + 'auditallow' : 'AUDITALLOW', + 'neverallow' : 'NEVERALLOW', + 'permissive' : 'PERMISSIVE', + 'type_transition' : 'TYPE_TRANSITION', + 'type_change' : 'TYPE_CHANGE', + 'type_member' : 'TYPE_MEMBER', + 'range_transition' : 'RANGE_TRANSITION', + 'role_transition' : 'ROLE_TRANSITION', + # refpolicy keywords + 'optional_policy' : 'OPT_POLICY', + 'interface' : 'INTERFACE', + 'tunable_policy' : 'TUNABLE_POLICY', + 'gen_require' : 'GEN_REQ', + 'template' : 'TEMPLATE', + 'gen_context' : 'GEN_CONTEXT', + # M4 + 'ifelse' : 'IFELSE', + 'ifndef' : 'IFNDEF', + 'ifdef' : 'IFDEF', + 'define' : 'DEFINE' + } + +# The ply lexer allows definition of tokens in 2 ways: regular expressions +# or functions. + +# Simple regex tokens +t_TICK = r'\`' +t_SQUOTE = r'\'' +t_OBRACE = r'\{' +t_CBRACE = r'\}' +# This will handle spurios extra ';' via the + +t_SEMI = r'\;+' +t_COLON = r'\:' +t_OPAREN = r'\(' +t_CPAREN = r'\)' +t_COMMA = r'\,' +t_MINUS = r'\-' +t_TILDE = r'\~' +t_ASTERISK = r'\*' +t_AMP = r'\&' +t_BAR = r'\|' +t_EXPL = r'\!' +t_EQUAL = r'\=' +t_NUMBER = r'[0-9\.]+' +t_PATH = r'/[a-zA-Z0-9)_\.\*/]*' +#t_IPV6_ADDR = r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]{0,4}:)*' + +# Ignore whitespace - this is a special token for ply that more efficiently +# ignores uninteresting tokens. +t_ignore = " \t" + +# More complex tokens +def t_IPV6_ADDR(t): + r'[a-fA-F0-9]{0,4}:[a-fA-F0-9]{0,4}:([a-fA-F0-9]|:)*' + # This is a function simply to force it sooner into + # the regex list + return t + +def t_m4comment(t): + r'dnl.*\n' + # Ignore all comments + t.lexer.lineno += 1 + +def t_refpolicywarn1(t): + r'define.*refpolicywarn\(.*\n' + # Ignore refpolicywarn statements - they sometimes + # contain text that we can't parse. + t.skip(1) + +def t_refpolicywarn(t): + r'refpolicywarn\(.*\n' + # Ignore refpolicywarn statements - they sometimes + # contain text that we can't parse. + t.lexer.lineno += 1 + +def t_IDENTIFIER(t): + r'[a-zA-Z_\$][a-zA-Z0-9_\-\+\.\$\*~]*' + # Handle any keywords + t.type = reserved.get(t.value,'IDENTIFIER') + return t + +def t_FILENAME(t): + r'\"[a-zA-Z0-9_\-\+\.\$\*~ :]+\"' + # Handle any keywords + t.type = reserved.get(t.value,'FILENAME') + return t + +def t_comment(t): + r'\#.*\n' + # Ignore all comments + t.lexer.lineno += 1 + +def t_error(t): + print "Illegal character '%s'" % t.value[0] + t.skip(1) + +def t_newline(t): + r'\n+' + t.lexer.lineno += len(t.value) + +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +# +# Parser +# +# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +# Global data used during parsing - making it global is easier than +# passing the state through the parsing functions. + +# m is the top-level data structure (stands for modules). +m = None +# error is either None (indicating no error) or a string error message. +error = None +parse_file = "" +# spt is the support macros (e.g., obj/perm sets) - it is an instance of +# refpolicy.SupportMacros and should always be present during parsing +# though it may not contain any macros. +spt = None +success = True + +# utilities +def collect(stmts, parent, val=None): + if stmts is None: + return + for s in stmts: + if s is None: + continue + s.parent = parent + if val is not None: + parent.children.insert(0, (val, s)) + else: + parent.children.insert(0, s) + +def expand(ids, s): + for id in ids: + if spt.has_key(id): + s.update(spt.by_name(id)) + else: + s.add(id) + +# Top-level non-terminal +def p_statements(p): + '''statements : statement + | statements statement + | empty + ''' + if len(p) == 2 and p[1]: + m.children.append(p[1]) + elif len(p) > 2 and p[2]: + m.children.append(p[2]) + +def p_statement(p): + '''statement : interface + | template + | obj_perm_set + | policy + | policy_module_stmt + | module_stmt + ''' + p[0] = p[1] + +def p_empty(p): + 'empty :' + pass + +# +# Reference policy language constructs +# + +# This is for the policy module statement (e.g., policy_module(foo,1.2.0)). +# We have a separate terminal for either the basic language module statement +# and interface calls to make it easier to identifier. +def p_policy_module_stmt(p): + 'policy_module_stmt : POLICY_MODULE OPAREN IDENTIFIER COMMA NUMBER CPAREN' + m = refpolicy.ModuleDeclaration() + m.name = p[3] + m.version = p[5] + m.refpolicy = True + p[0] = m + +def p_interface(p): + '''interface : INTERFACE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + x = refpolicy.Interface(p[4]) + collect(p[8], x) + p[0] = x + +def p_template(p): + '''template : TEMPLATE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + | DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + x = refpolicy.Template(p[4]) + collect(p[8], x) + p[0] = x + +def p_define(p): + '''define : DEFINE OPAREN TICK IDENTIFIER SQUOTE CPAREN''' + # This is for defining single M4 values (to be used later in ifdef statements). + # Example: define(`sulogin_no_pam'). We don't currently do anything with these + # but we should in the future when we correctly resolve ifdef statements. + p[0] = None + +def p_interface_stmts(p): + '''interface_stmts : policy + | interface_stmts policy + | empty + ''' + if len(p) == 2 and p[1]: + p[0] = p[1] + elif len(p) > 2: + if not p[1]: + if p[2]: + p[0] = p[2] + elif not p[2]: + p[0] = p[1] + else: + p[0] = p[1] + p[2] + +def p_optional_policy(p): + '''optional_policy : OPT_POLICY OPAREN TICK interface_stmts SQUOTE CPAREN + | OPT_POLICY OPAREN TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + o = refpolicy.OptionalPolicy() + collect(p[4], o, val=True) + if len(p) > 7: + collect(p[8], o, val=False) + p[0] = [o] + +def p_tunable_policy(p): + '''tunable_policy : TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + | TUNABLE_POLICY OPAREN TICK cond_expr SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN + ''' + x = refpolicy.TunablePolicy() + x.cond_expr = p[4] + collect(p[8], x, val=True) + if len(p) > 11: + collect(p[12], x, val=False) + p[0] = [x] + +def p_ifelse(p): + '''ifelse : IFELSE OPAREN TICK IDENTIFIER SQUOTE COMMA COMMA TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + | IFELSE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + ''' +# x = refpolicy.IfDef(p[4]) +# v = True +# collect(p[8], x, val=v) +# if len(p) > 12: +# collect(p[12], x, val=False) +# p[0] = [x] + pass + + +def p_ifdef(p): + '''ifdef : IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + | IFNDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + | IFDEF OPAREN TICK IDENTIFIER SQUOTE COMMA TICK interface_stmts SQUOTE COMMA TICK interface_stmts SQUOTE CPAREN optional_semi + ''' + x = refpolicy.IfDef(p[4]) + if p[1] == 'ifdef': + v = True + else: + v = False + collect(p[8], x, val=v) + if len(p) > 12: + collect(p[12], x, val=False) + p[0] = [x] + +def p_interface_call(p): + '''interface_call : IDENTIFIER OPAREN interface_call_param_list CPAREN + | IDENTIFIER OPAREN CPAREN + | IDENTIFIER OPAREN interface_call_param_list CPAREN SEMI''' + # Allow spurious semi-colons at the end of interface calls + i = refpolicy.InterfaceCall(ifname=p[1]) + if len(p) > 4: + i.args.extend(p[3]) + p[0] = i + +def p_interface_call_param(p): + '''interface_call_param : IDENTIFIER + | IDENTIFIER MINUS IDENTIFIER + | nested_id_set + | TRUE + | FALSE + | FILENAME + ''' + # Intentionally let single identifiers pass through + # List means set, non-list identifier + if len(p) == 2: + p[0] = p[1] + else: + p[0] = [p[1], "-" + p[3]] + +def p_interface_call_param_list(p): + '''interface_call_param_list : interface_call_param + | interface_call_param_list COMMA interface_call_param + ''' + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = p[1] + [p[3]] + + +def p_obj_perm_set(p): + 'obj_perm_set : DEFINE OPAREN TICK IDENTIFIER SQUOTE COMMA TICK names SQUOTE CPAREN' + s = refpolicy.ObjPermSet(p[4]) + s.perms = p[8] + p[0] = s + +# +# Basic SELinux policy language +# + +def p_policy(p): + '''policy : policy_stmt + | optional_policy + | tunable_policy + | ifdef + | ifelse + | conditional + ''' + p[0] = p[1] + +def p_policy_stmt(p): + '''policy_stmt : gen_require + | avrule_def + | typerule_def + | typeattribute_def + | roleattribute_def + | interface_call + | role_def + | role_allow + | permissive + | type_def + | typealias_def + | attribute_def + | attribute_role_def + | range_transition_def + | role_transition_def + | bool + | define + | initial_sid + | genfscon + | fs_use + | portcon + | nodecon + | netifcon + | pirqcon + | iomemcon + | ioportcon + | pcidevicecon + | devicetreecon + ''' + if p[1]: + p[0] = [p[1]] + +def p_module_stmt(p): + 'module_stmt : MODULE IDENTIFIER NUMBER SEMI' + m = refpolicy.ModuleDeclaration() + m.name = p[2] + m.version = p[3] + m.refpolicy = False + p[0] = m + +def p_gen_require(p): + '''gen_require : GEN_REQ OPAREN TICK requires SQUOTE CPAREN + | REQUIRE OBRACE requires CBRACE''' + # We ignore the require statements - they are redundant data from our point-of-view. + # Checkmodule will verify them later anyway so we just assume that they match what + # is in the rest of the interface. + pass + +def p_requires(p): + '''requires : require + | requires require + | ifdef + | requires ifdef + ''' + pass + +def p_require(p): + '''require : TYPE comma_list SEMI + | ROLE comma_list SEMI + | ATTRIBUTE comma_list SEMI + | ATTRIBUTE_ROLE comma_list SEMI + | CLASS comma_list SEMI + | BOOL comma_list SEMI + ''' + pass + +def p_security_context(p): + '''security_context : IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER + | IDENTIFIER COLON IDENTIFIER COLON IDENTIFIER COLON mls_range_def''' + # This will likely need some updates to handle complex levels + s = refpolicy.SecurityContext() + s.user = p[1] + s.role = p[3] + s.type = p[5] + if len(p) > 6: + s.level = p[7] + + p[0] = s + +def p_gen_context(p): + '''gen_context : GEN_CONTEXT OPAREN security_context COMMA mls_range_def CPAREN + ''' + # We actually store gen_context statements in a SecurityContext + # object - it knows how to output either a bare context or a + # gen_context statement. + s = p[3] + s.level = p[5] + + p[0] = s + +def p_context(p): + '''context : security_context + | gen_context + ''' + p[0] = p[1] + +def p_initial_sid(p): + '''initial_sid : SID IDENTIFIER context''' + s = refpolicy.InitialSid() + s.name = p[2] + s.context = p[3] + p[0] = s + +def p_genfscon(p): + '''genfscon : GENFSCON IDENTIFIER PATH context''' + + g = refpolicy.GenfsCon() + g.filesystem = p[2] + g.path = p[3] + g.context = p[4] + + p[0] = g + +def p_fs_use(p): + '''fs_use : FS_USE_XATTR IDENTIFIER context SEMI + | FS_USE_TASK IDENTIFIER context SEMI + | FS_USE_TRANS IDENTIFIER context SEMI + ''' + f = refpolicy.FilesystemUse() + if p[1] == "fs_use_xattr": + f.type = refpolicy.FilesystemUse.XATTR + elif p[1] == "fs_use_task": + f.type = refpolicy.FilesystemUse.TASK + elif p[1] == "fs_use_trans": + f.type = refpolicy.FilesystemUse.TRANS + + f.filesystem = p[2] + f.context = p[3] + + p[0] = f + +def p_portcon(p): + '''portcon : PORTCON IDENTIFIER NUMBER context + | PORTCON IDENTIFIER NUMBER MINUS NUMBER context''' + c = refpolicy.PortCon() + c.port_type = p[2] + if len(p) == 5: + c.port_number = p[3] + c.context = p[4] + else: + c.port_number = p[3] + "-" + p[4] + c.context = p[5] + + p[0] = c + +def p_nodecon(p): + '''nodecon : NODECON NUMBER NUMBER context + | NODECON IPV6_ADDR IPV6_ADDR context + ''' + n = refpolicy.NodeCon() + n.start = p[2] + n.end = p[3] + n.context = p[4] + + p[0] = n + +def p_netifcon(p): + 'netifcon : NETIFCON IDENTIFIER context context' + n = refpolicy.NetifCon() + n.interface = p[2] + n.interface_context = p[3] + n.packet_context = p[4] + + p[0] = n + +def p_pirqcon(p): + 'pirqcon : PIRQCON NUMBER context' + c = refpolicy.PirqCon() + c.pirq_number = p[2] + c.context = p[3] + + p[0] = c + +def p_iomemcon(p): + '''iomemcon : IOMEMCON NUMBER context + | IOMEMCON NUMBER MINUS NUMBER context''' + c = refpolicy.IomemCon() + if len(p) == 4: + c.device_mem = p[2] + c.context = p[3] + else: + c.device_mem = p[2] + "-" + p[3] + c.context = p[4] + + p[0] = c + +def p_ioportcon(p): + '''ioportcon : IOPORTCON NUMBER context + | IOPORTCON NUMBER MINUS NUMBER context''' + c = refpolicy.IoportCon() + if len(p) == 4: + c.ioport = p[2] + c.context = p[3] + else: + c.ioport = p[2] + "-" + p[3] + c.context = p[4] + + p[0] = c + +def p_pcidevicecon(p): + 'pcidevicecon : PCIDEVICECON NUMBER context' + c = refpolicy.PciDeviceCon() + c.device = p[2] + c.context = p[3] + + p[0] = c + +def p_devicetreecon(p): + 'devicetreecon : DEVICETREECON NUMBER context' + c = refpolicy.DevicetTeeCon() + c.path = p[2] + c.context = p[3] + + p[0] = c + +def p_mls_range_def(p): + '''mls_range_def : mls_level_def MINUS mls_level_def + | mls_level_def + ''' + p[0] = p[1] + if len(p) > 2: + p[0] = p[0] + "-" + p[3] + +def p_mls_level_def(p): + '''mls_level_def : IDENTIFIER COLON comma_list + | IDENTIFIER + ''' + p[0] = p[1] + if len(p) > 2: + p[0] = p[0] + ":" + ",".join(p[3]) + +def p_type_def(p): + '''type_def : TYPE IDENTIFIER COMMA comma_list SEMI + | TYPE IDENTIFIER SEMI + | TYPE IDENTIFIER ALIAS names SEMI + | TYPE IDENTIFIER ALIAS names COMMA comma_list SEMI + ''' + t = refpolicy.Type(p[2]) + if len(p) == 6: + if p[3] == ',': + t.attributes.update(p[4]) + else: + t.aliases = p[4] + elif len(p) > 4: + t.aliases = p[4] + if len(p) == 8: + t.attributes.update(p[6]) + p[0] = t + +def p_attribute_def(p): + 'attribute_def : ATTRIBUTE IDENTIFIER SEMI' + a = refpolicy.Attribute(p[2]) + p[0] = a + +def p_attribute_role_def(p): + 'attribute_role_def : ATTRIBUTE_ROLE IDENTIFIER SEMI' + a = refpolicy.Attribute_Role(p[2]) + p[0] = a + +def p_typealias_def(p): + 'typealias_def : TYPEALIAS IDENTIFIER ALIAS names SEMI' + t = refpolicy.TypeAlias() + t.type = p[2] + t.aliases = p[4] + p[0] = t + +def p_role_def(p): + '''role_def : ROLE IDENTIFIER TYPES comma_list SEMI + | ROLE IDENTIFIER SEMI''' + r = refpolicy.Role() + r.role = p[2] + if len(p) > 4: + r.types.update(p[4]) + p[0] = r + +def p_role_allow(p): + 'role_allow : ALLOW names names SEMI' + r = refpolicy.RoleAllow() + r.src_roles = p[2] + r.tgt_roles = p[3] + p[0] = r + +def p_permissive(p): + 'permissive : PERMISSIVE names SEMI' + t.skip(1) + +def p_avrule_def(p): + '''avrule_def : ALLOW names names COLON names names SEMI + | DONTAUDIT names names COLON names names SEMI + | AUDITALLOW names names COLON names names SEMI + | NEVERALLOW names names COLON names names SEMI + ''' + a = refpolicy.AVRule() + if p[1] == 'dontaudit': + a.rule_type = refpolicy.AVRule.DONTAUDIT + elif p[1] == 'auditallow': + a.rule_type = refpolicy.AVRule.AUDITALLOW + elif p[1] == 'neverallow': + a.rule_type = refpolicy.AVRule.NEVERALLOW + a.src_types = p[2] + a.tgt_types = p[3] + a.obj_classes = p[5] + a.perms = p[6] + p[0] = a + +def p_typerule_def(p): + '''typerule_def : TYPE_TRANSITION names names COLON names IDENTIFIER SEMI + | TYPE_TRANSITION names names COLON names IDENTIFIER FILENAME SEMI + | TYPE_TRANSITION names names COLON names IDENTIFIER IDENTIFIER SEMI + | TYPE_CHANGE names names COLON names IDENTIFIER SEMI + | TYPE_MEMBER names names COLON names IDENTIFIER SEMI + ''' + t = refpolicy.TypeRule() + if p[1] == 'type_change': + t.rule_type = refpolicy.TypeRule.TYPE_CHANGE + elif p[1] == 'type_member': + t.rule_type = refpolicy.TypeRule.TYPE_MEMBER + t.src_types = p[2] + t.tgt_types = p[3] + t.obj_classes = p[5] + t.dest_type = p[6] + t.file_name = p[7] + p[0] = t + +def p_bool(p): + '''bool : BOOL IDENTIFIER TRUE SEMI + | BOOL IDENTIFIER FALSE SEMI''' + b = refpolicy.Bool() + b.name = p[2] + if p[3] == "true": + b.state = True + else: + b.state = False + p[0] = b + +def p_conditional(p): + ''' conditional : IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE + | IF OPAREN cond_expr CPAREN OBRACE interface_stmts CBRACE ELSE OBRACE interface_stmts CBRACE + ''' + c = refpolicy.Conditional() + c.cond_expr = p[3] + collect(p[6], c, val=True) + if len(p) > 8: + collect(p[10], c, val=False) + p[0] = [c] + +def p_typeattribute_def(p): + '''typeattribute_def : TYPEATTRIBUTE IDENTIFIER comma_list SEMI''' + t = refpolicy.TypeAttribute() + t.type = p[2] + t.attributes.update(p[3]) + p[0] = t + +def p_roleattribute_def(p): + '''roleattribute_def : ROLEATTRIBUTE IDENTIFIER comma_list SEMI''' + t = refpolicy.RoleAttribute() + t.role = p[2] + t.roleattributes.update(p[3]) + p[0] = t + +def p_range_transition_def(p): + '''range_transition_def : RANGE_TRANSITION names names COLON names mls_range_def SEMI + | RANGE_TRANSITION names names names SEMI''' + pass + +def p_role_transition_def(p): + '''role_transition_def : ROLE_TRANSITION names names names SEMI''' + pass + +def p_cond_expr(p): + '''cond_expr : IDENTIFIER + | EXPL cond_expr + | cond_expr AMP AMP cond_expr + | cond_expr BAR BAR cond_expr + | cond_expr EQUAL EQUAL cond_expr + | cond_expr EXPL EQUAL cond_expr + ''' + l = len(p) + if l == 2: + p[0] = [p[1]] + elif l == 3: + p[0] = [p[1]] + p[2] + else: + p[0] = p[1] + [p[2] + p[3]] + p[4] + + +# +# Basic terminals +# + +# Identifiers and lists of identifiers. These must +# be handled somewhat gracefully. Names returns an IdSet and care must +# be taken that this is _assigned_ to an object to correctly update +# all of the flags (as opposed to using update). The other terminals +# return list - this is to preserve ordering if it is important for +# parsing (for example, interface_call must retain the ordering). Other +# times the list should be used to update an IdSet. + +def p_names(p): + '''names : identifier + | nested_id_set + | asterisk + | TILDE identifier + | TILDE nested_id_set + | IDENTIFIER MINUS IDENTIFIER + ''' + s = refpolicy.IdSet() + if len(p) < 3: + expand(p[1], s) + elif len(p) == 3: + expand(p[2], s) + s.compliment = True + else: + expand([p[1]]) + s.add("-" + p[3]) + p[0] = s + +def p_identifier(p): + 'identifier : IDENTIFIER' + p[0] = [p[1]] + +def p_asterisk(p): + 'asterisk : ASTERISK' + p[0] = [p[1]] + +def p_nested_id_set(p): + '''nested_id_set : OBRACE nested_id_list CBRACE + ''' + p[0] = p[2] + +def p_nested_id_list(p): + '''nested_id_list : nested_id_element + | nested_id_list nested_id_element + ''' + if len(p) == 2: + p[0] = p[1] + else: + p[0] = p[1] + p[2] + +def p_nested_id_element(p): + '''nested_id_element : identifier + | MINUS IDENTIFIER + | nested_id_set + ''' + if len(p) == 2: + p[0] = p[1] + else: + # For now just leave the '-' + str = "-" + p[2] + p[0] = [str] + +def p_comma_list(p): + '''comma_list : nested_id_list + | comma_list COMMA nested_id_list + ''' + if len(p) > 2: + p[1] = p[1] + p[3] + p[0] = p[1] + +def p_optional_semi(p): + '''optional_semi : SEMI + | empty''' + pass + + +# +# Interface to the parser +# + +def p_error(tok): + global error, parse_file, success, parser + error = "%s: Syntax error on line %d %s [type=%s]" % (parse_file, tok.lineno, tok.value, tok.type) + print error + success = False + +def prep_spt(spt): + if not spt: + return { } + map = {} + for x in spt: + map[x.name] = x + +parser = None +lexer = None +def create_globals(module, support, debug): + global parser, lexer, m, spt + + if not parser: + lexer = lex.lex() + parser = yacc.yacc(method="LALR", debug=debug, write_tables=0) + + if module is not None: + m = module + else: + m = refpolicy.Module() + + if not support: + spt = refpolicy.SupportMacros() + else: + spt = support + +def parse(text, module=None, support=None, debug=False): + create_globals(module, support, debug) + global error, parser, lexer, success + + success = True + + try: + parser.parse(text, debug=debug, lexer=lexer) + except Exception, e: + parser = None + lexer = None + error = "internal parser error: %s" % str(e) + "\n" + traceback.format_exc() + + if not success: + # force the parser and lexer to be rebuilt - we have some problems otherwise + parser = None + msg = 'could not parse text: "%s"' % error + raise ValueError(msg) + return m + +def list_headers(root): + modules = [] + support_macros = None + + for dirpath, dirnames, filenames in os.walk(root): + for name in filenames: + modname = os.path.splitext(name) + filename = os.path.join(dirpath, name) + + if modname[1] == '.spt': + if name == "obj_perm_sets.spt": + support_macros = filename + elif len(re.findall("patterns", modname[0])): + modules.append((modname[0], filename)) + elif modname[1] == '.if': + modules.append((modname[0], filename)) + + return (modules, support_macros) + + +def parse_headers(root, output=None, expand=True, debug=False): + import util + + headers = refpolicy.Headers() + + modules = [] + support_macros = None + + if os.path.isfile(root): + name = os.path.split(root)[1] + if name == '': + raise ValueError("Invalid file name %s" % root) + modname = os.path.splitext(name) + modules.append((modname[0], root)) + all_modules, support_macros = list_headers(defaults.headers()) + else: + modules, support_macros = list_headers(root) + + if expand and not support_macros: + raise ValueError("could not find support macros (obj_perm_sets.spt)") + + def o(msg): + if output: + output.write(msg) + + def parse_file(f, module, spt=None): + global parse_file + if debug: + o("parsing file %s\n" % f) + try: + fd = open(f) + txt = fd.read() + fd.close() + parse_file = f + parse(txt, module, spt, debug) + except IOError, e: + return + except ValueError, e: + raise ValueError("error parsing file %s: %s" % (f, str(e))) + + spt = None + if support_macros: + o("Parsing support macros (%s): " % support_macros) + spt = refpolicy.SupportMacros() + parse_file(support_macros, spt) + + headers.children.append(spt) + + # FIXME: Total hack - add in can_exec rather than parse the insanity + # of misc_macros. We are just going to pretend that this is an interface + # to make the expansion work correctly. + can_exec = refpolicy.Interface("can_exec") + av = access.AccessVector(["$1","$2","file","execute_no_trans","open", "read", + "getattr","lock","execute","ioctl"]) + + can_exec.children.append(refpolicy.AVRule(av)) + headers.children.append(can_exec) + + o("done.\n") + + if output and not debug: + status = util.ConsoleProgressBar(sys.stdout, steps=len(modules)) + status.start("Parsing interface files") + + failures = [] + for x in modules: + m = refpolicy.Module() + m.name = x[0] + try: + if expand: + parse_file(x[1], m, spt) + else: + parse_file(x[1], m) + except ValueError, e: + o(str(e) + "\n") + failures.append(x[1]) + continue + + headers.children.append(m) + if output and not debug: + status.step() + + if len(failures): + o("failed to parse some headers: %s" % ", ".join(failures)) + + return headers diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/refpolicy.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/refpolicy.py new file mode 100644 index 0000000..b8ed5c1 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/refpolicy.py @@ -0,0 +1,917 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +import string +import itertools +import selinux + +# OVERVIEW +# +# This file contains objects and functions used to represent the reference +# policy (including the headers, M4 macros, and policy language statements). +# +# This representation is very different from the semantic representation +# used in libsepol. Instead, it is a more typical abstract representation +# used by the first stage of compilers. It is basically a parse tree. +# +# This choice is intentional as it allows us to handle the unprocessed +# M4 statements - including the $1 style arguments - and to more easily generate +# the data structures that we need for policy generation. +# + +# Constans for referring to fields +SRC_TYPE = 0 +TGT_TYPE = 1 +OBJ_CLASS = 2 +PERMS = 3 +ROLE = 4 +DEST_TYPE = 5 + +# String represenations of the above constants +field_to_str = ["source", "target", "object", "permission", "role", "destination" ] +str_to_field = { "source" : SRC_TYPE, "target" : TGT_TYPE, "object" : OBJ_CLASS, + "permission" : PERMS, "role" : ROLE, "destination" : DEST_TYPE } + +# Base Classes + +class PolicyBase: + def __init__(self, parent=None): + self.parent = None + self.comment = None + +class Node(PolicyBase): + """Base class objects produced from parsing the reference policy. + + The Node class is used as the base class for any non-leaf + object produced by parsing the reference policy. This object + should contain a reference to its parent (or None for a top-level + object) and 0 or more children. + + The general idea here is to have a very simple tree structure. Children + are not separated out by type. Instead the tree structure represents + fairly closely the real structure of the policy statements. + + The object should be iterable - by default over all children but + subclasses are free to provide additional iterators over a subset + of their childre (see Interface for example). + """ + + def __init__(self, parent=None): + PolicyBase.__init__(self, parent) + self.children = [] + + def __iter__(self): + return iter(self.children) + + # Not all of the iterators will return something on all Nodes, but + # they won't explode either. Putting them here is just easier. + + # Top level nodes + + def nodes(self): + return itertools.ifilter(lambda x: isinstance(x, Node), walktree(self)) + + def modules(self): + return itertools.ifilter(lambda x: isinstance(x, Module), walktree(self)) + + def interfaces(self): + return itertools.ifilter(lambda x: isinstance(x, Interface), walktree(self)) + + def templates(self): + return itertools.ifilter(lambda x: isinstance(x, Template), walktree(self)) + + def support_macros(self): + return itertools.ifilter(lambda x: isinstance(x, SupportMacros), walktree(self)) + + # Common policy statements + + def module_declarations(self): + return itertools.ifilter(lambda x: isinstance(x, ModuleDeclaration), walktree(self)) + + def interface_calls(self): + return itertools.ifilter(lambda x: isinstance(x, InterfaceCall), walktree(self)) + + def avrules(self): + return itertools.ifilter(lambda x: isinstance(x, AVRule), walktree(self)) + + def typerules(self): + return itertools.ifilter(lambda x: isinstance(x, TypeRule), walktree(self)) + + def typeattributes(self): + """Iterate over all of the TypeAttribute children of this Interface.""" + return itertools.ifilter(lambda x: isinstance(x, TypeAttribute), walktree(self)) + + def roleattributes(self): + """Iterate over all of the RoleAttribute children of this Interface.""" + return itertools.ifilter(lambda x: isinstance(x, RoleAttribute), walktree(self)) + + def requires(self): + return itertools.ifilter(lambda x: isinstance(x, Require), walktree(self)) + + def roles(self): + return itertools.ifilter(lambda x: isinstance(x, Role), walktree(self)) + + def role_allows(self): + return itertools.ifilter(lambda x: isinstance(x, RoleAllow), walktree(self)) + + def role_types(self): + return itertools.ifilter(lambda x: isinstance(x, RoleType), walktree(self)) + + def __str__(self): + if self.comment: + return str(self.comment) + "\n" + self.to_string() + else: + return self.to_string() + + def __repr__(self): + return "<%s(%s)>" % (self.__class__.__name__, self.to_string()) + + def to_string(self): + return "" + + +class Leaf(PolicyBase): + def __init__(self, parent=None): + PolicyBase.__init__(self, parent) + + def __str__(self): + if self.comment: + return str(self.comment) + "\n" + self.to_string() + else: + return self.to_string() + + def __repr__(self): + return "<%s(%s)>" % (self.__class__.__name__, self.to_string()) + + def to_string(self): + return "" + + + +# Utility functions + +def walktree(node, depthfirst=True, showdepth=False, type=None): + """Iterate over a Node and its Children. + + The walktree function iterates over a tree containing Nodes and + leaf objects. The iteration can perform a depth first or a breadth + first traversal of the tree (controlled by the depthfirst + paramater. The passed in node will be returned. + + This function will only work correctly for trees - arbitrary graphs + will likely cause infinite looping. + """ + # We control depth first / versus breadth first by + # how we pop items off of the node stack. + if depthfirst: + index = -1 + else: + index = 0 + + stack = [(node, 0)] + while len(stack) > 0: + cur, depth = stack.pop(index) + if showdepth: + yield cur, depth + else: + yield cur + + # If the node is not a Node instance it must + # be a leaf - so no need to add it to the stack + if isinstance(cur, Node): + items = [] + i = len(cur.children) - 1 + while i >= 0: + if type is None or isinstance(cur.children[i], type): + items.append((cur.children[i], depth + 1)) + i -= 1 + + stack.extend(items) + +def walknode(node, type=None): + """Iterate over the direct children of a Node. + + The walktree function iterates over the children of a Node. + Unlike walktree it does note return the passed in node or + the children of any Node objects (that is, it does not go + beyond the current level in the tree). + """ + for x in node: + if type is None or isinstance(x, type): + yield x + + +def list_to_space_str(s, cont=('{', '}')): + """Convert a set (or any sequence type) into a string representation + formatted to match SELinux space separated list conventions. + + For example the list ['read', 'write'] would be converted into: + '{ read write }' + """ + l = len(s) + str = "" + if l < 1: + raise ValueError("cannot convert 0 len set to string") + str = " ".join(s) + if l == 1: + return str + else: + return cont[0] + " " + str + " " + cont[1] + +def list_to_comma_str(s): + l = len(s) + if l < 1: + raise ValueError("cannot conver 0 len set to comma string") + + return ", ".join(s) + +# Basic SELinux types + +class IdSet(set): + def __init__(self, list=None): + if list: + set.__init__(self, list) + else: + set.__init__(self) + self.compliment = False + + def to_space_str(self): + return list_to_space_str(self) + + def to_comma_str(self): + return list_to_comma_str(self) + +class SecurityContext(Leaf): + """An SELinux security context with optional MCS / MLS fields.""" + def __init__(self, context=None, parent=None): + """Create a SecurityContext object, optionally from a string. + + Parameters: + [context] - string representing a security context. Same format + as a string passed to the from_string method. + """ + Leaf.__init__(self, parent) + self.user = "" + self.role = "" + self.type = "" + self.level = None + if context is not None: + self.from_string(context) + + def from_string(self, context): + """Parse a string representing a context into a SecurityContext. + + The string should be in the standard format - e.g., + 'user:role:type:level'. + + Raises ValueError if the string is not parsable as a security context. + """ + fields = context.split(":") + if len(fields) < 3: + raise ValueError("context string [%s] not in a valid format" % context) + + self.user = fields[0] + self.role = fields[1] + self.type = fields[2] + if len(fields) > 3: + # FUTURE - normalize level fields to allow more comparisons to succeed. + self.level = string.join(fields[3:], ':') + else: + self.level = None + + def __eq__(self, other): + """Compare two SecurityContext objects - all fields must be exactly the + the same for the comparison to work. It is possible for the level fields + to be semantically the same yet syntactically different - in this case + this function will return false. + """ + return self.user == other.user and \ + self.role == other.role and \ + self.type == other.type and \ + self.level == other.level + + def to_string(self, default_level=None): + """Return a string representing this security context. + + By default, the string will contiain a MCS / MLS level + potentially from the default which is passed in if none was + set. + + Arguments: + default_level - the default level to use if self.level is an + empty string. + + Returns: + A string represening the security context in the form + 'user:role:type:level'. + """ + fields = [self.user, self.role, self.type] + if self.level is None: + if default_level is None: + if selinux.is_selinux_mls_enabled() == 1: + fields.append("s0") + else: + fields.append(default_level) + else: + fields.append(self.level) + return ":".join(fields) + +class ObjectClass(Leaf): + """SELinux object class and permissions. + + This class is a basic representation of an SELinux object + class - it does not represent separate common permissions - + just the union of the common and class specific permissions. + It is meant to be convenient for policy generation. + """ + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + self.perms = IdSet() + +# Basic statements + +class TypeAttribute(Leaf): + """SElinux typeattribute statement. + + This class represents a typeattribute statement. + """ + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = "" + self.attributes = IdSet() + + def to_string(self): + return "typeattribute %s %s;" % (self.type, self.attributes.to_comma_str()) + +class RoleAttribute(Leaf): + """SElinux roleattribute statement. + + This class represents a roleattribute statement. + """ + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.role = "" + self.roleattributes = IdSet() + + def to_string(self): + return "roleattribute %s %s;" % (self.role, self.roleattributes.to_comma_str()) + + +class Role(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.role = "" + self.types = IdSet() + + def to_string(self): + s = "" + for t in self.types: + s += "role %s types %s;\n" % (self.role, t) + return s + +class Type(Leaf): + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + self.attributes = IdSet() + self.aliases = IdSet() + + def to_string(self): + s = "type %s" % self.name + if len(self.aliases) > 0: + s = s + "alias %s" % self.aliases.to_space_str() + if len(self.attributes) > 0: + s = s + ", %s" % self.attributes.to_comma_str() + return s + ";" + +class TypeAlias(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = "" + self.aliases = IdSet() + + def to_string(self): + return "typealias %s alias %s;" % (self.type, self.aliases.to_space_str()) + +class Attribute(Leaf): + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + + def to_string(self): + return "attribute %s;" % self.name + +class Attribute_Role(Leaf): + def __init__(self, name="", parent=None): + Leaf.__init__(self, parent) + self.name = name + + def to_string(self): + return "attribute_role %s;" % self.name + + +# Classes representing rules + +class AVRule(Leaf): + """SELinux access vector (AV) rule. + + The AVRule class represents all varieties of AV rules including + allow, dontaudit, and auditallow (indicated by the flags self.ALLOW, + self.DONTAUDIT, and self.AUDITALLOW respectively). + + The source and target types, object classes, and perms are all represented + by sets containing strings. Sets are used to make it simple to add + strings repeatedly while avoiding duplicates. + + No checking is done to make certain that the symbols are valid or + consistent (e.g., perms that don't match the object classes). It is + even possible to put invalid types like '$1' into the rules to allow + storage of the reference policy interfaces. + """ + ALLOW = 0 + DONTAUDIT = 1 + AUDITALLOW = 2 + NEVERALLOW = 3 + + def __init__(self, av=None, parent=None): + Leaf.__init__(self, parent) + self.src_types = IdSet() + self.tgt_types = IdSet() + self.obj_classes = IdSet() + self.perms = IdSet() + self.rule_type = self.ALLOW + if av: + self.from_av(av) + + def __rule_type_str(self): + if self.rule_type == self.ALLOW: + return "allow" + elif self.rule_type == self.DONTAUDIT: + return "dontaudit" + else: + return "auditallow" + + def from_av(self, av): + """Add the access from an access vector to this allow + rule. + """ + self.src_types.add(av.src_type) + if av.src_type == av.tgt_type: + self.tgt_types.add("self") + else: + self.tgt_types.add(av.tgt_type) + self.obj_classes.add(av.obj_class) + self.perms.update(av.perms) + + def to_string(self): + """Return a string representation of the rule + that is a valid policy language representation (assuming + that the types, object class, etc. are valie). + """ + return "%s %s %s:%s %s;" % (self.__rule_type_str(), + self.src_types.to_space_str(), + self.tgt_types.to_space_str(), + self.obj_classes.to_space_str(), + self.perms.to_space_str()) +class TypeRule(Leaf): + """SELinux type rules. + + This class is very similar to the AVRule class, but is for representing + the type rules (type_trans, type_change, and type_member). The major + difference is the lack of perms and only and sing destination type. + """ + TYPE_TRANSITION = 0 + TYPE_CHANGE = 1 + TYPE_MEMBER = 2 + + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.src_types = IdSet() + self.tgt_types = IdSet() + self.obj_classes = IdSet() + self.dest_type = "" + self.rule_type = self.TYPE_TRANSITION + + def __rule_type_str(self): + if self.rule_type == self.TYPE_TRANSITION: + return "type_transition" + elif self.rule_type == self.TYPE_CHANGE: + return "type_change" + else: + return "type_member" + + def to_string(self): + return "%s %s %s:%s %s;" % (self.__rule_type_str(), + self.src_types.to_space_str(), + self.tgt_types.to_space_str(), + self.obj_classes.to_space_str(), + self.dest_type) + +class RoleAllow(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.src_roles = IdSet() + self.tgt_roles = IdSet() + + def to_string(self): + return "allow %s %s;" % (self.src_roles.to_comma_str(), + self.tgt_roles.to_comma_str()) + +class RoleType(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.role = "" + self.types = IdSet() + + def to_string(self): + s = "" + for t in self.types: + s += "role %s types %s;\n" % (self.role, t) + return s + +class ModuleDeclaration(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.name = "" + self.version = "" + self.refpolicy = False + + def to_string(self): + if self.refpolicy: + return "policy_module(%s, %s)" % (self.name, self.version) + else: + return "module %s %s;" % (self.name, self.version) + +class Conditional(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + self.cond_expr = [] + + def to_string(self): + return "[If %s]" % list_to_space_str(self.cond_expr, cont=("", "")) + +class Bool(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.name = "" + self.state = False + + def to_string(self): + s = "bool %s " % self.name + if s.state: + return s + "true" + else: + return s + "false" + +class InitialSid(Leaf): + def __init(self, parent=None): + Leaf.__init__(self, parent) + self.name = "" + self.context = None + + def to_string(self): + return "sid %s %s" % (self.name, str(self.context)) + +class GenfsCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.filesystem = "" + self.path = "" + self.context = None + + def to_string(self): + return "genfscon %s %s %s" % (self.filesystem, self.path, str(self.context)) + +class FilesystemUse(Leaf): + XATTR = 1 + TRANS = 2 + TASK = 3 + + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = self.XATTR + self.filesystem = "" + self.context = None + + def to_string(self): + s = "" + if self.type == XATTR: + s = "fs_use_xattr " + elif self.type == TRANS: + s = "fs_use_trans " + elif self.type == TASK: + s = "fs_use_task " + + return "%s %s %s;" % (s, self.filesystem, str(self.context)) + +class PortCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.port_type = "" + self.port_number = "" + self.context = None + + def to_string(self): + return "portcon %s %s %s" % (self.port_type, self.port_number, str(self.context)) + +class NodeCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.start = "" + self.end = "" + self.context = None + + def to_string(self): + return "nodecon %s %s %s" % (self.start, self.end, str(self.context)) + +class NetifCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.interface = "" + self.interface_context = None + self.packet_context = None + + def to_string(self): + return "netifcon %s %s %s" % (self.interface, str(self.interface_context), + str(self.packet_context)) +class PirqCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.pirq_number = "" + self.context = None + + def to_string(self): + return "pirqcon %s %s" % (self.pirq_number, str(self.context)) + +class IomemCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.device_mem = "" + self.context = None + + def to_string(self): + return "iomemcon %s %s" % (self.device_mem, str(self.context)) + +class IoportCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.ioport = "" + self.context = None + + def to_string(self): + return "ioportcon %s %s" % (self.ioport, str(self.context)) + +class PciDeviceCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.device = "" + self.context = None + + def to_string(self): + return "pcidevicecon %s %s" % (self.device, str(self.context)) + +class DeviceTreeCon(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.path = "" + self.context = None + + def to_string(self): + return "devicetreecon %s %s" % (self.path, str(self.context)) + +# Reference policy specific types + +def print_tree(head): + for node, depth in walktree(head, showdepth=True): + s = "" + for i in range(depth): + s = s + "\t" + print s + str(node) + + +class Headers(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + + def to_string(self): + return "[Headers]" + + +class Module(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + + def to_string(self): + return "" + +class Interface(Node): + """A reference policy interface definition. + + This class represents a reference policy interface definition. + """ + def __init__(self, name="", parent=None): + Node.__init__(self, parent) + self.name = name + + def to_string(self): + return "[Interface name: %s]" % self.name + +class TunablePolicy(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + self.cond_expr = [] + + def to_string(self): + return "[Tunable Policy %s]" % list_to_space_str(self.cond_expr, cont=("", "")) + +class Template(Node): + def __init__(self, name="", parent=None): + Node.__init__(self, parent) + self.name = name + + def to_string(self): + return "[Template name: %s]" % self.name + +class IfDef(Node): + def __init__(self, name="", parent=None): + Node.__init__(self, parent) + self.name = name + + def to_string(self): + return "[Ifdef name: %s]" % self.name + +class InterfaceCall(Leaf): + def __init__(self, ifname="", parent=None): + Leaf.__init__(self, parent) + self.ifname = ifname + self.args = [] + self.comments = [] + + def matches(self, other): + if self.ifname != other.ifname: + return False + if len(self.args) != len(other.args): + return False + for a,b in zip(self.args, other.args): + if a != b: + return False + return True + + def to_string(self): + s = "%s(" % self.ifname + i = 0 + for a in self.args: + if isinstance(a, list): + str = list_to_space_str(a) + else: + str = a + + if i != 0: + s = s + ", %s" % str + else: + s = s + str + i += 1 + return s + ")" + +class OptionalPolicy(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + + def to_string(self): + return "[Optional Policy]" + +class SupportMacros(Node): + def __init__(self, parent=None): + Node.__init__(self, parent) + self.map = None + + def to_string(self): + return "[Support Macros]" + + def __expand_perm(self, perm): + # Recursive expansion - the assumption is that these + # are ordered correctly so that no macro is used before + # it is defined + s = set() + if self.map.has_key(perm): + for p in self.by_name(perm): + s.update(self.__expand_perm(p)) + else: + s.add(perm) + return s + + def __gen_map(self): + self.map = {} + for x in self: + exp_perms = set() + for perm in x.perms: + exp_perms.update(self.__expand_perm(perm)) + self.map[x.name] = exp_perms + + def by_name(self, name): + if not self.map: + self.__gen_map() + return self.map[name] + + def has_key(self, name): + if not self.map: + self.__gen_map() + return self.map.has_key(name) + +class Require(Leaf): + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.types = IdSet() + self.obj_classes = { } + self.roles = IdSet() + self.data = IdSet() + self.users = IdSet() + + def add_obj_class(self, obj_class, perms): + p = self.obj_classes.setdefault(obj_class, IdSet()) + p.update(perms) + + + def to_string(self): + s = [] + s.append("require {") + for type in self.types: + s.append("\ttype %s;" % type) + for obj_class, perms in self.obj_classes.items(): + s.append("\tclass %s %s;" % (obj_class, perms.to_space_str())) + for role in self.roles: + s.append("\trole %s;" % role) + for bool in self.data: + s.append("\tbool %s;" % bool) + for user in self.users: + s.append("\tuser %s;" % user) + s.append("}") + + # Handle empty requires + if len(s) == 2: + return "" + + return "\n".join(s) + + +class ObjPermSet: + def __init__(self, name): + self.name = name + self.perms = set() + + def to_string(self): + return "define(`%s', `%s')" % (self.name, self.perms.to_space_str()) + +class ClassMap: + def __init__(self, obj_class, perms): + self.obj_class = obj_class + self.perms = perms + + def to_string(self): + return self.obj_class + ": " + self.perms + +class Comment: + def __init__(self, l=None): + if l: + self.lines = l + else: + self.lines = [] + + def to_string(self): + # If there are no lines, treat this as a spacer between + # policy statements and return a new line. + if len(self.lines) == 0: + return "" + else: + out = [] + for line in self.lines: + out.append("#" + line) + return "\n".join(out) + + def merge(self, other): + if len(other.lines): + for line in other.lines: + if line != "": + self.lines.append(line) + + def __str__(self): + return self.to_string() + + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/sepolgeni18n.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/sepolgeni18n.py new file mode 100644 index 0000000..998c435 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/sepolgeni18n.py @@ -0,0 +1,26 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +try: + import gettext + t = gettext.translation( 'yumex' ) + _ = t.gettext +except: + def _(str): + return str diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/util.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/util.py new file mode 100644 index 0000000..74a11f5 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/util.py @@ -0,0 +1,87 @@ +# Authors: Karl MacMillan +# +# Copyright (C) 2006 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You 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 +# + +class ConsoleProgressBar: + def __init__(self, out, steps=100, indicator='#'): + self.blocks = 0 + self.current = 0 + self.steps = steps + self.indicator = indicator + self.out = out + self.done = False + + def start(self, message=None): + self.done = False + if message: + self.out.write('\n%s:\n' % message) + self.out.write('%--10---20---30---40---50---60---70---80---90--100\n') + + def step(self, n=1): + self.current += n + + old = self.blocks + self.blocks = int(round(self.current / float(self.steps) * 100) / 2) + + if self.blocks > 50: + self.blocks = 50 + + new = self.blocks - old + + self.out.write(self.indicator * new) + self.out.flush() + + if self.blocks == 50 and not self.done: + self.done = True + self.out.write("\n") + +def set_to_list(s): + l = [] + l.extend(s) + return l + +def first(s, sorted=False): + """ + Return the first element of a set. + + It sometimes useful to return the first element from a set but, + because sets are not indexable, this is rather hard. This function + will return the first element from a set. If sorted is True, then + the set will first be sorted (making this an expensive operation). + Otherwise a random element will be returned (as sets are not ordered). + """ + if not len(s): + raise IndexError("empty containter") + + if sorted: + l = set_to_list(s) + l.sort() + return l[0] + else: + for x in s: + return x + +if __name__ == "__main__": + import sys + import time + p = ConsoleProgressBar(sys.stdout, steps=999) + p.start("computing pi") + for i in range(999): + p.step() + time.sleep(0.001) + diff --git a/lib/python2.7/site-packages/setoolsgui/sepolgen/yacc.py b/lib/python2.7/site-packages/setoolsgui/sepolgen/yacc.py new file mode 100644 index 0000000..bc4536d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sepolgen/yacc.py @@ -0,0 +1,2209 @@ +#----------------------------------------------------------------------------- +# ply: yacc.py +# +# Author(s): David M. Beazley (dave@dabeaz.com) +# +# Copyright (C) 2001-2006, David M. Beazley +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See the file COPYING for a complete copy of the LGPL. +# +# +# This implements an LR parser that is constructed from grammar rules defined +# as Python functions. The grammer is specified by supplying the BNF inside +# Python documentation strings. The inspiration for this technique was borrowed +# from John Aycock's Spark parsing system. PLY might be viewed as cross between +# Spark and the GNU bison utility. +# +# The current implementation is only somewhat object-oriented. The +# LR parser itself is defined in terms of an object (which allows multiple +# parsers to co-exist). However, most of the variables used during table +# construction are defined in terms of global variables. Users shouldn't +# notice unless they are trying to define multiple parsers at the same +# time using threads (in which case they should have their head examined). +# +# This implementation supports both SLR and LALR(1) parsing. LALR(1) +# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), +# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, +# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced +# by the more efficient DeRemer and Pennello algorithm. +# +# :::::::: WARNING ::::::: +# +# Construction of LR parsing tables is fairly complicated and expensive. +# To make this module run fast, a *LOT* of work has been put into +# optimization---often at the expensive of readability and what might +# consider to be good Python "coding style." Modify the code at your +# own risk! +# ---------------------------------------------------------------------------- + +__version__ = "2.2" + +#----------------------------------------------------------------------------- +# === User configurable parameters === +# +# Change these to modify the default behavior of yacc (if you wish) +#----------------------------------------------------------------------------- + +yaccdebug = 1 # Debugging mode. If set, yacc generates a + # a 'parser.out' file in the current directory + +debug_file = 'parser.out' # Default name of the debugging file +tab_module = 'parsetab' # Default name of the table module +default_lr = 'LALR' # Default LR table generation method + +error_count = 3 # Number of symbols that must be shifted to leave recovery mode + +import re, types, sys, cStringIO, hashlib, os.path + +# Exception raised for yacc-related errors +class YaccError(Exception): pass + +#----------------------------------------------------------------------------- +# === LR Parsing Engine === +# +# The following classes are used for the LR parser itself. These are not +# used during table construction and are independent of the actual LR +# table generation algorithm +#----------------------------------------------------------------------------- + +# This class is used to hold non-terminal grammar symbols during parsing. +# It normally has the following attributes set: +# .type = Grammar symbol type +# .value = Symbol value +# .lineno = Starting line number +# .endlineno = Ending line number (optional, set automatically) +# .lexpos = Starting lex position +# .endlexpos = Ending lex position (optional, set automatically) + +class YaccSymbol: + def __str__(self): return self.type + def __repr__(self): return str(self) + +# This class is a wrapper around the objects actually passed to each +# grammar rule. Index lookup and assignment actually assign the +# .value attribute of the underlying YaccSymbol object. +# The lineno() method returns the line number of a given +# item (or 0 if not defined). The linespan() method returns +# a tuple of (startline,endline) representing the range of lines +# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) +# representing the range of positional information for a symbol. + +class YaccProduction: + def __init__(self,s,stack=None): + self.slice = s + self.pbstack = [] + self.stack = stack + + def __getitem__(self,n): + if type(n) == types.IntType: + if n >= 0: return self.slice[n].value + else: return self.stack[n].value + else: + return [s.value for s in self.slice[n.start:n.stop:n.step]] + + def __setitem__(self,n,v): + self.slice[n].value = v + + def __len__(self): + return len(self.slice) + + def lineno(self,n): + return getattr(self.slice[n],"lineno",0) + + def linespan(self,n): + startline = getattr(self.slice[n],"lineno",0) + endline = getattr(self.slice[n],"endlineno",startline) + return startline,endline + + def lexpos(self,n): + return getattr(self.slice[n],"lexpos",0) + + def lexspan(self,n): + startpos = getattr(self.slice[n],"lexpos",0) + endpos = getattr(self.slice[n],"endlexpos",startpos) + return startpos,endpos + + def pushback(self,n): + if n <= 0: + raise ValueError, "Expected a positive value" + if n > (len(self.slice)-1): + raise ValueError, "Can't push %d tokens. Only %d are available." % (n,len(self.slice)-1) + for i in range(0,n): + self.pbstack.append(self.slice[-i-1]) + +# The LR Parsing engine. This is defined as a class so that multiple parsers +# can exist in the same process. A user never instantiates this directly. +# Instead, the global yacc() function should be used to create a suitable Parser +# object. + +class Parser: + def __init__(self,magic=None): + + # This is a hack to keep users from trying to instantiate a Parser + # object directly. + + if magic != "xyzzy": + raise YaccError, "Can't instantiate Parser. Use yacc() instead." + + # Reset internal state + self.productions = None # List of productions + self.errorfunc = None # Error handling function + self.action = { } # LR Action table + self.goto = { } # LR goto table + self.require = { } # Attribute require table + self.method = "Unknown LR" # Table construction method used + + def errok(self): + self.errorcount = 0 + + def restart(self): + del self.statestack[:] + del self.symstack[:] + sym = YaccSymbol() + sym.type = '$end' + self.symstack.append(sym) + self.statestack.append(0) + + def parse(self,input=None,lexer=None,debug=0): + lookahead = None # Current lookahead symbol + lookaheadstack = [ ] # Stack of lookahead symbols + actions = self.action # Local reference to action table + goto = self.goto # Local reference to goto table + prod = self.productions # Local reference to production list + pslice = YaccProduction(None) # Production object passed to grammar rules + pslice.parser = self # Parser object + self.errorcount = 0 # Used during error recovery + + # If no lexer was given, we will try to use the lex module + if not lexer: + import lex + lexer = lex.lexer + + pslice.lexer = lexer + + # If input was supplied, pass to lexer + if input: + lexer.input(input) + + # Tokenize function + get_token = lexer.token + + statestack = [ ] # Stack of parsing states + self.statestack = statestack + symstack = [ ] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + + while 1: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + if debug > 1: + print 'state', statestack[-1] + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + if debug: + errorlead = ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip() + + # Check the action table + s = statestack[-1] + ltype = lookahead.type + t = actions.get((s,ltype),None) + + if debug > 1: + print 'action', t + if t is not None: + if t > 0: + # shift a symbol on the stack + if ltype == '$end': + # Error, end of input + sys.stderr.write("yacc: Parse error. EOF\n") + return + statestack.append(t) + if debug > 1: + sys.stderr.write("%-60s shift state %s\n" % (errorlead, t)) + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if self.errorcount > 0: + self.errorcount -= 1 + + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + if debug > 1: + sys.stderr.write("%-60s reduce %d\n" % (errorlead, -t)) + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + try: + sym.lineno = targ[1].lineno + sym.endlineno = getattr(targ[-1],"endlineno",targ[-1].lineno) + sym.lexpos = targ[1].lexpos + sym.endlexpos = getattr(targ[-1],"endlexpos",targ[-1].lexpos) + except AttributeError: + sym.lineno = 0 + del symstack[-plen:] + del statestack[-plen:] + else: + sym.lineno = 0 + targ = [ sym ] + pslice.slice = targ + pslice.pbstack = [] + # Call the grammar rule with our special slice object + p.func(pslice) + + # If there was a pushback, put that on the stack + if pslice.pbstack: + lookaheadstack.append(lookahead) + for _t in pslice.pbstack: + lookaheadstack.append(_t) + lookahead = None + + symstack.append(sym) + statestack.append(goto[statestack[-1],pname]) + continue + + if t == 0: + n = symstack[-1] + return getattr(n,"value",None) + sys.stderr.write(errorlead, "\n") + + if t == None: + if debug: + sys.stderr.write(errorlead + "\n") + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if not self.errorcount: + self.errorcount = error_count + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + global errok,token,restart + errok = self.errok # Set some special functions available in error recovery + token = get_token + restart = self.restart + tok = self.errorfunc(errtoken) + del errok, token, restart # Delete special functions + + if not self.errorcount: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken,"lineno"): lineno = lookahead.lineno + else: lineno = 0 + if lineno: + sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) + else: + sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) + else: + sys.stderr.write("yacc: Parse error in input. EOF\n") + return + + else: + self.errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + t = YaccSymbol() + t.type = 'error' + if hasattr(lookahead,"lineno"): + t.lineno = lookahead.lineno + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + symstack.pop() + statestack.pop() + + continue + + # Call an error function here + raise RuntimeError, "yacc: internal parser error!!!\n" + +# ----------------------------------------------------------------------------- +# === Parser Construction === +# +# The following functions and variables are used to implement the yacc() function +# itself. This is pretty hairy stuff involving lots of error checking, +# construction of LR items, kernels, and so forth. Although a lot of +# this work is done using global variables, the resulting Parser object +# is completely self contained--meaning that it is safe to repeatedly +# call yacc() with different grammars in the same application. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# validate_file() +# +# This function checks to see if there are duplicated p_rulename() functions +# in the parser module file. Without this function, it is really easy for +# users to make mistakes by cutting and pasting code fragments (and it's a real +# bugger to try and figure out why the resulting parser doesn't work). Therefore, +# we just do a little regular expression pattern matching of def statements +# to try and detect duplicates. +# ----------------------------------------------------------------------------- + +def validate_file(filename): + base,ext = os.path.splitext(filename) + if ext != '.py': return 1 # No idea. Assume it's okay. + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + return 1 # Oh well + + # Match def p_funcname( + fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') + counthash = { } + linen = 1 + noerror = 1 + for l in lines: + m = fre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + sys.stderr.write("%s:%d: Function %s redefined. Previously defined on line %d\n" % (filename,linen,name,prev)) + noerror = 0 + linen += 1 + return noerror + +# This function looks for functions that might be grammar rules, but which don't have the proper p_suffix. +def validate_dict(d): + for n,v in d.items(): + if n[0:2] == 'p_' and type(v) in (types.FunctionType, types.MethodType): continue + if n[0:2] == 't_': continue + + if n[0:2] == 'p_': + sys.stderr.write("yacc: Warning. '%s' not defined as a function\n" % n) + if 1 and isinstance(v,types.FunctionType) and v.func_code.co_argcount == 1: + try: + doc = v.__doc__.split(" ") + if doc[1] == ':': + sys.stderr.write("%s:%d: Warning. Possible grammar rule '%s' defined without p_ prefix.\n" % (v.func_code.co_filename, v.func_code.co_firstlineno,n)) + except StandardError: + pass + +# ----------------------------------------------------------------------------- +# === GRAMMAR FUNCTIONS === +# +# The following global variables and functions are used to store, manipulate, +# and verify the grammar rules specified by the user. +# ----------------------------------------------------------------------------- + +# Initialize all of the global variables used during grammar construction +def initialize_vars(): + global Productions, Prodnames, Prodmap, Terminals + global Nonterminals, First, Follow, Precedence, LRitems + global Errorfunc, Signature, Requires + + Productions = [None] # A list of all of the productions. The first + # entry is always reserved for the purpose of + # building an augmented grammar + + Prodnames = { } # A dictionary mapping the names of nonterminals to a list of all + # productions of that nonterminal. + + Prodmap = { } # A dictionary that is only used to detect duplicate + # productions. + + Terminals = { } # A dictionary mapping the names of terminal symbols to a + # list of the rules where they are used. + + Nonterminals = { } # A dictionary mapping names of nonterminals to a list + # of rule numbers where they are used. + + First = { } # A dictionary of precomputed FIRST(x) symbols + + Follow = { } # A dictionary of precomputed FOLLOW(x) symbols + + Precedence = { } # Precedence rules for each terminal. Contains tuples of the + # form ('right',level) or ('nonassoc', level) or ('left',level) + + LRitems = [ ] # A list of all LR items for the grammar. These are the + # productions with the "dot" like E -> E . PLUS E + + Errorfunc = None # User defined error handler + + Signature = hashlib.sha256() # Digital signature of the grammar rules, precedence + # and other information. Used to determined when a + # parsing table needs to be regenerated. + + Requires = { } # Requires list + + # File objects used when creating the parser.out debugging file + global _vf, _vfc + _vf = cStringIO.StringIO() + _vfc = cStringIO.StringIO() + +# ----------------------------------------------------------------------------- +# class Production: +# +# This class stores the raw information about a single production or grammar rule. +# It has a few required attributes: +# +# name - Name of the production (nonterminal) +# prod - A list of symbols making up its production +# number - Production number. +# +# In addition, a few additional attributes are used to help with debugging or +# optimization of table generation. +# +# file - File where production action is defined. +# lineno - Line number where action is defined +# func - Action function +# prec - Precedence level +# lr_next - Next LR item. Example, if we are ' E -> E . PLUS E' +# then lr_next refers to 'E -> E PLUS . E' +# lr_index - LR item index (location of the ".") in the prod list. +# lookaheads - LALR lookahead symbols for this item +# len - Length of the production (number of symbols on right hand side) +# ----------------------------------------------------------------------------- + +class Production: + def __init__(self,**kw): + for k,v in kw.items(): + setattr(self,k,v) + self.lr_index = -1 + self.lr0_added = 0 # Flag indicating whether or not added to LR0 closure + self.lr1_added = 0 # Flag indicating whether or not added to LR1 + self.usyms = [ ] + self.lookaheads = { } + self.lk_added = { } + self.setnumbers = [ ] + + def __str__(self): + if self.prod: + s = "%s -> %s" % (self.name," ".join(self.prod)) + else: + s = "%s -> " % self.name + return s + + def __repr__(self): + return str(self) + + # Compute lr_items from the production + def lr_item(self,n): + if n > len(self.prod): return None + p = Production() + p.name = self.name + p.prod = list(self.prod) + p.number = self.number + p.lr_index = n + p.lookaheads = { } + p.setnumbers = self.setnumbers + p.prod.insert(n,".") + p.prod = tuple(p.prod) + p.len = len(p.prod) + p.usyms = self.usyms + + # Precompute list of productions immediately following + try: + p.lrafter = Prodnames[p.prod[n+1]] + except (IndexError,KeyError),e: + p.lrafter = [] + try: + p.lrbefore = p.prod[n-1] + except IndexError: + p.lrbefore = None + + return p + +class MiniProduction: + pass + +# regex matching identifiers +_is_identifier = re.compile(r'^[a-zA-Z0-9_-~]+$') + +# ----------------------------------------------------------------------------- +# add_production() +# +# Given an action function, this function assembles a production rule. +# The production rule is assumed to be found in the function's docstring. +# This rule has the general syntax: +# +# name1 ::= production1 +# | production2 +# | production3 +# ... +# | productionn +# name2 ::= production1 +# | production2 +# ... +# ----------------------------------------------------------------------------- + +def add_production(f,file,line,prodname,syms): + + if Terminals.has_key(prodname): + sys.stderr.write("%s:%d: Illegal rule name '%s'. Already defined as a token.\n" % (file,line,prodname)) + return -1 + if prodname == 'error': + sys.stderr.write("%s:%d: Illegal rule name '%s'. error is a reserved word.\n" % (file,line,prodname)) + return -1 + + if not _is_identifier.match(prodname): + sys.stderr.write("%s:%d: Illegal rule name '%s'\n" % (file,line,prodname)) + return -1 + + for x in range(len(syms)): + s = syms[x] + if s[0] in "'\"": + try: + c = eval(s) + if (len(c) > 1): + sys.stderr.write("%s:%d: Literal token %s in rule '%s' may only be a single character\n" % (file,line,s, prodname)) + return -1 + if not Terminals.has_key(c): + Terminals[c] = [] + syms[x] = c + continue + except SyntaxError: + pass + if not _is_identifier.match(s) and s != '%prec': + sys.stderr.write("%s:%d: Illegal name '%s' in rule '%s'\n" % (file,line,s, prodname)) + return -1 + + # See if the rule is already in the rulemap + map = "%s -> %s" % (prodname,syms) + if Prodmap.has_key(map): + m = Prodmap[map] + sys.stderr.write("%s:%d: Duplicate rule %s.\n" % (file,line, m)) + sys.stderr.write("%s:%d: Previous definition at %s:%d\n" % (file,line, m.file, m.line)) + return -1 + + p = Production() + p.name = prodname + p.prod = syms + p.file = file + p.line = line + p.func = f + p.number = len(Productions) + + + Productions.append(p) + Prodmap[map] = p + if not Nonterminals.has_key(prodname): + Nonterminals[prodname] = [ ] + + # Add all terminals to Terminals + i = 0 + while i < len(p.prod): + t = p.prod[i] + if t == '%prec': + try: + precname = p.prod[i+1] + except IndexError: + sys.stderr.write("%s:%d: Syntax error. Nothing follows %%prec.\n" % (p.file,p.line)) + return -1 + + prec = Precedence.get(precname,None) + if not prec: + sys.stderr.write("%s:%d: Nothing known about the precedence of '%s'\n" % (p.file,p.line,precname)) + return -1 + else: + p.prec = prec + del p.prod[i] + del p.prod[i] + continue + + if Terminals.has_key(t): + Terminals[t].append(p.number) + # Is a terminal. We'll assign a precedence to p based on this + if not hasattr(p,"prec"): + p.prec = Precedence.get(t,('right',0)) + else: + if not Nonterminals.has_key(t): + Nonterminals[t] = [ ] + Nonterminals[t].append(p.number) + i += 1 + + if not hasattr(p,"prec"): + p.prec = ('right',0) + + # Set final length of productions + p.len = len(p.prod) + p.prod = tuple(p.prod) + + # Calculate unique syms in the production + p.usyms = [ ] + for s in p.prod: + if s not in p.usyms: + p.usyms.append(s) + + # Add to the global productions list + try: + Prodnames[p.name].append(p) + except KeyError: + Prodnames[p.name] = [ p ] + return 0 + +# Given a raw rule function, this function rips out its doc string +# and adds rules to the grammar + +def add_function(f): + line = f.func_code.co_firstlineno + file = f.func_code.co_filename + error = 0 + + if isinstance(f,types.MethodType): + reqdargs = 2 + else: + reqdargs = 1 + + if f.func_code.co_argcount > reqdargs: + sys.stderr.write("%s:%d: Rule '%s' has too many arguments.\n" % (file,line,f.__name__)) + return -1 + + if f.func_code.co_argcount < reqdargs: + sys.stderr.write("%s:%d: Rule '%s' requires an argument.\n" % (file,line,f.__name__)) + return -1 + + if f.__doc__: + # Split the doc string into lines + pstrings = f.__doc__.splitlines() + lastp = None + dline = line + for ps in pstrings: + dline += 1 + p = ps.split() + if not p: continue + try: + if p[0] == '|': + # This is a continuation of a previous rule + if not lastp: + sys.stderr.write("%s:%d: Misplaced '|'.\n" % (file,dline)) + return -1 + prodname = lastp + if len(p) > 1: + syms = p[1:] + else: + syms = [ ] + else: + prodname = p[0] + lastp = prodname + assign = p[1] + if len(p) > 2: + syms = p[2:] + else: + syms = [ ] + if assign != ':' and assign != '::=': + sys.stderr.write("%s:%d: Syntax error. Expected ':'\n" % (file,dline)) + return -1 + + + e = add_production(f,file,dline,prodname,syms) + error += e + + + except StandardError: + sys.stderr.write("%s:%d: Syntax error in rule '%s'\n" % (file,dline,ps)) + error -= 1 + else: + sys.stderr.write("%s:%d: No documentation string specified in function '%s'\n" % (file,line,f.__name__)) + return error + + +# Cycle checking code (Michael Dyck) + +def compute_reachable(): + ''' + Find each symbol that can be reached from the start symbol. + Print a warning for any nonterminals that can't be reached. + (Unused terminals have already had their warning.) + ''' + Reachable = { } + for s in Terminals.keys() + Nonterminals.keys(): + Reachable[s] = 0 + + mark_reachable_from( Productions[0].prod[0], Reachable ) + + for s in Nonterminals.keys(): + if not Reachable[s]: + sys.stderr.write("yacc: Symbol '%s' is unreachable.\n" % s) + +def mark_reachable_from(s, Reachable): + ''' + Mark all symbols that are reachable from symbol s. + ''' + if Reachable[s]: + # We've already reached symbol s. + return + Reachable[s] = 1 + for p in Prodnames.get(s,[]): + for r in p.prod: + mark_reachable_from(r, Reachable) + +# ----------------------------------------------------------------------------- +# compute_terminates() +# +# This function looks at the various parsing rules and tries to detect +# infinite recursion cycles (grammar rules where there is no possible way +# to derive a string of only terminals). +# ----------------------------------------------------------------------------- +def compute_terminates(): + ''' + Raise an error for any symbols that don't terminate. + ''' + Terminates = {} + + # Terminals: + for t in Terminals.keys(): + Terminates[t] = 1 + + Terminates['$end'] = 1 + + # Nonterminals: + + # Initialize to false: + for n in Nonterminals.keys(): + Terminates[n] = 0 + + # Then propagate termination until no change: + while 1: + some_change = 0 + for (n,pl) in Prodnames.items(): + # Nonterminal n terminates iff any of its productions terminates. + for p in pl: + # Production p terminates iff all of its rhs symbols terminate. + for s in p.prod: + if not Terminates[s]: + # The symbol s does not terminate, + # so production p does not terminate. + p_terminates = 0 + break + else: + # didn't break from the loop, + # so every symbol s terminates + # so production p terminates. + p_terminates = 1 + + if p_terminates: + # symbol n terminates! + if not Terminates[n]: + Terminates[n] = 1 + some_change = 1 + # Don't need to consider any more productions for this n. + break + + if not some_change: + break + + some_error = 0 + for (s,terminates) in Terminates.items(): + if not terminates: + if not Prodnames.has_key(s) and not Terminals.has_key(s) and s != 'error': + # s is used-but-not-defined, and we've already warned of that, + # so it would be overkill to say that it's also non-terminating. + pass + else: + sys.stderr.write("yacc: Infinite recursion detected for symbol '%s'.\n" % s) + some_error = 1 + + return some_error + +# ----------------------------------------------------------------------------- +# verify_productions() +# +# This function examines all of the supplied rules to see if they seem valid. +# ----------------------------------------------------------------------------- +def verify_productions(cycle_check=1): + error = 0 + for p in Productions: + if not p: continue + + for s in p.prod: + if not Prodnames.has_key(s) and not Terminals.has_key(s) and s != 'error': + sys.stderr.write("%s:%d: Symbol '%s' used, but not defined as a token or a rule.\n" % (p.file,p.line,s)) + error = 1 + continue + + unused_tok = 0 + # Now verify all of the tokens + if yaccdebug: + _vf.write("Unused terminals:\n\n") + for s,v in Terminals.items(): + if s != 'error' and not v: + sys.stderr.write("yacc: Warning. Token '%s' defined, but not used.\n" % s) + if yaccdebug: _vf.write(" %s\n"% s) + unused_tok += 1 + + # Print out all of the productions + if yaccdebug: + _vf.write("\nGrammar\n\n") + for i in range(1,len(Productions)): + _vf.write("Rule %-5d %s\n" % (i, Productions[i])) + + unused_prod = 0 + # Verify the use of all productions + for s,v in Nonterminals.items(): + if not v: + p = Prodnames[s][0] + sys.stderr.write("%s:%d: Warning. Rule '%s' defined, but not used.\n" % (p.file,p.line, s)) + unused_prod += 1 + + + if unused_tok == 1: + sys.stderr.write("yacc: Warning. There is 1 unused token.\n") + if unused_tok > 1: + sys.stderr.write("yacc: Warning. There are %d unused tokens.\n" % unused_tok) + + if unused_prod == 1: + sys.stderr.write("yacc: Warning. There is 1 unused rule.\n") + if unused_prod > 1: + sys.stderr.write("yacc: Warning. There are %d unused rules.\n" % unused_prod) + + if yaccdebug: + _vf.write("\nTerminals, with rules where they appear\n\n") + ks = Terminals.keys() + ks.sort() + for k in ks: + _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Terminals[k]]))) + _vf.write("\nNonterminals, with rules where they appear\n\n") + ks = Nonterminals.keys() + ks.sort() + for k in ks: + _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Nonterminals[k]]))) + + if (cycle_check): + compute_reachable() + error += compute_terminates() +# error += check_cycles() + return error + +# ----------------------------------------------------------------------------- +# build_lritems() +# +# This function walks the list of productions and builds a complete set of the +# LR items. The LR items are stored in two ways: First, they are uniquely +# numbered and placed in the list _lritems. Second, a linked list of LR items +# is built for each production. For example: +# +# E -> E PLUS E +# +# Creates the list +# +# [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] +# ----------------------------------------------------------------------------- + +def build_lritems(): + for p in Productions: + lastlri = p + lri = p.lr_item(0) + i = 0 + while 1: + lri = p.lr_item(i) + lastlri.lr_next = lri + if not lri: break + lri.lr_num = len(LRitems) + LRitems.append(lri) + lastlri = lri + i += 1 + + # In order for the rest of the parser generator to work, we need to + # guarantee that no more lritems are generated. Therefore, we nuke + # the p.lr_item method. (Only used in debugging) + # Production.lr_item = None + +# ----------------------------------------------------------------------------- +# add_precedence() +# +# Given a list of precedence rules, add to the precedence table. +# ----------------------------------------------------------------------------- + +def add_precedence(plist): + plevel = 0 + error = 0 + for p in plist: + plevel += 1 + try: + prec = p[0] + terms = p[1:] + if prec != 'left' and prec != 'right' and prec != 'nonassoc': + sys.stderr.write("yacc: Invalid precedence '%s'\n" % prec) + return -1 + for t in terms: + if Precedence.has_key(t): + sys.stderr.write("yacc: Precedence already specified for terminal '%s'\n" % t) + error += 1 + continue + Precedence[t] = (prec,plevel) + except: + sys.stderr.write("yacc: Invalid precedence table.\n") + error += 1 + + return error + +# ----------------------------------------------------------------------------- +# augment_grammar() +# +# Compute the augmented grammar. This is just a rule S' -> start where start +# is the starting symbol. +# ----------------------------------------------------------------------------- + +def augment_grammar(start=None): + if not start: + start = Productions[1].name + Productions[0] = Production(name="S'",prod=[start],number=0,len=1,prec=('right',0),func=None) + Productions[0].usyms = [ start ] + Nonterminals[start].append(0) + + +# ------------------------------------------------------------------------- +# first() +# +# Compute the value of FIRST1(beta) where beta is a tuple of symbols. +# +# During execution of compute_first1, the result may be incomplete. +# Afterward (e.g., when called from compute_follow()), it will be complete. +# ------------------------------------------------------------------------- +def first(beta): + + # We are computing First(x1,x2,x3,...,xn) + result = [ ] + for x in beta: + x_produces_empty = 0 + + # Add all the non- symbols of First[x] to the result. + for f in First[x]: + if f == '': + x_produces_empty = 1 + else: + if f not in result: result.append(f) + + if x_produces_empty: + # We have to consider the next x in beta, + # i.e. stay in the loop. + pass + else: + # We don't have to consider any further symbols in beta. + break + else: + # There was no 'break' from the loop, + # so x_produces_empty was true for all x in beta, + # so beta produces empty as well. + result.append('') + + return result + + +# FOLLOW(x) +# Given a non-terminal. This function computes the set of all symbols +# that might follow it. Dragon book, p. 189. + +def compute_follow(start=None): + # Add '$end' to the follow list of the start symbol + for k in Nonterminals.keys(): + Follow[k] = [ ] + + if not start: + start = Productions[1].name + + Follow[start] = [ '$end' ] + + while 1: + didadd = 0 + for p in Productions[1:]: + # Here is the production set + for i in range(len(p.prod)): + B = p.prod[i] + if Nonterminals.has_key(B): + # Okay. We got a non-terminal in a production + fst = first(p.prod[i+1:]) + hasempty = 0 + for f in fst: + if f != '' and f not in Follow[B]: + Follow[B].append(f) + didadd = 1 + if f == '': + hasempty = 1 + if hasempty or i == (len(p.prod)-1): + # Add elements of follow(a) to follow(b) + for f in Follow[p.name]: + if f not in Follow[B]: + Follow[B].append(f) + didadd = 1 + if not didadd: break + + if 0 and yaccdebug: + _vf.write('\nFollow:\n') + for k in Nonterminals.keys(): + _vf.write("%-20s : %s\n" % (k, " ".join([str(s) for s in Follow[k]]))) + +# ------------------------------------------------------------------------- +# compute_first1() +# +# Compute the value of FIRST1(X) for all symbols +# ------------------------------------------------------------------------- +def compute_first1(): + + # Terminals: + for t in Terminals.keys(): + First[t] = [t] + + First['$end'] = ['$end'] + First['#'] = ['#'] # what's this for? + + # Nonterminals: + + # Initialize to the empty set: + for n in Nonterminals.keys(): + First[n] = [] + + # Then propagate symbols until no change: + while 1: + some_change = 0 + for n in Nonterminals.keys(): + for p in Prodnames[n]: + for f in first(p.prod): + if f not in First[n]: + First[n].append( f ) + some_change = 1 + if not some_change: + break + + if 0 and yaccdebug: + _vf.write('\nFirst:\n') + for k in Nonterminals.keys(): + _vf.write("%-20s : %s\n" % + (k, " ".join([str(s) for s in First[k]]))) + +# ----------------------------------------------------------------------------- +# === SLR Generation === +# +# The following functions are used to construct SLR (Simple LR) parsing tables +# as described on p.221-229 of the dragon book. +# ----------------------------------------------------------------------------- + +# Global variables for the LR parsing engine +def lr_init_vars(): + global _lr_action, _lr_goto, _lr_method + global _lr_goto_cache, _lr0_cidhash + + _lr_action = { } # Action table + _lr_goto = { } # Goto table + _lr_method = "Unknown" # LR method used + _lr_goto_cache = { } + _lr0_cidhash = { } + + +# Compute the LR(0) closure operation on I, where I is a set of LR(0) items. +# prodlist is a list of productions. + +_add_count = 0 # Counter used to detect cycles + +def lr0_closure(I): + global _add_count + + _add_count += 1 + prodlist = Productions + + # Add everything in I to J + J = I[:] + didadd = 1 + while didadd: + didadd = 0 + for j in J: + for x in j.lrafter: + if x.lr0_added == _add_count: continue + # Add B --> .G to J + J.append(x.lr_next) + x.lr0_added = _add_count + didadd = 1 + + return J + +# Compute the LR(0) goto function goto(I,X) where I is a set +# of LR(0) items and X is a grammar symbol. This function is written +# in a way that guarantees uniqueness of the generated goto sets +# (i.e. the same goto set will never be returned as two different Python +# objects). With uniqueness, we can later do fast set comparisons using +# id(obj) instead of element-wise comparison. + +def lr0_goto(I,x): + # First we look for a previously cached entry + g = _lr_goto_cache.get((id(I),x),None) + if g: return g + + # Now we generate the goto set in a way that guarantees uniqueness + # of the result + + s = _lr_goto_cache.get(x,None) + if not s: + s = { } + _lr_goto_cache[x] = s + + gs = [ ] + for p in I: + n = p.lr_next + if n and n.lrbefore == x: + s1 = s.get(id(n),None) + if not s1: + s1 = { } + s[id(n)] = s1 + gs.append(n) + s = s1 + g = s.get('$end',None) + if not g: + if gs: + g = lr0_closure(gs) + s['$end'] = g + else: + s['$end'] = gs + _lr_goto_cache[(id(I),x)] = g + return g + +_lr0_cidhash = { } + +# Compute the LR(0) sets of item function +def lr0_items(): + + C = [ lr0_closure([Productions[0].lr_next]) ] + i = 0 + for I in C: + _lr0_cidhash[id(I)] = i + i += 1 + + # Loop over the items in C and each grammar symbols + i = 0 + while i < len(C): + I = C[i] + i += 1 + + # Collect all of the symbols that could possibly be in the goto(I,X) sets + asyms = { } + for ii in I: + for s in ii.usyms: + asyms[s] = None + + for x in asyms.keys(): + g = lr0_goto(I,x) + if not g: continue + if _lr0_cidhash.has_key(id(g)): continue + _lr0_cidhash[id(g)] = len(C) + C.append(g) + + return C + +# ----------------------------------------------------------------------------- +# ==== LALR(1) Parsing ==== +# +# LALR(1) parsing is almost exactly the same as SLR except that instead of +# relying upon Follow() sets when performing reductions, a more selective +# lookahead set that incorporates the state of the LR(0) machine is utilized. +# Thus, we mainly just have to focus on calculating the lookahead sets. +# +# The method used here is due to DeRemer and Pennelo (1982). +# +# DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) +# Lookahead Sets", ACM Transactions on Programming Languages and Systems, +# Vol. 4, No. 4, Oct. 1982, pp. 615-649 +# +# Further details can also be found in: +# +# J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", +# McGraw-Hill Book Company, (1985). +# +# Note: This implementation is a complete replacement of the LALR(1) +# implementation in PLY-1.x releases. That version was based on +# a less efficient algorithm and it had bugs in its implementation. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# compute_nullable_nonterminals() +# +# Creates a dictionary containing all of the non-terminals that might produce +# an empty production. +# ----------------------------------------------------------------------------- + +def compute_nullable_nonterminals(): + nullable = {} + num_nullable = 0 + while 1: + for p in Productions[1:]: + if p.len == 0: + nullable[p.name] = 1 + continue + for t in p.prod: + if not nullable.has_key(t): break + else: + nullable[p.name] = 1 + if len(nullable) == num_nullable: break + num_nullable = len(nullable) + return nullable + +# ----------------------------------------------------------------------------- +# find_nonterminal_trans(C) +# +# Given a set of LR(0) items, this functions finds all of the non-terminal +# transitions. These are transitions in which a dot appears immediately before +# a non-terminal. Returns a list of tuples of the form (state,N) where state +# is the state number and N is the nonterminal symbol. +# +# The input C is the set of LR(0) items. +# ----------------------------------------------------------------------------- + +def find_nonterminal_transitions(C): + trans = [] + for state in range(len(C)): + for p in C[state]: + if p.lr_index < p.len - 1: + t = (state,p.prod[p.lr_index+1]) + if Nonterminals.has_key(t[1]): + if t not in trans: trans.append(t) + state = state + 1 + return trans + +# ----------------------------------------------------------------------------- +# dr_relation() +# +# Computes the DR(p,A) relationships for non-terminal transitions. The input +# is a tuple (state,N) where state is a number and N is a nonterminal symbol. +# +# Returns a list of terminals. +# ----------------------------------------------------------------------------- + +def dr_relation(C,trans,nullable): + dr_set = { } + state,N = trans + terms = [] + + g = lr0_goto(C[state],N) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index+1] + if Terminals.has_key(a): + if a not in terms: terms.append(a) + + # This extra bit is to handle the start state + if state == 0 and N == Productions[0].prod[0]: + terms.append('$end') + + return terms + +# ----------------------------------------------------------------------------- +# reads_relation() +# +# Computes the READS() relation (p,A) READS (t,C). +# ----------------------------------------------------------------------------- + +def reads_relation(C, trans, empty): + # Look for empty transitions + rel = [] + state, N = trans + + g = lr0_goto(C[state],N) + j = _lr0_cidhash.get(id(g),-1) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index + 1] + if empty.has_key(a): + rel.append((j,a)) + + return rel + +# ----------------------------------------------------------------------------- +# compute_lookback_includes() +# +# Determines the lookback and includes relations +# +# LOOKBACK: +# +# This relation is determined by running the LR(0) state machine forward. +# For example, starting with a production "N : . A B C", we run it forward +# to obtain "N : A B C ." We then build a relationship between this final +# state and the starting state. These relationships are stored in a dictionary +# lookdict. +# +# INCLUDES: +# +# Computes the INCLUDE() relation (p,A) INCLUDES (p',B). +# +# This relation is used to determine non-terminal transitions that occur +# inside of other non-terminal transition states. (p,A) INCLUDES (p', B) +# if the following holds: +# +# B -> LAT, where T -> epsilon and p' -L-> p +# +# L is essentially a prefix (which may be empty), T is a suffix that must be +# able to derive an empty string. State p' must lead to state p with the string L. +# +# ----------------------------------------------------------------------------- + +def compute_lookback_includes(C,trans,nullable): + + lookdict = {} # Dictionary of lookback relations + includedict = {} # Dictionary of include relations + + # Make a dictionary of non-terminal transitions + dtrans = {} + for t in trans: + dtrans[t] = 1 + + # Loop over all transitions and compute lookbacks and includes + for state,N in trans: + lookb = [] + includes = [] + for p in C[state]: + if p.name != N: continue + + # Okay, we have a name match. We now follow the production all the way + # through the state machine until we get the . on the right hand side + + lr_index = p.lr_index + j = state + while lr_index < p.len - 1: + lr_index = lr_index + 1 + t = p.prod[lr_index] + + # Check to see if this symbol and state are a non-terminal transition + if dtrans.has_key((j,t)): + # Yes. Okay, there is some chance that this is an includes relation + # the only way to know for certain is whether the rest of the + # production derives empty + + li = lr_index + 1 + while li < p.len: + if Terminals.has_key(p.prod[li]): break # No forget it + if not nullable.has_key(p.prod[li]): break + li = li + 1 + else: + # Appears to be a relation between (j,t) and (state,N) + includes.append((j,t)) + + g = lr0_goto(C[j],t) # Go to next set + j = _lr0_cidhash.get(id(g),-1) # Go to next state + + # When we get here, j is the final state, now we have to locate the production + for r in C[j]: + if r.name != p.name: continue + if r.len != p.len: continue + i = 0 + # This look is comparing a production ". A B C" with "A B C ." + while i < r.lr_index: + if r.prod[i] != p.prod[i+1]: break + i = i + 1 + else: + lookb.append((j,r)) + for i in includes: + if not includedict.has_key(i): includedict[i] = [] + includedict[i].append((state,N)) + lookdict[(state,N)] = lookb + + return lookdict,includedict + +# ----------------------------------------------------------------------------- +# digraph() +# traverse() +# +# The following two functions are used to compute set valued functions +# of the form: +# +# F(x) = F'(x) U U{F(y) | x R y} +# +# This is used to compute the values of Read() sets as well as FOLLOW sets +# in LALR(1) generation. +# +# Inputs: X - An input set +# R - A relation +# FP - Set-valued function +# ------------------------------------------------------------------------------ + +def digraph(X,R,FP): + N = { } + for x in X: + N[x] = 0 + stack = [] + F = { } + for x in X: + if N[x] == 0: traverse(x,N,stack,F,X,R,FP) + return F + +def traverse(x,N,stack,F,X,R,FP): + stack.append(x) + d = len(stack) + N[x] = d + F[x] = FP(x) # F(X) <- F'(x) + + rel = R(x) # Get y's related to x + for y in rel: + if N[y] == 0: + traverse(y,N,stack,F,X,R,FP) + N[x] = min(N[x],N[y]) + for a in F.get(y,[]): + if a not in F[x]: F[x].append(a) + if N[x] == d: + N[stack[-1]] = sys.maxint + F[stack[-1]] = F[x] + element = stack.pop() + while element != x: + N[stack[-1]] = sys.maxint + F[stack[-1]] = F[x] + element = stack.pop() + +# ----------------------------------------------------------------------------- +# compute_read_sets() +# +# Given a set of LR(0) items, this function computes the read sets. +# +# Inputs: C = Set of LR(0) items +# ntrans = Set of nonterminal transitions +# nullable = Set of empty transitions +# +# Returns a set containing the read sets +# ----------------------------------------------------------------------------- + +def compute_read_sets(C, ntrans, nullable): + FP = lambda x: dr_relation(C,x,nullable) + R = lambda x: reads_relation(C,x,nullable) + F = digraph(ntrans,R,FP) + return F + +# ----------------------------------------------------------------------------- +# compute_follow_sets() +# +# Given a set of LR(0) items, a set of non-terminal transitions, a readset, +# and an include set, this function computes the follow sets +# +# Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} +# +# Inputs: +# ntrans = Set of nonterminal transitions +# readsets = Readset (previously computed) +# inclsets = Include sets (previously computed) +# +# Returns a set containing the follow sets +# ----------------------------------------------------------------------------- + +def compute_follow_sets(ntrans,readsets,inclsets): + FP = lambda x: readsets[x] + R = lambda x: inclsets.get(x,[]) + F = digraph(ntrans,R,FP) + return F + +# ----------------------------------------------------------------------------- +# add_lookaheads() +# +# Attaches the lookahead symbols to grammar rules. +# +# Inputs: lookbacks - Set of lookback relations +# followset - Computed follow set +# +# This function directly attaches the lookaheads to productions contained +# in the lookbacks set +# ----------------------------------------------------------------------------- + +def add_lookaheads(lookbacks,followset): + for trans,lb in lookbacks.items(): + # Loop over productions in lookback + for state,p in lb: + if not p.lookaheads.has_key(state): + p.lookaheads[state] = [] + f = followset.get(trans,[]) + for a in f: + if a not in p.lookaheads[state]: p.lookaheads[state].append(a) + +# ----------------------------------------------------------------------------- +# add_lalr_lookaheads() +# +# This function does all of the work of adding lookahead information for use +# with LALR parsing +# ----------------------------------------------------------------------------- + +def add_lalr_lookaheads(C): + # Determine all of the nullable nonterminals + nullable = compute_nullable_nonterminals() + + # Find all non-terminal transitions + trans = find_nonterminal_transitions(C) + + # Compute read sets + readsets = compute_read_sets(C,trans,nullable) + + # Compute lookback/includes relations + lookd, included = compute_lookback_includes(C,trans,nullable) + + # Compute LALR FOLLOW sets + followsets = compute_follow_sets(trans,readsets,included) + + # Add all of the lookaheads + add_lookaheads(lookd,followsets) + +# ----------------------------------------------------------------------------- +# lr_parse_table() +# +# This function constructs the parse tables for SLR or LALR +# ----------------------------------------------------------------------------- +def lr_parse_table(method): + global _lr_method + goto = _lr_goto # Goto array + action = _lr_action # Action array + actionp = { } # Action production array (temporary) + + _lr_method = method + + n_srconflict = 0 + n_rrconflict = 0 + + if yaccdebug: + sys.stderr.write("yacc: Generating %s parsing table...\n" % method) + _vf.write("\n\nParsing method: %s\n\n" % method) + + # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items + # This determines the number of states + + C = lr0_items() + + if method == 'LALR': + add_lalr_lookaheads(C) + + # Build the parser table, state by state + st = 0 + for I in C: + # Loop over each production in I + actlist = [ ] # List of actions + + if yaccdebug: + _vf.write("\nstate %d\n\n" % st) + for p in I: + _vf.write(" (%d) %s\n" % (p.number, str(p))) + _vf.write("\n") + + for p in I: + try: + if p.prod[-1] == ".": + if p.name == "S'": + # Start symbol. Accept! + action[st,"$end"] = 0 + actionp[st,"$end"] = p + else: + # We are at the end of a production. Reduce! + if method == 'LALR': + laheads = p.lookaheads[st] + else: + laheads = Follow[p.name] + for a in laheads: + actlist.append((a,p,"reduce using rule %d (%s)" % (p.number,p))) + r = action.get((st,a),None) + if r is not None: + # Whoa. Have a shift/reduce or reduce/reduce conflict + if r > 0: + # Need to decide on shift or reduce here + # By default we favor shifting. Need to add + # some precedence rules here. + sprec,slevel = Productions[actionp[st,a].number].prec + rprec,rlevel = Precedence.get(a,('right',0)) + if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): + # We really need to reduce here. + action[st,a] = -p.number + actionp[st,a] = p + if not slevel and not rlevel: + _vfc.write("shift/reduce conflict in state %d resolved as reduce.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as reduce.\n" % a) + n_srconflict += 1 + elif (slevel == rlevel) and (rprec == 'nonassoc'): + action[st,a] = None + else: + # Hmmm. Guess we'll keep the shift + if not rlevel: + _vfc.write("shift/reduce conflict in state %d resolved as shift.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as shift.\n" % a) + n_srconflict +=1 + elif r < 0: + # Reduce/reduce conflict. In this case, we favor the rule + # that was defined first in the grammar file + oldp = Productions[-r] + pp = Productions[p.number] + if oldp.line > pp.line: + action[st,a] = -p.number + actionp[st,a] = p + # sys.stderr.write("Reduce/reduce conflict in state %d\n" % st) + n_rrconflict += 1 + _vfc.write("reduce/reduce conflict in state %d resolved using rule %d (%s).\n" % (st, actionp[st,a].number, actionp[st,a])) + _vf.write(" ! reduce/reduce conflict for %s resolved using rule %d (%s).\n" % (a,actionp[st,a].number, actionp[st,a])) + else: + sys.stderr.write("Unknown conflict in state %d\n" % st) + else: + action[st,a] = -p.number + actionp[st,a] = p + else: + i = p.lr_index + a = p.prod[i+1] # Get symbol right after the "." + if Terminals.has_key(a): + g = lr0_goto(I,a) + j = _lr0_cidhash.get(id(g),-1) + if j >= 0: + # We are in a shift state + actlist.append((a,p,"shift and go to state %d" % j)) + r = action.get((st,a),None) + if r is not None: + # Whoa have a shift/reduce or shift/shift conflict + if r > 0: + if r != j: + sys.stderr.write("Shift/shift conflict in state %d\n" % st) + elif r < 0: + # Do a precedence check. + # - if precedence of reduce rule is higher, we reduce. + # - if precedence of reduce is same and left assoc, we reduce. + # - otherwise we shift + rprec,rlevel = Productions[actionp[st,a].number].prec + sprec,slevel = Precedence.get(a,('right',0)) + if (slevel > rlevel) or ((slevel == rlevel) and (rprec != 'left')): + # We decide to shift here... highest precedence to shift + action[st,a] = j + actionp[st,a] = p + if not rlevel: + n_srconflict += 1 + _vfc.write("shift/reduce conflict in state %d resolved as shift.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as shift.\n" % a) + elif (slevel == rlevel) and (rprec == 'nonassoc'): + action[st,a] = None + else: + # Hmmm. Guess we'll keep the reduce + if not slevel and not rlevel: + n_srconflict +=1 + _vfc.write("shift/reduce conflict in state %d resolved as reduce.\n" % st) + _vf.write(" ! shift/reduce conflict for %s resolved as reduce.\n" % a) + + else: + sys.stderr.write("Unknown conflict in state %d\n" % st) + else: + action[st,a] = j + actionp[st,a] = p + + except StandardError,e: + raise YaccError, "Hosed in lr_parse_table", e + + # Print the actions associated with each terminal + if yaccdebug: + _actprint = { } + for a,p,m in actlist: + if action.has_key((st,a)): + if p is actionp[st,a]: + _vf.write(" %-15s %s\n" % (a,m)) + _actprint[(a,m)] = 1 + _vf.write("\n") + for a,p,m in actlist: + if action.has_key((st,a)): + if p is not actionp[st,a]: + if not _actprint.has_key((a,m)): + _vf.write(" ! %-15s [ %s ]\n" % (a,m)) + _actprint[(a,m)] = 1 + + # Construct the goto table for this state + if yaccdebug: + _vf.write("\n") + nkeys = { } + for ii in I: + for s in ii.usyms: + if Nonterminals.has_key(s): + nkeys[s] = None + for n in nkeys.keys(): + g = lr0_goto(I,n) + j = _lr0_cidhash.get(id(g),-1) + if j >= 0: + goto[st,n] = j + if yaccdebug: + _vf.write(" %-30s shift and go to state %d\n" % (n,j)) + + st += 1 + + if yaccdebug: + if n_srconflict == 1: + sys.stderr.write("yacc: %d shift/reduce conflict\n" % n_srconflict) + if n_srconflict > 1: + sys.stderr.write("yacc: %d shift/reduce conflicts\n" % n_srconflict) + if n_rrconflict == 1: + sys.stderr.write("yacc: %d reduce/reduce conflict\n" % n_rrconflict) + if n_rrconflict > 1: + sys.stderr.write("yacc: %d reduce/reduce conflicts\n" % n_rrconflict) + +# ----------------------------------------------------------------------------- +# ==== LR Utility functions ==== +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# _lr_write_tables() +# +# This function writes the LR parsing tables to a file +# ----------------------------------------------------------------------------- + +def lr_write_tables(modulename=tab_module,outputdir=''): + filename = os.path.join(outputdir,modulename) + ".py" + try: + f = open(filename,"w") + + f.write(""" +# %s +# This file is automatically generated. Do not edit. + +_lr_method = %s + +_lr_signature = %s +""" % (filename, repr(_lr_method), repr(Signature.digest()))) + + # Change smaller to 0 to go back to original tables + smaller = 1 + + # Factor out names to try and make smaller + if smaller: + items = { } + + for k,v in _lr_action.items(): + i = items.get(k[1]) + if not i: + i = ([],[]) + items[k[1]] = i + i[0].append(k[0]) + i[1].append(v) + + f.write("\n_lr_action_items = {") + for k,v in items.items(): + f.write("%r:([" % k) + for i in v[0]: + f.write("%r," % i) + f.write("],[") + for i in v[1]: + f.write("%r," % i) + + f.write("]),") + f.write("}\n") + + f.write(""" +_lr_action = { } +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + _lr_action[(_x,_k)] = _y +del _lr_action_items +""") + + else: + f.write("\n_lr_action = { "); + for k,v in _lr_action.items(): + f.write("(%r,%r):%r," % (k[0],k[1],v)) + f.write("}\n"); + + if smaller: + # Factor out names to try and make smaller + items = { } + + for k,v in _lr_goto.items(): + i = items.get(k[1]) + if not i: + i = ([],[]) + items[k[1]] = i + i[0].append(k[0]) + i[1].append(v) + + f.write("\n_lr_goto_items = {") + for k,v in items.items(): + f.write("%r:([" % k) + for i in v[0]: + f.write("%r," % i) + f.write("],[") + for i in v[1]: + f.write("%r," % i) + + f.write("]),") + f.write("}\n") + + f.write(""" +_lr_goto = { } +for _k, _v in _lr_goto_items.items(): + for _x,_y in zip(_v[0],_v[1]): + _lr_goto[(_x,_k)] = _y +del _lr_goto_items +""") + else: + f.write("\n_lr_goto = { "); + for k,v in _lr_goto.items(): + f.write("(%r,%r):%r," % (k[0],k[1],v)) + f.write("}\n"); + + # Write production table + f.write("_lr_productions = [\n") + for p in Productions: + if p: + if (p.func): + f.write(" (%r,%d,%r,%r,%d),\n" % (p.name, p.len, p.func.__name__,p.file,p.line)) + else: + f.write(" (%r,%d,None,None,None),\n" % (p.name, p.len)) + else: + f.write(" None,\n") + f.write("]\n") + + f.close() + + except IOError,e: + print "Unable to create '%s'" % filename + print e + return + +def lr_read_tables(module=tab_module,optimize=0): + global _lr_action, _lr_goto, _lr_productions, _lr_method + try: + exec "import %s as parsetab" % module + + if (optimize) or (Signature.digest() == parsetab._lr_signature): + _lr_action = parsetab._lr_action + _lr_goto = parsetab._lr_goto + _lr_productions = parsetab._lr_productions + _lr_method = parsetab._lr_method + return 1 + else: + return 0 + + except (ImportError,AttributeError): + return 0 + + +# Available instance types. This is used when parsers are defined by a class. +# it's a little funky because I want to preserve backwards compatibility +# with Python 2.0 where types.ObjectType is undefined. + +try: + _INSTANCETYPE = (types.InstanceType, types.ObjectType) +except AttributeError: + _INSTANCETYPE = types.InstanceType + +# ----------------------------------------------------------------------------- +# yacc(module) +# +# Build the parser module +# ----------------------------------------------------------------------------- + +def yacc(method=default_lr, debug=yaccdebug, module=None, tabmodule=tab_module, start=None, check_recursion=1, optimize=0,write_tables=1,debugfile=debug_file,outputdir=''): + global yaccdebug + yaccdebug = debug + + initialize_vars() + files = { } + error = 0 + + + # Add parsing method to signature + Signature.update(method) + + # If a "module" parameter was supplied, extract its dictionary. + # Note: a module may in fact be an instance as well. + + if module: + # User supplied a module object. + if isinstance(module, types.ModuleType): + ldict = module.__dict__ + elif isinstance(module, _INSTANCETYPE): + _items = [(k,getattr(module,k)) for k in dir(module)] + ldict = { } + for i in _items: + ldict[i[0]] = i[1] + else: + raise ValueError,"Expected a module" + + else: + # No module given. We might be able to get information from the caller. + # Throw an exception and unwind the traceback to get the globals + + try: + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + f = f.f_back # Walk out to our calling function + ldict = f.f_globals # Grab its globals dictionary + + # Add starting symbol to signature + if not start: + start = ldict.get("start",None) + if start: + Signature.update(start) + + # If running in optimized mode. We're going to + + if (optimize and lr_read_tables(tabmodule,1)): + # Read parse table + del Productions[:] + for p in _lr_productions: + if not p: + Productions.append(None) + else: + m = MiniProduction() + m.name = p[0] + m.len = p[1] + m.file = p[3] + m.line = p[4] + if p[2]: + m.func = ldict[p[2]] + Productions.append(m) + + else: + # Get the tokens map + if (module and isinstance(module,_INSTANCETYPE)): + tokens = getattr(module,"tokens",None) + else: + tokens = ldict.get("tokens",None) + + if not tokens: + raise YaccError,"module does not define a list 'tokens'" + if not (isinstance(tokens,types.ListType) or isinstance(tokens,types.TupleType)): + raise YaccError,"tokens must be a list or tuple." + + # Check to see if a requires dictionary is defined. + requires = ldict.get("require",None) + if requires: + if not (isinstance(requires,types.DictType)): + raise YaccError,"require must be a dictionary." + + for r,v in requires.items(): + try: + if not (isinstance(v,types.ListType)): + raise TypeError + v1 = [x.split(".") for x in v] + Requires[r] = v1 + except StandardError: + print "Invalid specification for rule '%s' in require. Expected a list of strings" % r + + + # Build the dictionary of terminals. We a record a 0 in the + # dictionary to track whether or not a terminal is actually + # used in the grammar + + if 'error' in tokens: + print "yacc: Illegal token 'error'. Is a reserved word." + raise YaccError,"Illegal token name" + + for n in tokens: + if Terminals.has_key(n): + print "yacc: Warning. Token '%s' multiply defined." % n + Terminals[n] = [ ] + + Terminals['error'] = [ ] + + # Get the precedence map (if any) + prec = ldict.get("precedence",None) + if prec: + if not (isinstance(prec,types.ListType) or isinstance(prec,types.TupleType)): + raise YaccError,"precedence must be a list or tuple." + add_precedence(prec) + Signature.update(repr(prec)) + + for n in tokens: + if not Precedence.has_key(n): + Precedence[n] = ('right',0) # Default, right associative, 0 precedence + + # Look for error handler + ef = ldict.get('p_error',None) + if ef: + if isinstance(ef,types.FunctionType): + ismethod = 0 + elif isinstance(ef, types.MethodType): + ismethod = 1 + else: + raise YaccError,"'p_error' defined, but is not a function or method." + eline = ef.func_code.co_firstlineno + efile = ef.func_code.co_filename + files[efile] = None + + if (ef.func_code.co_argcount != 1+ismethod): + raise YaccError,"%s:%d: p_error() requires 1 argument." % (efile,eline) + global Errorfunc + Errorfunc = ef + else: + print "yacc: Warning. no p_error() function is defined." + + # Get the list of built-in functions with p_ prefix + symbols = [ldict[f] for f in ldict.keys() + if (type(ldict[f]) in (types.FunctionType, types.MethodType) and ldict[f].__name__[:2] == 'p_' + and ldict[f].__name__ != 'p_error')] + + # Check for non-empty symbols + if len(symbols) == 0: + raise YaccError,"no rules of the form p_rulename are defined." + + # Sort the symbols by line number + symbols.sort(lambda x,y: cmp(x.func_code.co_firstlineno,y.func_code.co_firstlineno)) + + # Add all of the symbols to the grammar + for f in symbols: + if (add_function(f)) < 0: + error += 1 + else: + files[f.func_code.co_filename] = None + + # Make a signature of the docstrings + for f in symbols: + if f.__doc__: + Signature.update(f.__doc__) + + lr_init_vars() + + if error: + raise YaccError,"Unable to construct parser." + + if not lr_read_tables(tabmodule): + + # Validate files + for filename in files.keys(): + if not validate_file(filename): + error = 1 + + # Validate dictionary + validate_dict(ldict) + + if start and not Prodnames.has_key(start): + raise YaccError,"Bad starting symbol '%s'" % start + + augment_grammar(start) + error = verify_productions(cycle_check=check_recursion) + otherfunc = [ldict[f] for f in ldict.keys() + if (type(f) in (types.FunctionType,types.MethodType) and ldict[f].__name__[:2] != 'p_')] + + if error: + raise YaccError,"Unable to construct parser." + + build_lritems() + compute_first1() + compute_follow(start) + + if method in ['SLR','LALR']: + lr_parse_table(method) + else: + raise YaccError, "Unknown parsing method '%s'" % method + + if write_tables: + lr_write_tables(tabmodule,outputdir) + + if yaccdebug: + try: + f = open(os.path.join(outputdir,debugfile),"w") + f.write(_vfc.getvalue()) + f.write("\n\n") + f.write(_vf.getvalue()) + f.close() + except IOError,e: + print "yacc: can't create '%s'" % debugfile,e + + # Made it here. Create a parser object and set up its internal state. + # Set global parse() method to bound method of parser object. + + p = Parser("xyzzy") + p.productions = Productions + p.errorfunc = Errorfunc + p.action = _lr_action + p.goto = _lr_goto + p.method = _lr_method + p.require = Requires + + global parse + parse = p.parse + + global parser + parser = p + + # Clean up all of the globals we created + if (not optimize): + yacc_cleanup() + return p + +# yacc_cleanup function. Delete all of the global variables +# used during table construction + +def yacc_cleanup(): + global _lr_action, _lr_goto, _lr_method, _lr_goto_cache + del _lr_action, _lr_goto, _lr_method, _lr_goto_cache + + global Productions, Prodnames, Prodmap, Terminals + global Nonterminals, First, Follow, Precedence, LRitems + global Errorfunc, Signature, Requires + + del Productions, Prodnames, Prodmap, Terminals + del Nonterminals, First, Follow, Precedence, LRitems + del Errorfunc, Signature, Requires + + global _vf, _vfc + del _vf, _vfc + + +# Stub that raises an error if parsing is attempted without first calling yacc() +def parse(*args,**kwargs): + raise YaccError, "yacc: No parser built with yacc()" + diff --git a/lib/python2.7/site-packages/setoolsgui/sesearch b/lib/python2.7/site-packages/setoolsgui/sesearch new file mode 100755 index 0000000..e861db6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/sesearch @@ -0,0 +1,206 @@ +#!/usr/bin/python +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# SETools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SETools. If not, see . +# + +from __future__ import print_function +import setools +import argparse +import sys +import logging + +parser = argparse.ArgumentParser( + description="SELinux policy rule search tool.", + epilog="TE/MLS rule searches cannot be mixed with RBAC rule searches.") +parser.add_argument("--version", action="version", version=setools.__version__) +parser.add_argument("policy", help="Path to the SELinux policy to search.", nargs="?") +parser.add_argument("-v", "--verbose", action="store_true", + help="Print extra informational messages") +parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.") + +rtypes = parser.add_argument_group("TE Rule Types") +rtypes.add_argument("-A", "--allow", action="append_const", + const="allow", dest="tertypes", + help="Search allow rules.") +rtypes.add_argument("--auditallow", action="append_const", + const="auditallow", dest="tertypes", + help="Search auditallow rules.") +rtypes.add_argument("--dontaudit", action="append_const", + const="dontaudit", dest="tertypes", + help="Search dontaudit rules.") +rtypes.add_argument("-T", "--type_trans", action="append_const", + const="type_transition", dest="tertypes", + help="Search type_transition rules.") +rtypes.add_argument("--type_change", action="append_const", + const="type_change", dest="tertypes", + help="Search type_change rules.") +rtypes.add_argument("--type_member", action="append_const", + const="type_member", dest="tertypes", + help="Search type_member rules.") + +rbacrtypes = parser.add_argument_group("RBAC Rule Types") +rbacrtypes.add_argument("--role_allow", action="append_const", + const="allow", dest="rbacrtypes", + help="Search role allow rules.") +rbacrtypes.add_argument("--role_trans", action="append_const", + const="role_transition", dest="rbacrtypes", + help="Search role_transition rules.") + +mlsrtypes = parser.add_argument_group("MLS Rule Types") +mlsrtypes.add_argument("--range_trans", action="append_const", + const="range_transition", dest="mlsrtypes", + help="Search range_transition rules.") + +expr = parser.add_argument_group("Expressions") +expr.add_argument("-s", "--source", + help="Source type/role of the TE/RBAC rule.") +expr.add_argument("-t", "--target", + help="Target type/role of the TE/RBAC rule.") +expr.add_argument("-c", "--class", dest="tclass", + help="Comma separated list of object classes") +expr.add_argument("-p", "--perms", metavar="PERMS", + help="Comma separated list of permissions.") +expr.add_argument("-D", "--default", + help="Default of the rule. (type/role/range transition rules)") +expr.add_argument("-b", "--bool", dest="boolean", metavar="BOOL", + help="Comma separated list of Booleans in the conditional expression.") + +opts = parser.add_argument_group("Search options") +opts.add_argument("-eb", action="store_true", dest="boolean_equal", + help="Match Boolean list exactly instead of matching any listed Boolean.") +opts.add_argument("-ep", action="store_true", dest="perms_equal", + help="Match permission set exactly instead of matching any listed permission.") +opts.add_argument("-ds", action="store_false", dest="source_indirect", + help="Match source attributes directly instead of matching member types/roles.") +opts.add_argument("-dt", action="store_false", dest="target_indirect", + help="Match target attributes directly instead of matching member types/roles.") +opts.add_argument("-rs", action="store_true", dest="source_regex", + help="Use regular expression matching for the source type/role.") +opts.add_argument("-rt", action="store_true", dest="target_regex", + help="Use regular expression matching for the target type/role.") +opts.add_argument("-rc", action="store_true", dest="tclass_regex", + help="Use regular expression matching for the object class.") +opts.add_argument("-rd", action="store_true", dest="default_regex", + help="Use regular expression matching for the default type/role.") +opts.add_argument("-rb", action="store_true", dest="boolean_regex", + help="Use regular expression matching for Booleans.") + +args = parser.parse_args() + +if not args.tertypes and not args.mlsrtypes and not args.rbacrtypes: + parser.error("At least one rule type must be specified.") + +if args.debug: + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') +elif args.verbose: + logging.basicConfig(level=logging.INFO, format='%(message)s') +else: + logging.basicConfig(level=logging.WARNING, format='%(message)s') + +try: + p = setools.SELinuxPolicy(args.policy) + + if args.tertypes: + q = setools.TERuleQuery(p, + ruletype=args.tertypes, + source=args.source, + source_indirect=args.source_indirect, + source_regex=args.source_regex, + target=args.target, + target_indirect=args.target_indirect, + target_regex=args.target_regex, + tclass_regex=args.tclass_regex, + perms_equal=args.perms_equal, + default=args.default, + default_regex=args.default_regex, + boolean_regex=args.boolean_regex, + boolean_equal=args.boolean_equal) + + # these are broken out from the above statement to prevent making a list + # with an empty string in it (split on empty string) + if args.tclass: + if args.tclass_regex: + q.tclass = args.tclass + else: + q.tclass = args.tclass.split(",") + + if args.perms: + q.perms = args.perms.split(",") + + if args.boolean: + if args.boolean_regex: + q.boolean = args.boolean + else: + q.boolean = args.boolean.split(",") + + for r in sorted(q.results()): + print(r) + + if args.rbacrtypes: + q = setools.RBACRuleQuery(p, + ruletype=args.rbacrtypes, + source=args.source, + source_indirect=args.source_indirect, + source_regex=args.source_regex, + target=args.target, + target_indirect=args.target_indirect, + target_regex=args.target_regex, + default=args.default, + default_regex=args.default_regex, + tclass_regex=args.tclass_regex) + + # these are broken out from the above statement to prevent making a list + # with an empty string in it (split on empty string) + if args.tclass: + if args.tclass_regex: + q.tclass = args.tclass + else: + q.tclass = args.tclass.split(",") + + for r in sorted(q.results()): + print(r) + + if args.mlsrtypes: + q = setools.MLSRuleQuery(p, + ruletype=args.mlsrtypes, + source=args.source, + source_regex=args.source_regex, + target=args.target, + target_regex=args.target_regex, + tclass_regex=args.tclass_regex, + default=args.default) + + # these are broken out from the above statement to prevent making a list + # with an empty string in it (split on empty string) + if args.tclass: + if args.tclass_regex: + q.tclass = args.tclass + else: + q.tclass = args.tclass.split(",") + + for r in sorted(q.results()): + print(r) + +except Exception as err: + if args.debug: + import traceback + traceback.print_exc() + else: + print(err) + + sys.exit(-1) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/__init__.py b/lib/python2.7/site-packages/setoolsgui/setools/__init__.py new file mode 100644 index 0000000..4d03553 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/__init__.py @@ -0,0 +1,68 @@ +"""The SETools SELinux policy analysis library.""" +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +#try: +# import pkg_resources +# # pylint: disable=no-member +# __version__ = pkg_resources.get_distribution("setools").version +#except ImportError: # pragma: no cover +# __version__ = "unknown" +__version__ = "3.3.8" + +# Python classes for policy representation +from . import policyrep +from .policyrep import SELinuxPolicy + +# Exceptions +from . import exception + +# Component Queries +from .boolquery import BoolQuery +from .categoryquery import CategoryQuery +from .commonquery import CommonQuery +from .objclassquery import ObjClassQuery +from .polcapquery import PolCapQuery +from .rolequery import RoleQuery +from .sensitivityquery import SensitivityQuery +from .typequery import TypeQuery +from .typeattrquery import TypeAttributeQuery +from .userquery import UserQuery + +# Rule Queries +from .mlsrulequery import MLSRuleQuery +from .rbacrulequery import RBACRuleQuery +from .terulequery import TERuleQuery + +# Constraint queries +from .constraintquery import ConstraintQuery + +# In-policy Context Queries +from .fsusequery import FSUseQuery +from .genfsconquery import GenfsconQuery +from .initsidquery import InitialSIDQuery +from .netifconquery import NetifconQuery +from .nodeconquery import NodeconQuery +from .portconquery import PortconQuery + +# Information Flow Analysis +from .infoflow import InfoFlowAnalysis +from .permmap import PermissionMap + +# Domain Transition Analysis +from .dta import DomainTransitionAnalysis diff --git a/lib/python2.7/site-packages/setoolsgui/setools/boolquery.py b/lib/python2.7/site-packages/setoolsgui/setools/boolquery.py new file mode 100644 index 0000000..b70b7d5 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/boolquery.py @@ -0,0 +1,66 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from .descriptors import CriteriaDescriptor + + +class BoolQuery(compquery.ComponentQuery): + + """Query SELinux policy Booleans. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The Boolean name to match. + name_regex If true, regular expression matching + will be used on the Boolean name. + default The default state to match. If this + is None, the default state not be matched. + """ + + _default = None + + @property + def default(self): + return self._default + + @default.setter + def default(self, value): + if value is None: + self._default = None + else: + self._default = bool(value) + + def results(self): + """Generator which yields all Booleans matching the criteria.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Default: {0.default}".format(self)) + + for boolean in self.policy.bools(): + if not self._match_name(boolean): + continue + + if self.default is not None and boolean.state != self.default: + continue + + yield boolean diff --git a/lib/python2.7/site-packages/setoolsgui/setools/categoryquery.py b/lib/python2.7/site-packages/setoolsgui/setools/categoryquery.py new file mode 100644 index 0000000..d4d7c4c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/categoryquery.py @@ -0,0 +1,55 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import mixins + + +class CategoryQuery(mixins.MatchAlias, compquery.ComponentQuery): + + """ + Query MLS Categories + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the category to match. + name_regex If true, regular expression matching will + be used for matching the name. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + """ + + def results(self): + """Generator which yields all matching categories.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + + for cat in self.policy.categories(): + if not self._match_name(cat): + continue + + if not self._match_alias(cat): + continue + + yield cat diff --git a/lib/python2.7/site-packages/setoolsgui/setools/commonquery.py b/lib/python2.7/site-packages/setoolsgui/setools/commonquery.py new file mode 100644 index 0000000..e105ccb --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/commonquery.py @@ -0,0 +1,60 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery, mixins + + +class CommonQuery(mixins.MatchPermission, compquery.ComponentQuery): + + """ + Query common permission sets. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the common to match. + name_regex If true, regular expression matching will + be used for matching the name. + perms The permissions to match. + perms_equal If true, only commons with permission sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + """ + + def results(self): + """Generator which yields all matching commons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + + for com in self.policy.commons(): + if not self._match_name(com): + continue + + if not self._match_perms(com): + continue + + yield com diff --git a/lib/python2.7/site-packages/setoolsgui/setools/compquery.py b/lib/python2.7/site-packages/setoolsgui/setools/compquery.py new file mode 100644 index 0000000..3d8851a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/compquery.py @@ -0,0 +1,39 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=no-member,attribute-defined-outside-init,abstract-method +import re + +from . import query +from .descriptors import CriteriaDescriptor + + +class ComponentQuery(query.PolicyQuery): + + """Base class for SETools component queries.""" + + name = CriteriaDescriptor("name_regex") + name_regex = False + + def _match_name(self, obj): + """Match the object to the name criteria.""" + if not self.name: + # if there is no criteria, everything matches. + return True + + return self._match_regex(obj, self.name, self.name_regex) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/constraintquery.py b/lib/python2.7/site-packages/setoolsgui/setools/constraintquery.py new file mode 100644 index 0000000..82a6fc2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/constraintquery.py @@ -0,0 +1,142 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor +from .policyrep.exception import ConstraintUseError + + +class ConstraintQuery(mixins.MatchObjClass, mixins.MatchPermission, query.PolicyQuery): + + """ + Query constraint rules, (mls)constrain/(mls)validatetrans. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + perms The permission(s) to match. + perms_equal If true, the permission set of the rule + must exactly match the permissions + criteria. If false, any set intersection + will match. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + role The name of the role to match in the + constraint expression. + role_indirect If true, members of an attribute will be + matched rather than the attribute itself. + role_regex If true, regular expression matching will + be used on the role. + type_ The name of the type/attribute to match in the + constraint expression. + type_indirect If true, members of an attribute will be + matched rather than the attribute itself. + type_regex If true, regular expression matching will + be used on the type/attribute. + user The name of the user to match in the + constraint expression. + user_regex If true, regular expression matching will + be used on the user. + """ + + ruletype = RuletypeDescriptor("validate_constraint_ruletype") + user = CriteriaDescriptor("user_regex", "lookup_user") + user_regex = False + role = CriteriaDescriptor("role_regex", "lookup_role") + role_regex = False + role_indirect = True + type_ = CriteriaDescriptor("type_regex", "lookup_type_or_attr") + type_regex = False + type_indirect = True + + def _match_expr(self, expr, criteria, indirect, regex): + """ + Match roles/types/users in a constraint expression, + optionally by expanding the contents of attributes. + + Parameters: + expr The expression to match. + criteria The criteria to match. + indirect If attributes in the expression should be expanded. + regex If regular expression matching should be used. + """ + + if indirect: + obj = set() + for item in expr: + obj.update(item.expand()) + else: + obj = expr + + return self._match_in_set(obj, criteria, regex) + + def results(self): + """Generator which yields all matching constraints rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + + for c in self.policy.constraints(): + if self.ruletype: + if c.ruletype not in self.ruletype: + continue + + if not self._match_object_class(c): + continue + + try: + if not self._match_perms(c): + continue + except ConstraintUseError: + continue + + if self.role and not self._match_expr( + c.roles, + self.role, + self.role_indirect, + self.role_regex): + continue + + if self.type_ and not self._match_expr( + c.types, + self.type_, + self.type_indirect, + self.type_regex): + continue + + if self.user and not self._match_expr( + c.users, + self.user, + False, + self.user_regex): + continue + + yield c diff --git a/lib/python2.7/site-packages/setoolsgui/setools/contextquery.py b/lib/python2.7/site-packages/setoolsgui/setools/contextquery.py new file mode 100644 index 0000000..5ce1632 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/contextquery.py @@ -0,0 +1,98 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=attribute-defined-outside-init,no-member +import re + +from . import query +from .descriptors import CriteriaDescriptor + + +class ContextQuery(query.PolicyQuery): + + """ + Base class for SETools in-policy labeling/context queries. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + context The object to match. + user The user to match in the context. + user_regex If true, regular expression matching + will be used on the user. + role The role to match in the context. + role_regex If true, regular expression matching + will be used on the role. + type_ The type to match in the context. + type_regex If true, regular expression matching + will be used on the type. + range_ The range to match in the context. + range_subset If true, the criteria will match if it + is a subset of the context's range. + range_overlap If true, the criteria will match if it + overlaps any of the context's range. + range_superset If true, the criteria will match if it + is a superset of the context's range. + range_proper If true, use proper superset/subset + on range matching operations. + No effect if not using set operations. + """ + + user = CriteriaDescriptor("user_regex", "lookup_user") + user_regex = False + role = CriteriaDescriptor("role_regex", "lookup_role") + role_regex = False + type_ = CriteriaDescriptor("type_regex", "lookup_type") + type_regex = False + range_ = CriteriaDescriptor(lookup_function="lookup_range") + range_overlap = False + range_subset = False + range_superset = False + range_proper = False + + def _match_context(self, context): + + if self.user and not query.PolicyQuery._match_regex( + context.user, + self.user, + self.user_regex): + return False + + if self.role and not query.PolicyQuery._match_regex( + context.role, + self.role, + self.role_regex): + return False + + if self.type_ and not query.PolicyQuery._match_regex( + context.type_, + self.type_, + self.type_regex): + return False + + if self.range_ and not query.PolicyQuery._match_range( + context.range_, + self.range_, + self.range_subset, + self.range_overlap, + self.range_superset, + self.range_proper): + return False + + return True diff --git a/lib/python2.7/site-packages/setoolsgui/setools/descriptors.py b/lib/python2.7/site-packages/setoolsgui/setools/descriptors.py new file mode 100644 index 0000000..eab9210 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/descriptors.py @@ -0,0 +1,230 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +""" +SETools descriptors. + +These classes override how a class's attributes are get/set/deleted. +This is how the @property decorator works. + +See https://docs.python.org/3/howto/descriptor.html +for more details. +""" + +import re +from collections import defaultdict +from weakref import WeakKeyDictionary + +# +# Query criteria descriptors +# +# Implementation note: if the name_regex attribute value +# is changed the criteria must be reset. +# + + +class CriteriaDescriptor(object): + + """ + Single item criteria descriptor. + + Parameters: + name_regex The name of instance's regex setting attribute; + used as name_regex below. If unset, + regular expressions will never be used. + lookup_function The name of the SELinuxPolicy lookup function, + e.g. lookup_type or lookup_boolean. + default_value The default value of the criteria. The default + is None. + + Read-only instance attribute use (obj parameter): + policy The instance of SELinuxPolicy + name_regex This attribute is read to determine if + the criteria should be looked up or + compiled into a regex. If the attribute + does not exist, False is assumed. + """ + + def __init__(self, name_regex=None, lookup_function=None, default_value=None): + assert name_regex or lookup_function, "A simple attribute should be used if there is " \ + "no regex nor lookup function." + self.regex = name_regex + self.default_value = default_value + self.lookup_function = lookup_function + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return self.instances.setdefault(obj, self.default_value) + + def __set__(self, obj, value): + if not value: + self.instances[obj] = None + elif self.regex and getattr(obj, self.regex, False): + self.instances[obj] = re.compile(value) + elif self.lookup_function: + lookup = getattr(obj.policy, self.lookup_function) + self.instances[obj] = lookup(value) + else: + self.instances[obj] = value + + +class CriteriaSetDescriptor(CriteriaDescriptor): + + """Descriptor for a set of criteria.""" + + def __set__(self, obj, value): + if not value: + self.instances[obj] = None + elif self.regex and getattr(obj, self.regex, False): + self.instances[obj] = re.compile(value) + elif self.lookup_function: + lookup = getattr(obj.policy, self.lookup_function) + self.instances[obj] = set(lookup(v) for v in value) + else: + self.instances[obj] = set(value) + + +class RuletypeDescriptor(object): + + """ + Descriptor for a list of rule types. + + Parameters: + validator The name of the SELinuxPolicy ruletype + validator function, e.g. validate_te_ruletype + default_value The default value of the criteria. The default + is None. + + Read-only instance attribute use (obj parameter): + policy The instance of SELinuxPolicy + """ + + def __init__(self, validator): + self.validator = validator + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return self.instances.setdefault(obj, None) + + def __set__(self, obj, value): + if value: + validate = getattr(obj.policy, self.validator) + validate(value) + self.instances[obj] = value + else: + self.instances[obj] = None + + +# +# NetworkX Graph Descriptors +# +# These descriptors are used to simplify all +# of the dictionary use in the NetworkX graph. +# + + +class NetworkXGraphEdgeDescriptor(object): + + """ + Descriptor base class for NetworkX graph edge attributes. + + Parameter: + name The edge property name + + Instance class attribute use (obj parameter): + G The NetworkX graph + source The edge's source node + target The edge's target node + """ + + def __init__(self, propname): + self.name = propname + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return obj.G[obj.source][obj.target][self.name] + + def __set__(self, obj, value): + raise NotImplementedError + + def __delete__(self, obj): + raise NotImplementedError + + +class EdgeAttrDict(NetworkXGraphEdgeDescriptor): + + """A descriptor for edge attributes that are dictionaries.""" + + def __set__(self, obj, value): + # None is a special value to initialize the attribute + if value is None: + obj.G[obj.source][obj.target][self.name] = defaultdict(list) + else: + raise ValueError("{0} dictionaries should not be assigned directly".format(self.name)) + + def __delete__(self, obj): + obj.G[obj.source][obj.target][self.name].clear() + + +class EdgeAttrIntMax(NetworkXGraphEdgeDescriptor): + + """ + A descriptor for edge attributes that are non-negative integers that always + keep the max assigned value until re-initialized. + """ + + def __set__(self, obj, value): + # None is a special value to initialize + if value is None: + obj.G[obj.source][obj.target][self.name] = 0 + else: + current_value = obj.G[obj.source][obj.target][self.name] + obj.G[obj.source][obj.target][self.name] = max(current_value, value) + + +class EdgeAttrList(NetworkXGraphEdgeDescriptor): + + """A descriptor for edge attributes that are lists.""" + + def __set__(self, obj, value): + # None is a special value to initialize + if value is None: + obj.G[obj.source][obj.target][self.name] = [] + else: + raise ValueError("{0} lists should not be assigned directly".format(self.name)) + + def __delete__(self, obj): + # in Python3 a .clear() function was added for lists + # keep this implementation for Python 2 compat + del obj.G[obj.source][obj.target][self.name][:] diff --git a/lib/python2.7/site-packages/setoolsgui/setools/dta.py b/lib/python2.7/site-packages/setoolsgui/setools/dta.py new file mode 100644 index 0000000..271efc4 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/dta.py @@ -0,0 +1,603 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import itertools +import logging +from collections import defaultdict, namedtuple + +import networkx as nx +from networkx.exception import NetworkXError, NetworkXNoPath + +from .descriptors import EdgeAttrDict, EdgeAttrList + +__all__ = ['DomainTransitionAnalysis'] + +# Return values for the analysis +# are in the following tuple formats: +step_output = namedtuple("step", ["source", + "target", + "transition", + "entrypoints", + "setexec", + "dyntransition", + "setcurrent"]) + +entrypoint_output = namedtuple("entrypoints", ["name", + "entrypoint", + "execute", + "type_transition"]) + + +class DomainTransitionAnalysis(object): + + """Domain transition analysis.""" + + def __init__(self, policy, reverse=False, exclude=None): + """ + Parameter: + policy The policy to analyze. + """ + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + self.exclude = exclude + self.reverse = reverse + self.rebuildgraph = True + self.rebuildsubgraph = True + self.G = nx.DiGraph() + self.subG = None + + @property + def reverse(self): + return self._reverse + + @reverse.setter + def reverse(self, direction): + self._reverse = bool(direction) + self.rebuildsubgraph = True + + @property + def exclude(self): + return self._exclude + + @exclude.setter + def exclude(self, types): + if types: + self._exclude = [self.policy.lookup_type(t) for t in types] + else: + self._exclude = None + + self.rebuildsubgraph = True + + def shortest_path(self, source, target): + """ + Generator which yields one shortest domain transition path + between the source and target types (there may be more). + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating one shortest path from {0} to {1}...".format(s, t)) + + try: + yield self.__generate_steps(nx.shortest_path(self.subG, s, t)) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. excluded + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_paths(self, source, target, maxlen=2): + """ + Generator which yields all domain transition paths between + the source and target up to the specified maximum path + length. + + Parameters: + source The source type. + target The target type. + maxlen Maximum length of paths. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + if maxlen < 1: + raise ValueError("Maximum path length must be positive.") + + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all paths from {0} to {1}, max len {2}...".format(s, t, maxlen)) + + try: + for path in nx.all_simple_paths(self.subG, s, t, maxlen): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. excluded + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_shortest_paths(self, source, target): + """ + Generator which yields all shortest domain transition paths + between the source and target types. + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all shortest paths from {0} to {1}...".format(s, t)) + + try: + for path in nx.all_shortest_paths(self.subG, s, t): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError, KeyError): + # NetworkXError: the type is valid but not in graph, e.g. excluded + # NetworkXNoPath: no paths or the target type is + # not in the graph + # KeyError: work around NetworkX bug + # when the source node is not in the graph + pass + + def transitions(self, type_): + """ + Generator which yields all domain transitions out of a + specified source type. + + Parameters: + type_ The starting type. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + domain transition. + """ + s = self.policy.lookup_type(type_) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all transitions {1} {0}". + format(s, "in to" if self.reverse else "out from")) + + try: + for source, target in self.subG.out_edges_iter(s): + edge = Edge(self.subG, source, target) + + if self.reverse: + real_source, real_target = target, source + else: + real_source, real_target = source, target + + yield step_output(real_source, real_target, + edge.transition, + self.__generate_entrypoints(edge), + edge.setexec, + edge.dyntransition, + edge.setcurrent) + + except NetworkXError: + # NetworkXError: the type is valid but not in graph, e.g. excluded + pass + + def get_stats(self): # pragma: no cover + """ + Get the domain transition graph statistics. + + Return: tuple(nodes, edges) + + nodes The number of nodes (types) in the graph. + edges The number of edges (domain transitions) in the graph. + """ + return (self.G.number_of_nodes(), self.G.number_of_edges()) + + # + # Internal functions follow + # + @staticmethod + def __generate_entrypoints(edge): + """ + Generator which yields the entrypoint, execute, and + type_transition rules for each entrypoint. + + Parameter: + data The dictionary of entrypoints. + + Yield: tuple(type, entry, exec, trans) + + type The entrypoint type. + entry The list of entrypoint rules. + exec The list of execute rules. + trans The list of type_transition rules. + """ + for e in edge.entrypoint: + yield entrypoint_output(e, edge.entrypoint[e], edge.execute[e], edge.type_transition[e]) + + def __generate_steps(self, path): + """ + Generator which yields the source, target, and associated rules + for each domain transition. + + Parameter: + path A list of graph node names representing an information flow path. + + Yield: tuple(source, target, transition, entrypoints, + setexec, dyntransition, setcurrent) + + source The source type for this step of the domain transition. + target The target type for this step of the domain transition. + transition The list of transition rules. + entrypoints Generator which yields entrypoint-related rules. + setexec The list of setexec rules. + dyntranstion The list of dynamic transition rules. + setcurrent The list of setcurrent rules. + """ + + for s in range(1, len(path)): + source = path[s - 1] + target = path[s] + edge = Edge(self.subG, source, target) + + # Yield the actual source and target. + # The above perspective is reversed + # if the graph has been reversed. + if self.reverse: + real_source, real_target = target, source + else: + real_source, real_target = source, target + + yield step_output(real_source, real_target, + edge.transition, + self.__generate_entrypoints(edge), + edge.setexec, + edge.dyntransition, + edge.setcurrent) + + # + # Graph building functions + # + + # Domain transition requirements: + # + # Standard transitions a->b: + # allow a b:process transition; + # allow a b_exec:file execute; + # allow b b_exec:file entrypoint; + # + # and at least one of: + # allow a self:process setexec; + # type_transition a b_exec:process b; + # + # Dynamic transition x->y: + # allow x y:process dyntransition; + # allow x self:process setcurrent; + # + # Algorithm summary: + # 1. iterate over all rules + # 1. skip non allow/type_transition rules + # 2. if process transition or dyntransition, create edge, + # initialize rule lists, add the (dyn)transition rule + # 3. if process setexec or setcurrent, add to appropriate dict + # keyed on the subject + # 4. if file exec, entrypoint, or type_transition:process, + # add to appropriate dict keyed on subject,object. + # 2. Iterate over all graph edges: + # 1. if there is a transition rule (else add to invalid + # transition list): + # 1. use set intersection to find matching exec + # and entrypoint rules. If none, add to invalid + # transition list. + # 2. for each valid entrypoint, add rules to the + # edge's lists if there is either a + # type_transition for it or the source process + # has setexec permissions. + # 3. If there are neither type_transitions nor + # setexec permissions, add to the invalid + # transition list + # 2. if there is a dyntransition rule (else add to invalid + # dyntrans list): + # 1. If the source has a setcurrent rule, add it + # to the edge's list, else add to invalid + # dyntransition list. + # 3. Iterate over all graph edges: + # 1. if the edge has an invalid trans and dyntrans, delete + # the edge. + # 2. if the edge has an invalid trans, clear the related + # lists on the edge. + # 3. if the edge has an invalid dyntrans, clear the related + # lists on the edge. + # + def _build_graph(self): + self.G.clear() + + self.log.info("Building graph from {0}...".format(self.policy)) + + # hash tables keyed on domain type + setexec = defaultdict(list) + setcurrent = defaultdict(list) + + # hash tables keyed on (domain, entrypoint file type) + # the parameter for defaultdict has to be callable + # hence the lambda for the nested defaultdict + execute = defaultdict(lambda: defaultdict(list)) + entrypoint = defaultdict(lambda: defaultdict(list)) + + # hash table keyed on (domain, entrypoint, target domain) + type_trans = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) + + for rule in self.policy.terules(): + if rule.ruletype == "allow": + if rule.tclass not in ["process", "file"]: + continue + + perms = rule.perms + + if rule.tclass == "process": + if "transition" in perms: + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + # only add edges if they actually + # transition to a new type + if s != t: + edge = Edge(self.G, s, t, create=True) + edge.transition.append(rule) + + if "dyntransition" in perms: + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + # only add edges if they actually + # transition to a new type + if s != t: + e = Edge(self.G, s, t, create=True) + e.dyntransition.append(rule) + + if "setexec" in perms: + for s in rule.source.expand(): + setexec[s].append(rule) + + if "setcurrent" in perms: + for s in rule.source.expand(): + setcurrent[s].append(rule) + + else: + if "execute" in perms: + for s, t in itertools.product( + rule.source.expand(), + rule.target.expand()): + execute[s][t].append(rule) + + if "entrypoint" in perms: + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + entrypoint[s][t].append(rule) + + elif rule.ruletype == "type_transition": + if rule.tclass != "process": + continue + + d = rule.default + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + type_trans[s][t][d].append(rule) + + invalid_edge = [] + clear_transition = [] + clear_dyntransition = [] + + for s, t in self.G.edges_iter(): + edge = Edge(self.G, s, t) + invalid_trans = False + invalid_dyntrans = False + + if edge.transition: + # get matching domain exec w/entrypoint type + entry = set(entrypoint[t].keys()) + exe = set(execute[s].keys()) + match = entry.intersection(exe) + + if not match: + # there are no valid entrypoints + invalid_trans = True + else: + # TODO try to improve the + # efficiency in this loop + for m in match: + if s in setexec or type_trans[s][m]: + # add key for each entrypoint + edge.entrypoint[m] += entrypoint[t][m] + edge.execute[m] += execute[s][m] + + if type_trans[s][m][t]: + edge.type_transition[m] += type_trans[s][m][t] + + if s in setexec: + edge.setexec.extend(setexec[s]) + + if not edge.setexec and not edge.type_transition: + invalid_trans = True + else: + invalid_trans = True + + if edge.dyntransition: + if s in setcurrent: + edge.setcurrent.extend(setcurrent[s]) + else: + invalid_dyntrans = True + else: + invalid_dyntrans = True + + # cannot change the edges while iterating over them, + # so keep appropriate lists + if invalid_trans and invalid_dyntrans: + invalid_edge.append(edge) + elif invalid_trans: + clear_transition.append(edge) + elif invalid_dyntrans: + clear_dyntransition.append(edge) + + # Remove invalid transitions + self.G.remove_edges_from(invalid_edge) + for edge in clear_transition: + # if only the regular transition is invalid, + # clear the relevant lists + del edge.transition + del edge.execute + del edge.entrypoint + del edge.type_transition + del edge.setexec + for edge in clear_dyntransition: + # if only the dynamic transition is invalid, + # clear the relevant lists + del edge.dyntransition + del edge.setcurrent + + self.rebuildgraph = False + self.rebuildsubgraph = True + self.log.info("Completed building graph.") + + def __remove_excluded_entrypoints(self): + invalid_edges = [] + for source, target in self.subG.edges_iter(): + edge = Edge(self.subG, source, target) + entrypoints = set(edge.entrypoint) + entrypoints.intersection_update(self.exclude) + + if not entrypoints: + # short circuit if there are no + # excluded entrypoint types on + # this edge. + continue + + for e in entrypoints: + # clear the entrypoint data + del edge.entrypoint[e] + del edge.execute[e] + + try: + del edge.type_transition[e] + except KeyError: # setexec + pass + + # cannot delete the edges while iterating over them + if not edge.entrypoint and not edge.dyntransition: + invalid_edges.append(edge) + + self.subG.remove_edges_from(invalid_edges) + + def _build_subgraph(self): + if self.rebuildgraph: + self._build_graph() + + self.log.info("Building subgraph.") + self.log.debug("Excluding {0}".format(self.exclude)) + self.log.debug("Reverse {0}".format(self.reverse)) + + # reverse graph for reverse DTA + if self.reverse: + self.subG = self.G.reverse(copy=True) + else: + self.subG = self.G.copy() + + if self.exclude: + # delete excluded domains from subgraph + self.subG.remove_nodes_from(self.exclude) + + # delete excluded entrypoints from subgraph + self.__remove_excluded_entrypoints() + + self.rebuildsubgraph = False + self.log.info("Completed building subgraph.") + + +class Edge(object): + + """ + A graph edge. Also used for returning domain transition steps. + + Parameters: + source The source type of the edge. + target The target tyep of the edge. + + Keyword Parameters: + create (T/F) create the edge if it does not exist. + The default is False. + """ + + transition = EdgeAttrList('transition') + setexec = EdgeAttrList('setexec') + dyntransition = EdgeAttrList('dyntransition') + setcurrent = EdgeAttrList('setcurrent') + entrypoint = EdgeAttrDict('entrypoint') + execute = EdgeAttrDict('execute') + type_transition = EdgeAttrDict('type_transition') + + def __init__(self, graph, source, target, create=False): + self.G = graph + self.source = source + self.target = target + + # a bit of a hack to make Edges work + # in NetworkX functions that work on + # 2-tuples of (source, target) + # (see __getitem__ below) + self.st_tuple = (source, target) + + if not self.G.has_edge(source, target): + if not create: + raise ValueError("Edge does not exist in graph") + else: + self.G.add_edge(source, target) + self.transition = None + self.entrypoint = None + self.execute = None + self.type_transition = None + self.setexec = None + self.dyntransition = None + self.setcurrent = None + + def __getitem__(self, key): + return self.st_tuple[key] diff --git a/lib/python2.7/site-packages/setoolsgui/setools/exception.py b/lib/python2.7/site-packages/setoolsgui/setools/exception.py new file mode 100644 index 0000000..c3505cd --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/exception.py @@ -0,0 +1,62 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + +# +# Base class for exceptions +# + + +class SEToolsException(Exception): + + """Base class for all SETools exceptions.""" + pass + +# +# Permission map exceptions +# + + +class PermissionMapException(SEToolsException): + + """Base class for all permission map exceptions.""" + pass + + +class PermissionMapParseError(PermissionMapException): + + """Exception for parse errors while reading permission map files.""" + pass + + +class RuleTypeError(PermissionMapException): + + """Exception for using rules with incorrect rule type.""" + pass + + +class UnmappedClass(PermissionMapException): + + """Exception for classes that are unmapped""" + pass + + +class UnmappedPermission(PermissionMapException): + + """Exception for permissions that are unmapped""" + pass diff --git a/lib/python2.7/site-packages/setoolsgui/setools/fsusequery.py b/lib/python2.7/site-packages/setoolsgui/setools/fsusequery.py new file mode 100644 index 0000000..6825a45 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/fsusequery.py @@ -0,0 +1,87 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import contextquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + + +class FSUseQuery(contextquery.ContextQuery): + + """ + Query fs_use_* statements. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The rule type(s) to match. + fs The criteria to match the file system type. + fs_regex If true, regular expression matching + will be used on the file system type. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + ruletype = None + fs = CriteriaDescriptor("fs_regex") + fs_regex = False + + def results(self): + """Generator which yields all matching fs_use_* statements.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("FS: {0.fs!r}, regex: {0.fs_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for fsu in self.policy.fs_uses(): + if self.ruletype and fsu.ruletype not in self.ruletype: + continue + + if self.fs and not self._match_regex( + fsu.fs, + self.fs, + self.fs_regex): + continue + + if not self._match_context(fsu.context): + continue + + yield fsu diff --git a/lib/python2.7/site-packages/setoolsgui/setools/genfsconquery.py b/lib/python2.7/site-packages/setoolsgui/setools/genfsconquery.py new file mode 100644 index 0000000..c67dfd6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/genfsconquery.py @@ -0,0 +1,98 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import contextquery +from .descriptors import CriteriaDescriptor + + +class GenfsconQuery(contextquery.ContextQuery): + + """ + Query genfscon statements. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + fs The criteria to match the file system type. + fs_regex If true, regular expression matching + will be used on the file system type. + path The criteria to match the path. + path_regex If true, regular expression matching + will be used on the path. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + filetype = None + fs = CriteriaDescriptor("fs_regex") + fs_regex = False + path = CriteriaDescriptor("path_regex") + path_regex = False + + def results(self): + """Generator which yields all matching genfscons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("FS: {0.fs!r}, regex: {0.fs_regex}".format(self)) + self.log.debug("Path: {0.path!r}, regex: {0.path_regex}".format(self)) + self.log.debug("Filetype: {0.filetype!r}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for genfs in self.policy.genfscons(): + if self.fs and not self._match_regex( + genfs.fs, + self.fs, + self.fs_regex): + continue + + if self.path and not self._match_regex( + genfs.path, + self.path, + self.path_regex): + continue + + if self.filetype and not self.filetype == genfs.filetype: + continue + + if not self._match_context(genfs.context): + continue + + yield genfs diff --git a/lib/python2.7/site-packages/setoolsgui/setools/infoflow.py b/lib/python2.7/site-packages/setoolsgui/setools/infoflow.py new file mode 100644 index 0000000..ea3ec32 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/infoflow.py @@ -0,0 +1,403 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import itertools +import logging +from collections import namedtuple + +import networkx as nx +from networkx.exception import NetworkXError, NetworkXNoPath + +from .descriptors import EdgeAttrIntMax, EdgeAttrList + +__all__ = ['InfoFlowAnalysis'] + +# Return values for the analysis +# are in the following tuple format: +step_output = namedtuple("step", ["source", + "target", + "rules"]) + + +class InfoFlowAnalysis(object): + + """Information flow analysis.""" + + def __init__(self, policy, perm_map, min_weight=1, exclude=None): + """ + Parameters: + policy The policy to analyze. + perm_map The permission map or path to the permission map file. + minweight The minimum permission weight to include in the analysis. + (default is 1) + exclude The types excluded from the information flow analysis. + (default is none) + """ + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + + self.min_weight = min_weight + self.perm_map = perm_map + self.exclude = exclude + self.rebuildgraph = True + self.rebuildsubgraph = True + + self.G = nx.DiGraph() + self.subG = None + + @property + def min_weight(self): + return self._min_weight + + @min_weight.setter + def min_weight(self, weight): + if not 1 <= weight <= 10: + raise ValueError( + "Min information flow weight must be an integer 1-10.") + + self._min_weight = weight + self.rebuildsubgraph = True + + @property + def perm_map(self): + return self._perm_map + + @perm_map.setter + def perm_map(self, perm_map): + self._perm_map = perm_map + self.rebuildgraph = True + self.rebuildsubgraph = True + + @property + def exclude(self): + return self._exclude + + @exclude.setter + def exclude(self, types): + if types: + self._exclude = [self.policy.lookup_type(t) for t in types] + else: + self._exclude = [] + + self.rebuildsubgraph = True + + def shortest_path(self, source, target): + """ + Generator which yields one shortest path between the source + and target types (there may be more). + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating one shortest path from {0} to {1}...".format(s, t)) + + try: + yield self.__generate_steps(nx.shortest_path(self.subG, s, t)) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_paths(self, source, target, maxlen=2): + """ + Generator which yields all paths between the source and target + up to the specified maximum path length. This algorithm + tends to get very expensive above 3-5 steps, depending + on the policy complexity. + + Parameters: + source The source type. + target The target type. + maxlen Maximum length of paths. + + Yield: generator(steps) + + steps Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + if maxlen < 1: + raise ValueError("Maximum path length must be positive.") + + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all paths from {0} to {1}, max len {2}...".format(s, t, maxlen)) + + try: + for path in nx.all_simple_paths(self.subG, s, t, maxlen): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError): + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + # NetworkXNoPath: no paths or the target type is + # not in the graph + pass + + def all_shortest_paths(self, source, target): + """ + Generator which yields all shortest paths between the source + and target types. + + Parameters: + source The source type. + target The target type. + + Yield: generator(steps) + + steps Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + s = self.policy.lookup_type(source) + t = self.policy.lookup_type(target) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all shortest paths from {0} to {1}...".format(s, t)) + + try: + for path in nx.all_shortest_paths(self.subG, s, t): + yield self.__generate_steps(path) + except (NetworkXNoPath, NetworkXError, KeyError): + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + # NetworkXNoPath: no paths or the target type is + # not in the graph + # KeyError: work around NetworkX bug + # when the source node is not in the graph + pass + + def infoflows(self, type_, out=True): + """ + Generator which yields all information flows in/out of a + specified source type. + + Parameters: + source The starting type. + + Keyword Parameters: + out If true, information flows out of the type will + be returned. If false, information flows in to the + type will be returned. Default is true. + + Yield: generator(steps) + + steps A generator that returns the tuple of + source, target, and rules for each + information flow. + """ + s = self.policy.lookup_type(type_) + + if self.rebuildsubgraph: + self._build_subgraph() + + self.log.info("Generating all infoflows out of {0}...".format(s)) + + if out: + flows = self.subG.out_edges_iter(s) + else: + flows = self.subG.in_edges_iter(s) + + try: + for source, target in flows: + edge = Edge(self.subG, source, target) + yield step_output(source, target, edge.rules) + except NetworkXError: + # NetworkXError: the type is valid but not in graph, e.g. + # excluded or disconnected due to min weight + pass + + def get_stats(self): # pragma: no cover + """ + Get the information flow graph statistics. + + Return: tuple(nodes, edges) + + nodes The number of nodes (types) in the graph. + edges The number of edges (information flows between types) + in the graph. + """ + return (self.G.number_of_nodes(), self.G.number_of_edges()) + + # + # Internal functions follow + # + + def __generate_steps(self, path): + """ + Generator which returns the source, target, and associated rules + for each information flow step. + + Parameter: + path A list of graph node names representing an information flow path. + + Yield: tuple(source, target, rules) + + source The source type for this step of the information flow. + target The target type for this step of the information flow. + rules The list of rules creating this information flow step. + """ + for s in range(1, len(path)): + edge = Edge(self.subG, path[s - 1], path[s]) + yield step_output(edge.source, edge.target, edge.rules) + + # + # + # Graph building functions + # + # + # 1. _build_graph determines the flow in each direction for each TE + # rule and then expands the rule. All information flows are + # included in this main graph: memory is traded off for efficiency + # as the main graph should only need to be rebuilt if permission + # weights change. + # 2. _build_subgraph derives a subgraph which removes all excluded + # types (nodes) and edges (information flows) which are below the + # minimum weight. This subgraph is rebuilt only if the main graph + # is rebuilt or the minimum weight or excluded types change. + + def _build_graph(self): + self.G.clear() + + self.perm_map.map_policy(self.policy) + + self.log.info("Building graph from {0}...".format(self.policy)) + + for rule in self.policy.terules(): + if rule.ruletype != "allow": + continue + + (rweight, wweight) = self.perm_map.rule_weight(rule) + + for s, t in itertools.product(rule.source.expand(), rule.target.expand()): + # only add flows if they actually flow + # in or out of the source type type + if s != t: + if wweight: + edge = Edge(self.G, s, t, create=True) + edge.rules.append(rule) + edge.weight = wweight + + if rweight: + edge = Edge(self.G, t, s, create=True) + edge.rules.append(rule) + edge.weight = rweight + + self.rebuildgraph = False + self.rebuildsubgraph = True + self.log.info("Completed building graph.") + + def _build_subgraph(self): + if self.rebuildgraph: + self._build_graph() + + self.log.info("Building subgraph...") + self.log.debug("Excluding {0!r}".format(self.exclude)) + self.log.debug("Min weight {0}".format(self.min_weight)) + + # delete excluded types from subgraph + nodes = [n for n in self.G.nodes() if n not in self.exclude] + self.subG = self.G.subgraph(nodes) + + # delete edges below minimum weight. + # no need if weight is 1, since that + # does not exclude any edges. + if self.min_weight > 1: + delete_list = [] + for s, t in self.subG.edges_iter(): + edge = Edge(self.subG, s, t) + if edge.weight < self.min_weight: + delete_list.append(edge) + + self.subG.remove_edges_from(delete_list) + + self.rebuildsubgraph = False + self.log.info("Completed building subgraph.") + + +class Edge(object): + + """ + A graph edge. Also used for returning information flow steps. + + Parameters: + source The source type of the edge. + target The target type of the edge. + + Keyword Parameters: + create (T/F) create the edge if it does not exist. + The default is False. + """ + + rules = EdgeAttrList('rules') + + # use capacity to store the info flow weight so + # we can use network flow algorithms naturally. + # The weight for each edge is 1 since each info + # flow step is no more costly than another + # (see below add_edge() call) + weight = EdgeAttrIntMax('capacity') + + def __init__(self, graph, source, target, create=False): + self.G = graph + self.source = source + self.target = target + + # a bit of a hack to make edges work + # in NetworkX functions that work on + # 2-tuples of (source, target) + # (see __getitem__ below) + self.st_tuple = (source, target) + + if not self.G.has_edge(source, target): + if create: + self.G.add_edge(source, target, weight=1) + self.rules = None + self.weight = None + else: + raise ValueError("Edge does not exist in graph") + + def __getitem__(self, key): + return self.st_tuple[key] diff --git a/lib/python2.7/site-packages/setoolsgui/setools/initsidquery.py b/lib/python2.7/site-packages/setoolsgui/setools/initsidquery.py new file mode 100644 index 0000000..1eb3790 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/initsidquery.py @@ -0,0 +1,74 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import contextquery + + +class InitialSIDQuery(compquery.ComponentQuery, contextquery.ContextQuery): + + """ + Initial SID (Initial context) query. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The Initial SID name to match. + name_regex If true, regular expression matching + will be used on the Initial SID name. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + def results(self): + """Generator which yields all matching initial SIDs.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for i in self.policy.initialsids(): + if not self._match_name(i): + continue + + if not self._match_context(i.context): + continue + + yield i diff --git a/lib/python2.7/site-packages/setoolsgui/setools/mixins.py b/lib/python2.7/site-packages/setoolsgui/setools/mixins.py new file mode 100644 index 0000000..a31d420 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/mixins.py @@ -0,0 +1,91 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=attribute-defined-outside-init,no-member +import re + +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + + +class MatchAlias(object): + + """Mixin for matching an object's aliases.""" + + alias = CriteriaDescriptor("alias_regex") + alias_regex = False + + def _match_alias(self, obj): + """ + Match the alias criteria + + Parameter: + obj An object with an alias generator method named "aliases" + """ + + if not self.alias: + # if there is no criteria, everything matches. + return True + + return self._match_in_set(obj.aliases(), self.alias, self.alias_regex) + + +class MatchObjClass(object): + + """Mixin for matching an object's class.""" + + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + + def _match_object_class(self, obj): + """ + Match the object class criteria + + Parameter: + obj An object with an object class attribute named "tclass" + """ + + if not self.tclass: + # if there is no criteria, everything matches. + return True + elif self.tclass_regex: + return bool(self.tclass.search(str(obj.tclass))) + else: + return obj.tclass in self.tclass + + +class MatchPermission(object): + + """Mixin for matching an object's permissions.""" + + perms = CriteriaSetDescriptor("perms_regex") + perms_equal = False + perms_regex = False + + def _match_perms(self, obj): + """ + Match the permission criteria + + Parameter: + obj An object with a permission set class attribute named "perms" + """ + + if not self.perms: + # if there is no criteria, everything matches. + return True + + return self._match_regex_or_set(obj.perms, self.perms, self.perms_equal, self.perms_regex) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/mlsrulequery.py b/lib/python2.7/site-packages/setoolsgui/setools/mlsrulequery.py new file mode 100644 index 0000000..3a9e1bf --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/mlsrulequery.py @@ -0,0 +1,115 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor + + +class MLSRuleQuery(mixins.MatchObjClass, query.PolicyQuery): + + """ + Query MLS rules. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source type/attribute to match. + source_regex If true, regular expression matching will + be used on the source type/attribute. + target The name of the target type/attribute to match. + target_regex If true, regular expression matching will + be used on the target type/attribute. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + """ + + ruletype = RuletypeDescriptor("validate_mls_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_type_or_attr") + source_regex = False + target = CriteriaDescriptor("target_regex", "lookup_type_or_attr") + target_regex = False + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + default = CriteriaDescriptor(lookup_function="lookup_range") + default_overlap = False + default_subset = False + default_superset = False + default_proper = False + + def results(self): + """Generator which yields all matching MLS rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Source: {0.source!r}, regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Default: {0.default!r}, overlap: {0.default_overlap}, " + "subset: {0.default_subset}, superset: {0.default_superset}, " + "proper: {0.default_proper}".format(self)) + + for rule in self.policy.mlsrules(): + # + # Matching on rule type + # + if self.ruletype: + if rule.ruletype not in self.ruletype: + continue + + # + # Matching on source type + # + if self.source and not self._match_regex( + rule.source, + self.source, + self.source_regex): + continue + + # + # Matching on target type + # + if self.target and not self._match_regex( + rule.target, + self.target, + self.target_regex): + continue + + # + # Matching on object class + # + if not self._match_object_class(rule): + continue + + # + # Matching on range + # + if self.default and not self._match_range( + rule.default, + self.default, + self.default_subset, + self.default_overlap, + self.default_superset, + self.default_proper): + continue + + # if we get here, we have matched all available criteria + yield rule diff --git a/lib/python2.7/site-packages/setoolsgui/setools/netifconquery.py b/lib/python2.7/site-packages/setoolsgui/setools/netifconquery.py new file mode 100644 index 0000000..30db977 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/netifconquery.py @@ -0,0 +1,77 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import contextquery + + +class NetifconQuery(compquery.ComponentQuery, contextquery.ContextQuery): + + """ + Network interface context query. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the network interface to match. + name_regex If true, regular expression matching will + be used for matching the name. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + def results(self): + """Generator which yields all matching netifcons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for netif in self.policy.netifcons(): + if self.name and not self._match_regex( + netif.netif, + self.name, + self.name_regex): + continue + + if not self._match_context(netif.context): + continue + + yield netif diff --git a/lib/python2.7/site-packages/setoolsgui/setools/nodeconquery.py b/lib/python2.7/site-packages/setoolsgui/setools/nodeconquery.py new file mode 100644 index 0000000..eb21d81 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/nodeconquery.py @@ -0,0 +1,148 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +try: + import ipaddress +except ImportError: # pragma: no cover + pass + +import logging +from socket import AF_INET, AF_INET6 + +from . import contextquery + + +class NodeconQuery(contextquery.ContextQuery): + + """ + Query nodecon statements. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + network The IPv4/IPv6 address or IPv4/IPv6 network address + with netmask, e.g. 192.168.1.0/255.255.255.0 or + "192.168.1.0/24". + network_overlap If true, the net will match if it overlaps with + the nodecon's network instead of equality. + ip_version The IP version of the nodecon to match. (socket.AF_INET + for IPv4 or socket.AF_INET6 for IPv6) + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + _network = None + network_overlap = False + _ip_version = None + + @property + def ip_version(self): + return self._ip_version + + @ip_version.setter + def ip_version(self, value): + if value: + if not (value == AF_INET or value == AF_INET6): + raise ValueError( + "The address family must be {0} for IPv4 or {1} for IPv6.". + format(AF_INET, AF_INET6)) + + self._ip_version = value + else: + self._ip_version = None + + @property + def network(self): + return self._network + + @network.setter + def network(self, value): + if value: + try: + self._network = ipaddress.ip_network(value) + except NameError: # pragma: no cover + raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") + else: + self._network = None + + def results(self): + """Generator which yields all matching nodecons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Network: {0.network!r}, overlap: {0.network_overlap}".format(self)) + self.log.debug("IP Version: {0.ip_version}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for nodecon in self.policy.nodecons(): + + if self.network: + try: + netmask = ipaddress.ip_address(nodecon.netmask) + except NameError: # pragma: no cover + # Should never actually hit this since the self.network + # setter raises the same exception. + raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") + + # Python 3.3's IPv6Network constructor does not support + # expanded netmasks, only CIDR numbers. Convert netmask + # into CIDR. + # This is Brian Kernighan's method for counting set bits. + # If the netmask happens to be invalid, this will + # not detect it. + CIDR = 0 + int_netmask = int(netmask) + while int_netmask: + int_netmask &= int_netmask - 1 + CIDR += 1 + + net = ipaddress.ip_network('{0}/{1}'.format(nodecon.address, CIDR)) + + if self.network_overlap: + if not self.network.overlaps(net): + continue + else: + if not net == self.network: + continue + + if self.ip_version and self.ip_version != nodecon.ip_version: + continue + + if not self._match_context(nodecon.context): + continue + + yield nodecon diff --git a/lib/python2.7/site-packages/setoolsgui/setools/objclassquery.py b/lib/python2.7/site-packages/setoolsgui/setools/objclassquery.py new file mode 100644 index 0000000..8f40df8 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/objclassquery.py @@ -0,0 +1,101 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor +from .policyrep.exception import NoCommon + + +class ObjClassQuery(compquery.ComponentQuery): + + """ + Query object classes. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the object set to match. + name_regex If true, regular expression matching will + be used for matching the name. + common The name of the inherited common to match. + common_regex If true, regular expression matching will + be used for matching the common name. + perms The permissions to match. + perms_equal If true, only commons with permission sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + perms_regex If true, regular expression matching + will be used on the permission names instead + of set logic. + comparison will not be used. + perms_indirect If false, permissions inherited from a common + permission set not will be evaluated. Default + is true. + """ + + common = CriteriaDescriptor("common_regex", "lookup_common") + common_regex = False + perms = CriteriaSetDescriptor("perms_regex") + perms_equal = False + perms_indirect = True + perms_regex = False + + def results(self): + """Generator which yields all matching object classes.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Common: {0.common!r}, regex: {0.common_regex}".format(self)) + self.log.debug("Perms: {0.perms}, regex: {0.perms_regex}, " + "eq: {0.perms_equal}, indirect: {0.perms_indirect}".format(self)) + + for class_ in self.policy.classes(): + if not self._match_name(class_): + continue + + if self.common: + try: + if not self._match_regex( + class_.common, + self.common, + self.common_regex): + continue + except NoCommon: + continue + + if self.perms: + perms = class_.perms + + if self.perms_indirect: + try: + perms |= class_.common.perms + except NoCommon: + pass + + if not self._match_regex_or_set( + perms, + self.perms, + self.perms_equal, + self.perms_regex): + continue + + yield class_ diff --git a/lib/python2.7/site-packages/setoolsgui/setools/permmap.py b/lib/python2.7/site-packages/setoolsgui/setools/permmap.py new file mode 100644 index 0000000..54cd9f9 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/permmap.py @@ -0,0 +1,363 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import sys +import logging +from errno import ENOENT + +from . import exception +from . import policyrep + + +class PermissionMap(object): + + """Permission Map for information flow analysis.""" + + valid_infoflow_directions = ["r", "w", "b", "n", "u"] + min_weight = 1 + max_weight = 10 + + def __init__(self, permmapfile=None): + """ + Parameter: + permmapfile The path to the permission map to load. + """ + self.log = logging.getLogger(self.__class__.__name__) + + if permmapfile: + self.load(permmapfile) + else: + for path in ["data/", sys.prefix + "/share/setools/"]: + try: + self.load(path + "perm_map") + break + except (IOError, OSError) as err: + if err.errno != ENOENT: + raise + else: + raise RuntimeError("Unable to load default permission map.") + + def load(self, permmapfile): + """ + Parameter: + permmapfile The path to the permission map to load. + """ + self.log.info("Opening permission map \"{0}\"".format(permmapfile)) + + # state machine + # 1 = read number of classes + # 2 = read class name and number of perms + # 3 = read perms + with open(permmapfile, "r") as mapfile: + class_count = 0 + num_classes = 0 + state = 1 + + self.permmap = dict() + + for line_num, line in enumerate(mapfile, start=1): + entry = line.split() + + if len(entry) == 0 or entry[0][0] == '#': + continue + + if state == 1: + try: + num_classes = int(entry[0]) + except ValueError: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid number of classes: {2}". + format(permmapfile, line_num, entry[0])) + + if num_classes < 1: + raise exception.PermissionMapParseError( + "{0}:{1}:Number of classes must be positive: {2}". + format(permmapfile, line_num, entry[0])) + + state = 2 + + elif state == 2: + if len(entry) != 3 or entry[0] != "class": + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid class declaration: {2}". + format(permmapfile, line_num, entry)) + + class_name = str(entry[1]) + + try: + num_perms = int(entry[2]) + except ValueError: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid number of permissions: {2}". + format(permmapfile, line_num, entry[2])) + + if num_perms < 1: + raise exception.PermissionMapParseError( + "{0}:{1}:Number of permissions must be positive: {2}". + format(permmapfile, line_num, entry[2])) + + class_count += 1 + if class_count > num_classes: + raise exception.PermissionMapParseError( + "{0}:{1}:Extra class found: {2}". + format(permmapfile, line_num, class_name)) + + self.permmap[class_name] = dict() + perm_count = 0 + state = 3 + + elif state == 3: + perm_name = str(entry[0]) + + flow_direction = str(entry[1]) + if flow_direction not in self.valid_infoflow_directions: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid information flow direction: {2}". + format(permmapfile, line_num, entry[1])) + + try: + weight = int(entry[2]) + except ValueError: + raise exception.PermissionMapParseError( + "{0}:{1}:Invalid permission weight: {2}". + format(permmapfile, line_num, entry[2])) + + if not self.min_weight <= weight <= self.max_weight: + raise exception.PermissionMapParseError( + "{0}:{1}:Permission weight must be {3}-{4}: {2}". + format(permmapfile, line_num, entry[2], + self.min_weight, self.max_weight)) + + self.permmap[class_name][perm_name] = {'direction': flow_direction, + 'weight': weight, + 'enabled': True} + + perm_count += 1 + if perm_count >= num_perms: + state = 2 + + def exclude_class(self, class_): + """ + Exclude all permissions in an object class for calculating rule weights. + + Parameter: + class_ The object class to exclude. + + Exceptions: + UnmappedClass The specified object class is not mapped. + """ + + classname = str(class_) + + try: + for perm in self.permmap[classname]: + self.permmap[classname][perm]['enabled'] = False + except KeyError: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + def exclude_permission(self, class_, permission): + """ + Exclude a permission for calculating rule weights. + + Parameter: + class_ The object class of the permission. + permission The permission name to exclude. + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['enabled'] = False + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) + + def include_class(self, class_): + """ + Include all permissions in an object class for calculating rule weights. + + Parameter: + class_ The object class to include. + + Exceptions: + UnmappedClass The specified object class is not mapped. + """ + + classname = str(class_) + + try: + for perm in self.permmap[classname]: + self.permmap[classname][perm]['enabled'] = True + except KeyError: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + def include_permission(self, class_, permission): + """ + Include a permission for calculating rule weights. + + Parameter: + class_ The object class of the permission. + permission The permission name to include. + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['enabled'] = True + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) + + def map_policy(self, policy): + """Create mappings for all classes and permissions in the specified policy.""" + for class_ in policy.classes(): + class_name = str(class_) + + if class_name not in self.permmap: + self.log.info("Adding unmapped class {0} from {1}".format(class_name, policy)) + self.permmap[class_name] = dict() + + perms = class_.perms + + try: + perms |= class_.common.perms + except policyrep.exception.NoCommon: + pass + + for perm_name in perms: + if perm_name not in self.permmap[class_name]: + self.log.info("Adding unmapped permission {0} in {1} from {2}". + format(perm_name, class_name, policy)) + self.permmap[class_name][perm_name] = {'direction': 'u', + 'weight': 1, + 'enabled': True} + + def rule_weight(self, rule): + """ + Get the type enforcement rule's information flow read and write weights. + + Parameter: + rule A type enforcement rule. + + Return: Tuple(read_weight, write_weight) + read_weight The type enforcement rule's read weight. + write_weight The type enforcement rule's write weight. + """ + + write_weight = 0 + read_weight = 0 + class_name = str(rule.tclass) + + if rule.ruletype != 'allow': + raise exception.RuleTypeError("{0} rules cannot be used for calculating a weight". + format(rule.ruletype)) + + if class_name not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(class_name)) + + # iterate over the permissions and determine the + # weight of the rule in each direction. The result + # is the largest-weight permission in each direction + for perm_name in rule.perms: + try: + mapping = self.permmap[class_name][perm_name] + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(class_name, perm_name)) + + if not mapping['enabled']: + continue + + if mapping['direction'] == "r": + read_weight = max(read_weight, mapping['weight']) + elif mapping['direction'] == "w": + write_weight = max(write_weight, mapping['weight']) + elif mapping['direction'] == "b": + read_weight = max(read_weight, mapping['weight']) + write_weight = max(write_weight, mapping['weight']) + + return (read_weight, write_weight) + + def set_direction(self, class_, permission, direction): + """ + Set the information flow direction of a permission. + + Parameter: + class_ The object class of the permission. + permission The permission name. + direction The information flow direction the permission (r/w/b/n). + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + + if direction not in self.valid_infoflow_directions: + raise ValueError("Invalid information flow direction: {0}".format(direction)) + + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['direction'] = direction + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) + + def set_weight(self, class_, permission, weight): + """ + Set the weight of a permission. + + Parameter: + class_ The object class of the permission. + permission The permission name. + weight The weight of the permission (1-10). + + Exceptions: + UnmappedClass The specified object class is not mapped. + UnmappedPermission The specified permission is not mapped for the object class. + """ + + if not self.min_weight <= weight <= self.max_weight: + raise ValueError("Permission weights must be 1-10: {0}".format(weight)) + + classname = str(class_) + + if classname not in self.permmap: + raise exception.UnmappedClass("{0} is not mapped.".format(classname)) + + try: + self.permmap[classname][permission]['weight'] = weight + except KeyError: + raise exception.UnmappedPermission("{0}:{1} is not mapped.". + format(classname, permission)) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/polcapquery.py b/lib/python2.7/site-packages/setoolsgui/setools/polcapquery.py new file mode 100644 index 0000000..e024b05 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/polcapquery.py @@ -0,0 +1,47 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery + + +class PolCapQuery(compquery.ComponentQuery): + + """ + Query SELinux policy capabilities + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the policy capability to match. + name_regex If true, regular expression matching will + be used for matching the name. + """ + + def results(self): + """Generator which yields all matching policy capabilities.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + + for cap in self.policy.polcaps(): + if not self._match_name(cap): + continue + + yield cap diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/__init__.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/__init__.py new file mode 100644 index 0000000..b03e524 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/__init__.py @@ -0,0 +1,568 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=too-many-public-methods +# +# Create a Python representation of the policy. +# The idea is that this is module provides convenient +# abstractions and methods for accessing the policy +# structures. +import logging +from itertools import chain +from errno import ENOENT + +try: + import selinux +except ImportError: + pass + +from . import qpol + +# The libqpol SWIG class is not quite natural for +# Python the policy is repeatedly referenced in the +# function calls, which makes sense for C code +# but not for python code, so each object keeps +# a reference to the policy for internal use. +# This also makes sense since an object would only +# be valid for the policy it comes from. + +# Exceptions +from . import exception + +# Components +from . import boolcond +from . import default +from . import mls +from . import objclass +from . import polcap +from . import role +from . import typeattr +from . import user + +# Rules +from . import mlsrule +from . import rbacrule +from . import terule + +# Constraints +from . import constraint + +# In-policy Labeling +from . import fscontext +from . import initsid +from . import netcontext + + +class SELinuxPolicy(object): + + """The complete SELinux policy.""" + + def __init__(self, policyfile=None): + """ + Parameter: + policyfile Path to a policy to open. + """ + + self.log = logging.getLogger(self.__class__.__name__) + self.policy = None + self.filename = None + + if policyfile: + self._load_policy(policyfile) + else: + try: + self._load_running_policy() + except NameError: + raise RuntimeError("Loading the running policy requires libselinux Python bindings") + + def __repr__(self): + return "".format(self.filename) + + def __str__(self): + return self.filename + + def __deepcopy__(self, memo): + # shallow copy as all of the members are immutable + newobj = SELinuxPolicy.__new__(SELinuxPolicy) + newobj.policy = self.policy + newobj.filename = self.filename + memo[id(self)] = newobj + return newobj + + # + # Policy loading functions + # + + def _load_policy(self, filename): + """Load the specified policy.""" + self.log.info("Opening SELinux policy \"{0}\"".format(filename)) + + try: + self.policy = qpol.qpol_policy_factory(str(filename)) + except SyntaxError as err: + raise exception.InvalidPolicy("Error opening policy file \"{0}\": {1}". + format(filename, err)) + + self.log.info("Successfully opened SELinux policy \"{0}\"".format(filename)) + self.filename = filename + + @staticmethod + def _potential_policies(): + """Generate a list of potential policies to use.""" + # Start with binary policies in the standard location + base_policy_path = selinux.selinux_binary_policy_path() + for version in range(qpol.QPOL_POLICY_MAX_VERSION, qpol.QPOL_POLICY_MIN_VERSION-1, -1): + yield "{0}.{1}".format(base_policy_path, version) + + # Last chance, try selinuxfs. This is not first, to avoid + # holding kernel memory for a long time + if selinux.selinuxfs_exists(): + yield selinux.selinux_current_policy_path() + + def _load_running_policy(self): + """Try to load the current running policy.""" + self.log.info("Attempting to locate current running policy.") + + for filename in self._potential_policies(): + try: + self._load_policy(filename) + except OSError as err: + if err.errno != ENOENT: + raise + else: + break + else: + raise RuntimeError("Unable to locate an SELinux policy to load.") + + # + # Policy properties + # + @property + def handle_unknown(self): + """The handle unknown permissions setting (allow,deny,reject)""" + return self.policy.handle_unknown() + + @property + def mls(self): + """(T/F) The policy has MLS enabled.""" + return mls.enabled(self.policy) + + @property + def version(self): + """The policy database version (e.g. v29)""" + return self.policy.version() + + # + # Policy statistics + # + + @property + def allow_count(self): + """The number of (type) allow rules.""" + return self.policy.avrule_allow_count() + + @property + def auditallow_count(self): + """The number of auditallow rules.""" + return self.policy.avrule_auditallow_count() + + @property + def boolean_count(self): + """The number of Booleans.""" + return self.policy.bool_count() + + @property + def category_count(self): + """The number of categories.""" + return sum(1 for _ in self.categories()) + + @property + def class_count(self): + """The number of object classes.""" + return self.policy.class_count() + + @property + def common_count(self): + """The number of common permission sets.""" + return self.policy.common_count() + + @property + def conditional_count(self): + """The number of conditionals.""" + return self.policy.cond_count() + + @property + def constraint_count(self): + """The number of standard constraints.""" + return sum(1 for c in self.constraints() if c.ruletype == "constrain") + + @property + def dontaudit_count(self): + """The number of dontaudit rules.""" + return self.policy.avrule_dontaudit_count() + + @property + def fs_use_count(self): + """fs_use_* statements.""" + return self.policy.fs_use_count() + + @property + def genfscon_count(self): + """The number of genfscon statements.""" + return self.policy.genfscon_count() + + @property + def initialsids_count(self): + """The number of initial sid statements.""" + return self.policy.isid_count() + + @property + def level_count(self): + """The number of levels.""" + return sum(1 for _ in self.levels()) + + @property + def mlsconstraint_count(self): + """The number of MLS constraints.""" + return sum(1 for c in self.constraints() if c.ruletype == "mlsconstrain") + + @property + def mlsvalidatetrans_count(self): + """The number of MLS validatetrans.""" + return sum(1 for v in self.constraints() if v.ruletype == "mlsvalidatetrans") + + @property + def netifcon_count(self): + """The number of netifcon statements.""" + return self.policy.netifcon_count() + + @property + def neverallow_count(self): + """The number of neverallow rules.""" + return self.policy.avrule_neverallow_count() + + @property + def nodecon_count(self): + """The number of nodecon statements.""" + return self.policy.nodecon_count() + + @property + def permission_count(self): + """The number of permissions.""" + return sum(len(c.perms) for c in chain(self.commons(), self.classes())) + + @property + def permissives_count(self): + """The number of permissive types.""" + return self.policy.permissive_count() + + @property + def polcap_count(self): + """The number of policy capabilities.""" + return self.policy.polcap_count() + + @property + def portcon_count(self): + """The number of portcon statements.""" + return self.policy.portcon_count() + + @property + def range_transition_count(self): + """The number of range_transition rules.""" + return self.policy.range_trans_count() + + @property + def role_count(self): + """The number of roles.""" + return self.policy.role_count() + + @property + def role_allow_count(self): + """The number of (role) allow rules.""" + return self.policy.role_allow_count() + + @property + def role_transition_count(self): + """The number of role_transition rules.""" + return self.policy.role_trans_count() + + @property + def type_attribute_count(self): + """The number of (type) attributes.""" + return sum(1 for _ in self.typeattributes()) + + @property + def type_count(self): + """The number of types.""" + return sum(1 for _ in self.types()) + + @property + def type_change_count(self): + """The number of type_change rules.""" + return self.policy.terule_change_count() + + @property + def type_member_count(self): + """The number of type_member rules.""" + return self.policy.terule_member_count() + + @property + def type_transition_count(self): + """The number of type_transition rules.""" + return self.policy.terule_trans_count() + self.policy.filename_trans_count() + + @property + def user_count(self): + """The number of users.""" + return self.policy.user_count() + + @property + def validatetrans_count(self): + """The number of validatetrans.""" + return sum(1 for v in self.constraints() if v.ruletype == "validatetrans") + + # + # Policy components lookup functions + # + def lookup_boolean(self, name): + """Look up a Boolean.""" + return boolcond.boolean_factory(self.policy, name) + + def lookup_class(self, name): + """Look up an object class.""" + return objclass.class_factory(self.policy, name) + + def lookup_common(self, name): + """Look up a common permission set.""" + return objclass.common_factory(self.policy, name) + + def lookup_initialsid(self, name): + """Look up an initial sid.""" + return initsid.initialsid_factory(self.policy, name) + + def lookup_level(self, level): + """Look up a MLS level.""" + return mls.level_factory(self.policy, level) + + def lookup_sensitivity(self, name): + """Look up a MLS sensitivity by name.""" + return mls.sensitivity_factory(self.policy, name) + + def lookup_range(self, range_): + """Look up a MLS range.""" + return mls.range_factory(self.policy, range_) + + def lookup_role(self, name): + """Look up a role by name.""" + return role.role_factory(self.policy, name) + + def lookup_type(self, name): + """Look up a type by name.""" + return typeattr.type_factory(self.policy, name, deref=True) + + def lookup_type_or_attr(self, name): + """Look up a type or type attribute by name.""" + return typeattr.type_or_attr_factory(self.policy, name, deref=True) + + def lookup_typeattr(self, name): + """Look up a type attribute by name.""" + return typeattr.attribute_factory(self.policy, name) + + def lookup_user(self, name): + """Look up a user by name.""" + return user.user_factory(self.policy, name) + + # + # Policy components generators + # + + def bools(self): + """Generator which yields all Booleans.""" + for bool_ in self.policy.bool_iter(): + yield boolcond.boolean_factory(self.policy, bool_) + + def categories(self): + """Generator which yields all MLS categories.""" + for cat in self.policy.cat_iter(): + try: + yield mls.category_factory(self.policy, cat) + except TypeError: + # libqpol unfortunately iterates over aliases too + pass + + def classes(self): + """Generator which yields all object classes.""" + for class_ in self.policy.class_iter(): + yield objclass.class_factory(self.policy, class_) + + def commons(self): + """Generator which yields all commons.""" + for common in self.policy.common_iter(): + yield objclass.common_factory(self.policy, common) + + def defaults(self): + """Generator which yields all default_* statements.""" + for default_ in self.policy.default_iter(): + try: + for default_obj in default.default_factory(self.policy, default_): + yield default_obj + except exception.NoDefaults: + # qpol iterates over all classes. Handle case + # where a class has no default_* settings. + pass + + def levels(self): + """Generator which yields all level declarations.""" + for level in self.policy.level_iter(): + + try: + yield mls.level_decl_factory(self.policy, level) + except TypeError: + # libqpol unfortunately iterates over levels and sens aliases + pass + + def polcaps(self): + """Generator which yields all policy capabilities.""" + for cap in self.policy.polcap_iter(): + yield polcap.polcap_factory(self.policy, cap) + + def roles(self): + """Generator which yields all roles.""" + for role_ in self.policy.role_iter(): + yield role.role_factory(self.policy, role_) + + def sensitivities(self): + """Generator which yields all sensitivities.""" + # see mls.py for more info on why level_iter is used here. + for sens in self.policy.level_iter(): + try: + yield mls.sensitivity_factory(self.policy, sens) + except TypeError: + # libqpol unfortunately iterates over sens and aliases + pass + + def types(self): + """Generator which yields all types.""" + for type_ in self.policy.type_iter(): + try: + yield typeattr.type_factory(self.policy, type_) + except TypeError: + # libqpol unfortunately iterates over attributes and aliases + pass + + def typeattributes(self): + """Generator which yields all (type) attributes.""" + for type_ in self.policy.type_iter(): + try: + yield typeattr.attribute_factory(self.policy, type_) + except TypeError: + # libqpol unfortunately iterates over attributes and aliases + pass + + def users(self): + """Generator which yields all users.""" + for user_ in self.policy.user_iter(): + yield user.user_factory(self.policy, user_) + + # + # Policy rules generators + # + def mlsrules(self): + """Generator which yields all MLS rules.""" + for rule in self.policy.range_trans_iter(): + yield mlsrule.mls_rule_factory(self.policy, rule) + + def rbacrules(self): + """Generator which yields all RBAC rules.""" + for rule in chain(self.policy.role_allow_iter(), + self.policy.role_trans_iter()): + yield rbacrule.rbac_rule_factory(self.policy, rule) + + def terules(self): + """Generator which yields all type enforcement rules.""" + for rule in chain(self.policy.avrule_iter(), + self.policy.terule_iter(), + self.policy.filename_trans_iter()): + yield terule.te_rule_factory(self.policy, rule) + + # + # Policy rule type validators + # + @staticmethod + def validate_constraint_ruletype(types): + """Validate constraint types.""" + constraint.validate_ruletype(types) + + @staticmethod + def validate_mls_ruletype(types): + """Validate MLS rule types.""" + mlsrule.validate_ruletype(types) + + @staticmethod + def validate_rbac_ruletype(types): + """Validate RBAC rule types.""" + rbacrule.validate_ruletype(types) + + @staticmethod + def validate_te_ruletype(types): + """Validate type enforcement rule types.""" + terule.validate_ruletype(types) + + # + # Constraints generators + # + + def constraints(self): + """Generator which yields all constraints (regular and MLS).""" + for constraint_ in chain(self.policy.constraint_iter(), + self.policy.validatetrans_iter()): + + yield constraint.constraint_factory(self.policy, constraint_) + + # + # In-policy Labeling statement generators + # + def fs_uses(self): + """Generator which yields all fs_use_* statements.""" + for fs_use in self.policy.fs_use_iter(): + yield fscontext.fs_use_factory(self.policy, fs_use) + + def genfscons(self): + """Generator which yields all genfscon statements.""" + for fscon in self.policy.genfscon_iter(): + yield fscontext.genfscon_factory(self.policy, fscon) + + def initialsids(self): + """Generator which yields all initial SID statements.""" + for sid in self.policy.isid_iter(): + yield initsid.initialsid_factory(self.policy, sid) + + def netifcons(self): + """Generator which yields all netifcon statements.""" + for ifcon in self.policy.netifcon_iter(): + yield netcontext.netifcon_factory(self.policy, ifcon) + + def nodecons(self): + """Generator which yields all nodecon statements.""" + for node in self.policy.nodecon_iter(): + yield netcontext.nodecon_factory(self.policy, node) + + def portcons(self): + """Generator which yields all portcon statements.""" + for port in self.policy.portcon_iter(): + yield netcontext.portcon_factory(self.policy, port) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/_qpol.so b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/_qpol.so new file mode 100755 index 0000000000000000000000000000000000000000..aaccf28502370b3a08b4d557f04c7bd4076735b4 GIT binary patch literal 2151445 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∈-e9vJGtJPG4ME{jvWtOX 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⋘|sjAG#i&zx@p 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

9*8>7)B^*>sKlGA^Z2BlWPZ{E;Is7r!g=vfa(Ay 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� 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

#D3B<{coS}4a2PR0S8poMbW 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 z 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%0D%QzoII!?BSQd;Sq&8etAlZ1 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 Jfa4LIgaISp`7Hnd literal 0 HcmV?d00001 diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/boolcond.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/boolcond.py new file mode 100644 index 0000000..c3c0608 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/boolcond.py @@ -0,0 +1,167 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol + + +def boolean_factory(policy, name): + """Factory function for creating Boolean statement objects.""" + + if isinstance(name, Boolean): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_bool_t): + return Boolean(policy, name) + + try: + return Boolean(policy, qpol.qpol_bool_t(policy, str(name))) + except ValueError: + raise exception.InvalidBoolean("{0} is not a valid Boolean".format(name)) + + +def condexpr_factory(policy, name): + """Factory function for creating conditional expression objects.""" + + if not isinstance(name, qpol.qpol_cond_t): + raise TypeError("Conditional expressions cannot be looked up.") + + return ConditionalExpr(policy, name) + + +class Boolean(symbol.PolicySymbol): + + """A Boolean.""" + + @property + def state(self): + """The default state of the Boolean.""" + return bool(self.qpol_symbol.state(self.policy)) + + def statement(self): + """The policy statement.""" + return "bool {0} {1};".format(self, str(self.state).lower()) + + +class ConditionalExpr(symbol.PolicySymbol): + + """A conditional policy expression.""" + + _cond_expr_val_to_text = { + qpol.QPOL_COND_EXPR_NOT: "!", + qpol.QPOL_COND_EXPR_OR: "||", + qpol.QPOL_COND_EXPR_AND: "&&", + qpol.QPOL_COND_EXPR_XOR: "^", + qpol.QPOL_COND_EXPR_EQ: "==", + qpol.QPOL_COND_EXPR_NEQ: "!="} + + _cond_expr_val_to_precedence = { + qpol.QPOL_COND_EXPR_NOT: 5, + qpol.QPOL_COND_EXPR_OR: 1, + qpol.QPOL_COND_EXPR_AND: 3, + qpol.QPOL_COND_EXPR_XOR: 2, + qpol.QPOL_COND_EXPR_EQ: 4, + qpol.QPOL_COND_EXPR_NEQ: 4} + + def __contains__(self, other): + for expr_node in self.qpol_symbol.expr_node_iter(self.policy): + expr_node_type = expr_node.expr_type(self.policy) + + if expr_node_type == qpol.QPOL_COND_EXPR_BOOL and other == \ + boolean_factory(self.policy, expr_node.get_boolean(self.policy)): + return True + + return False + + def __str__(self): + # qpol representation is in postfix notation. This code + # converts it to infix notation. Parentheses are added + # to ensure correct expressions, though they may end up + # being overused. Set previous operator at start to the + # highest precedence (NOT) so if there is a single binary + # operator, no parentheses are output + stack = [] + prev_op_precedence = self._cond_expr_val_to_precedence[qpol.QPOL_COND_EXPR_NOT] + for expr_node in self.qpol_symbol.expr_node_iter(self.policy): + expr_node_type = expr_node.expr_type(self.policy) + + if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: + # append the boolean name + nodebool = boolean_factory( + self.policy, expr_node.get_boolean(self.policy)) + stack.append(str(nodebool)) + elif expr_node_type == qpol.QPOL_COND_EXPR_NOT: # unary operator + operand = stack.pop() + operator = self._cond_expr_val_to_text[expr_node_type] + op_precedence = self._cond_expr_val_to_precedence[expr_node_type] + + # NOT is the highest precedence, so only need + # parentheses if the operand is a subexpression + if isinstance(operand, list): + subexpr = [operator, "(", operand, ")"] + else: + subexpr = [operator, operand] + + stack.append(subexpr) + prev_op_precedence = op_precedence + else: + operand1 = stack.pop() + operand2 = stack.pop() + operator = self._cond_expr_val_to_text[expr_node_type] + op_precedence = self._cond_expr_val_to_precedence[expr_node_type] + + if prev_op_precedence > op_precedence: + # if previous operator is of higher precedence + # no parentheses are needed. + subexpr = [operand1, operator, operand2] + else: + subexpr = ["(", operand1, operator, operand2, ")"] + + stack.append(subexpr) + prev_op_precedence = op_precedence + + return self.__unwind_subexpression(stack) + + def __unwind_subexpression(self, expr): + ret = [] + + # do a string.join on sublists (subexpressions) + for i in expr: + if isinstance(i, list): + ret.append(self.__unwind_subexpression(i)) + else: + ret.append(i) + + return ' '.join(ret) + + @property + def booleans(self): + """The set of Booleans in the expression.""" + bools = set() + + for expr_node in self.qpol_symbol.expr_node_iter(self.policy): + expr_node_type = expr_node.expr_type(self.policy) + + if expr_node_type == qpol.QPOL_COND_EXPR_BOOL: + bools.add(boolean_factory(self.policy, expr_node.get_boolean(self.policy))) + + return bools + + def statement(self): + raise exception.NoStatement diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/constraint.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/constraint.py new file mode 100644 index 0000000..9994c5b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/constraint.py @@ -0,0 +1,297 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import role +from . import symbol +from . import objclass +from . import typeattr +from . import user + + +def _is_mls(policy, sym): + """Determine if this is a regular or MLS constraint/validatetrans.""" + # this can only be determined by inspecting the expression. + for expr_node in sym.expr_iter(policy): + sym_type = expr_node.sym_type(policy) + expr_type = expr_node.expr_type(policy) + + if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR and sym_type >= qpol.QPOL_CEXPR_SYM_L1L2: + return True + + return False + + +def validate_ruletype(types): + """Validate constraint rule types.""" + for t in types: + if t not in ["constrain", "mlsconstrain", "validatetrans", "mlsvalidatetrans"]: + raise exception.InvalidConstraintType("{0} is not a valid constraint type.".format(t)) + + +def constraint_factory(policy, sym): + """Factory function for creating constraint objects.""" + + try: + if _is_mls(policy, sym): + if isinstance(sym, qpol.qpol_constraint_t): + return Constraint(policy, sym, "mlsconstrain") + else: + return Validatetrans(policy, sym, "mlsvalidatetrans") + else: + if isinstance(sym, qpol.qpol_constraint_t): + return Constraint(policy, sym, "constrain") + else: + return Validatetrans(policy, sym, "validatetrans") + + except AttributeError: + raise TypeError("Constraints cannot be looked-up.") + + +class BaseConstraint(symbol.PolicySymbol): + + """Base class for constraint rules.""" + + _expr_type_to_text = { + qpol.QPOL_CEXPR_TYPE_NOT: "not", + qpol.QPOL_CEXPR_TYPE_AND: "and", + qpol.QPOL_CEXPR_TYPE_OR: "\n\tor"} + + _expr_op_to_text = { + qpol.QPOL_CEXPR_OP_EQ: "==", + qpol.QPOL_CEXPR_OP_NEQ: "!=", + qpol.QPOL_CEXPR_OP_DOM: "dom", + qpol.QPOL_CEXPR_OP_DOMBY: "domby", + qpol.QPOL_CEXPR_OP_INCOMP: "incomp"} + + _sym_to_text = { + qpol.QPOL_CEXPR_SYM_USER: "u1", + qpol.QPOL_CEXPR_SYM_ROLE: "r1", + qpol.QPOL_CEXPR_SYM_TYPE: "t1", + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET: "u2", + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET: "r2", + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET: "t2", + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_XTARGET: "u3", + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_XTARGET: "r3", + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_XTARGET: "t3", + qpol.QPOL_CEXPR_SYM_L1L2: "l1", + qpol.QPOL_CEXPR_SYM_L1H2: "l1", + qpol.QPOL_CEXPR_SYM_H1L2: "h1", + qpol.QPOL_CEXPR_SYM_H1H2: "h1", + qpol.QPOL_CEXPR_SYM_L1H1: "l1", + qpol.QPOL_CEXPR_SYM_L2H2: "l2", + qpol.QPOL_CEXPR_SYM_L1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2", + qpol.QPOL_CEXPR_SYM_L1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2", + qpol.QPOL_CEXPR_SYM_H1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2", + qpol.QPOL_CEXPR_SYM_H1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2", + qpol.QPOL_CEXPR_SYM_L1H1 + qpol.QPOL_CEXPR_SYM_TARGET: "h1", + qpol.QPOL_CEXPR_SYM_L2H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2"} + + # Boolean operators + _expr_type_to_precedence = { + qpol.QPOL_CEXPR_TYPE_NOT: 3, + qpol.QPOL_CEXPR_TYPE_AND: 2, + qpol.QPOL_CEXPR_TYPE_OR: 1} + + # Logical operators have the same precedence + _logical_op_precedence = 4 + + def __init__(self, policy, qpol_symbol, ruletype): + symbol.PolicySymbol.__init__(self, policy, qpol_symbol) + self.ruletype = ruletype + + def __str__(self): + raise NotImplementedError + + def _build_expression(self): + # qpol representation is in postfix notation. This code + # converts it to infix notation. Parentheses are added + # to ensure correct expressions, though they may end up + # being overused. Set previous operator at start to the + # highest precedence (op) so if there is a single binary + # operator, no parentheses are output + + stack = [] + prev_op_precedence = self._logical_op_precedence + for expr_node in self.qpol_symbol.expr_iter(self.policy): + op = expr_node.op(self.policy) + sym_type = expr_node.sym_type(self.policy) + expr_type = expr_node.expr_type(self.policy) + + if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR: + # logical operator with symbol (e.g. u1 == u2) + operand1 = self._sym_to_text[sym_type] + operand2 = self._sym_to_text[sym_type + qpol.QPOL_CEXPR_SYM_TARGET] + operator = self._expr_op_to_text[op] + + stack.append([operand1, operator, operand2]) + + prev_op_precedence = self._logical_op_precedence + elif expr_type == qpol.QPOL_CEXPR_TYPE_NAMES: + # logical operator with type or attribute list (e.g. t1 == { spam_t eggs_t }) + operand1 = self._sym_to_text[sym_type] + operator = self._expr_op_to_text[op] + + names = list(expr_node.names_iter(self.policy)) + + if not names: + operand2 = "" + elif len(names) == 1: + operand2 = names[0] + else: + operand2 = "{{ {0} }}".format(' '.join(names)) + + stack.append([operand1, operator, operand2]) + + prev_op_precedence = self._logical_op_precedence + elif expr_type == qpol.QPOL_CEXPR_TYPE_NOT: + # unary operator (not) + operand = stack.pop() + operator = self._expr_type_to_text[expr_type] + + stack.append([operator, "(", operand, ")"]) + + prev_op_precedence = self._expr_type_to_precedence[expr_type] + else: + # binary operator (and/or) + operand1 = stack.pop() + operand2 = stack.pop() + operator = self._expr_type_to_text[expr_type] + op_precedence = self._expr_type_to_precedence[expr_type] + + # if previous operator is of higher precedence + # no parentheses are needed. + if op_precedence < prev_op_precedence: + stack.append([operand1, operator, operand2]) + else: + stack.append(["(", operand1, operator, operand2, ")"]) + + prev_op_precedence = op_precedence + + return self.__unwind_subexpression(stack) + + def _get_symbols(self, syms, factory): + """ + Internal generator for getting users/roles/types in a constraint + expression. Symbols will be yielded multiple times if they appear + in the expression multiple times. + + Parameters: + syms List of qpol symbol types. + factory The factory function related to these symbols. + """ + for expr_node in self.qpol_symbol.expr_iter(self.policy): + sym_type = expr_node.sym_type(self.policy) + expr_type = expr_node.expr_type(self.policy) + + if expr_type == qpol.QPOL_CEXPR_TYPE_NAMES and sym_type in syms: + for s in expr_node.names_iter(self.policy): + yield factory(self.policy, s) + + def __unwind_subexpression(self, expr): + ret = [] + + # do a string.join on sublists (subexpressions) + for i in expr: + if isinstance(i, list): + ret.append(self.__unwind_subexpression(i)) + else: + ret.append(i) + + return ' '.join(ret) + + # There is no levels function as specific + # levels cannot be used in expressions, only + # the l1, h1, etc. symbols + + @property + def roles(self): + """The roles used in the expression.""" + role_syms = [qpol.QPOL_CEXPR_SYM_ROLE, + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET, + qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_XTARGET] + + return set(self._get_symbols(role_syms, role.role_factory)) + + @property + def perms(self): + raise NotImplementedError + + def statement(self): + return str(self) + + @property + def tclass(self): + """Object class for this constraint.""" + return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) + + @property + def types(self): + """The types and type attributes used in the expression.""" + type_syms = [qpol.QPOL_CEXPR_SYM_TYPE, + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET, + qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_XTARGET] + + return set(self._get_symbols(type_syms, typeattr.type_or_attr_factory)) + + @property + def users(self): + """The users used in the expression.""" + user_syms = [qpol.QPOL_CEXPR_SYM_USER, + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET, + qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_XTARGET] + + return set(self._get_symbols(user_syms, user.user_factory)) + + +class Constraint(BaseConstraint): + + """A constraint rule (constrain/mlsconstrain).""" + + def __str__(self): + rule_string = "{0.ruletype} {0.tclass} ".format(self) + + perms = self.perms + if len(perms) > 1: + rule_string += "{{ {0} }} (\n".format(' '.join(perms)) + else: + # convert to list since sets cannot be indexed + rule_string += "{0} (\n".format(list(perms)[0]) + + rule_string += "\t{0}\n);".format(self._build_expression()) + + return rule_string + + @property + def perms(self): + """The constraint's permission set.""" + return set(self.qpol_symbol.perm_iter(self.policy)) + + +class Validatetrans(BaseConstraint): + + """A validatetrans rule (validatetrans/mlsvalidatetrans).""" + + def __str__(self): + return "{0.ruletype} {0.tclass}\n\t{1}\n);".format(self, self._build_expression()) + + @property + def perms(self): + raise exception.ConstraintUseError("{0} rules do not have permissions.". + format(self.ruletype)) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/context.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/context.py new file mode 100644 index 0000000..f2f3fc7 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/context.py @@ -0,0 +1,68 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol +from . import user +from . import role +from . import typeattr +from . import mls + + +def context_factory(policy, name): + """Factory function for creating context objects.""" + + if not isinstance(name, qpol.qpol_context_t): + raise TypeError("Contexts cannot be looked-up.") + + return Context(policy, name) + + +class Context(symbol.PolicySymbol): + + """A SELinux security context/security attribute.""" + + def __str__(self): + try: + return "{0.user}:{0.role}:{0.type_}:{0.range_}".format(self) + except exception.MLSDisabled: + return "{0.user}:{0.role}:{0.type_}".format(self) + + @property + def user(self): + """The user portion of the context.""" + return user.user_factory(self.policy, self.qpol_symbol.user(self.policy)) + + @property + def role(self): + """The role portion of the context.""" + return role.role_factory(self.policy, self.qpol_symbol.role(self.policy)) + + @property + def type_(self): + """The type portion of the context.""" + return typeattr.type_factory(self.policy, self.qpol_symbol.type_(self.policy)) + + @property + def range_(self): + """The MLS range of the context.""" + return mls.range_factory(self.policy, self.qpol_symbol.range(self.policy)) + + def statement(self): + raise exception.NoStatement diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/default.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/default.py new file mode 100644 index 0000000..175b709 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/default.py @@ -0,0 +1,128 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import symbol +from . import objclass +from . import qpol + + +def default_factory(policy, sym): + """Factory generator for creating default_* statement objects.""" + + # The low level policy groups default_* settings by object class. + # Since each class can have up to four default_* statements, + # this factory function is a generator which yields up to + # four Default objects. + + if not isinstance(sym, qpol.qpol_default_object_t): + raise NotImplementedError + + # qpol will essentially iterate over all classes + # and emit None for classes that don't set a default + if not sym.object_class(policy): + raise exception.NoDefaults + + if sym.user_default(policy): + yield UserDefault(policy, sym) + + if sym.role_default(policy): + yield RoleDefault(policy, sym) + + if sym.type_default(policy): + yield TypeDefault(policy, sym) + + if sym.range_default(policy): + yield RangeDefault(policy, sym) + + +class Default(symbol.PolicySymbol): + + """Base class for default_* statements.""" + + def __str__(self): + raise NotImplementedError + + @property + def object_class(self): + """The object class.""" + return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) + + @property + def default(self): + raise NotImplementedError + + def statement(self): + return str(self) + + +class UserDefault(Default): + + """A default_user statement.""" + + def __str__(self): + return "default_user {0.object_class} {0.default};".format(self) + + @property + def default(self): + """The default user location (source/target).""" + return self.qpol_symbol.user_default(self.policy) + + +class RoleDefault(Default): + + """A default_role statement.""" + + def __str__(self): + return "default_role {0.object_class} {0.default};".format(self) + + @property + def default(self): + """The default role location (source/target).""" + return self.qpol_symbol.role_default(self.policy) + + +class TypeDefault(Default): + + """A default_type statement.""" + + def __str__(self): + return "default_type {0.object_class} {0.default};".format(self) + + @property + def default(self): + """The default type location (source/target).""" + return self.qpol_symbol.type_default(self.policy) + + +class RangeDefault(Default): + + """A default_range statement.""" + + def __str__(self): + return "default_range {0.object_class} {0.default} {0.default_range};".format(self) + + @property + def default(self): + """The default range location (source/target).""" + return self.qpol_symbol.range_default(self.policy).split()[0] + + @property + def default_range(self): + """The default range setting (low/high/low_high).""" + return self.qpol_symbol.range_default(self.policy).split()[1] diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/exception.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/exception.py new file mode 100644 index 0000000..ce367c0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/exception.py @@ -0,0 +1,248 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from ..exception import SEToolsException + +# +# Policyrep base exception +# + + +class PolicyrepException(SEToolsException): + + """Base class for all policyrep exceptions.""" + pass + + +# +# General Policyrep exceptions +# + + +class InvalidPolicy(SyntaxError, PolicyrepException): + + """Exception for invalid policy.""" + pass + + +class MLSDisabled(PolicyrepException): + + """ + Exception when MLS is disabled. + """ + pass + + +# +# Invalid component exceptions +# +class InvalidSymbol(ValueError, PolicyrepException): + + """ + Base class for invalid symbols. Typically this is attempting to + look up an object in the policy, but it does not exist. + """ + pass + + +class InvalidBoolean(InvalidSymbol): + + """Exception for invalid Booleans.""" + pass + + +class InvalidCategory(InvalidSymbol): + + """Exception for invalid MLS categories.""" + pass + + +class InvalidClass(InvalidSymbol): + + """Exception for invalid object classes.""" + pass + + +class InvalidCommon(InvalidSymbol): + + """Exception for invalid common permission sets.""" + pass + + +class InvalidInitialSid(InvalidSymbol): + + """Exception for invalid initial sids.""" + pass + + +class InvalidLevel(InvalidSymbol): + + """ + Exception for an invalid level. + """ + pass + + +class InvalidLevelDecl(InvalidSymbol): + + """ + Exception for an invalid level declaration. + """ + pass + + +class InvalidRange(InvalidSymbol): + + """ + Exception for an invalid range. + """ + pass + + +class InvalidRole(InvalidSymbol): + + """Exception for invalid roles.""" + pass + + +class InvalidSensitivity(InvalidSymbol): + + """ + Exception for an invalid sensitivity. + """ + pass + + +class InvalidType(InvalidSymbol): + + """Exception for invalid types and attributes.""" + pass + + +class InvalidUser(InvalidSymbol): + + """Exception for invalid users.""" + pass + +# +# Rule type exceptions +# + + +class InvalidRuleType(InvalidSymbol): + + """Exception for invalid rule types.""" + pass + + +class InvalidConstraintType(InvalidSymbol): + + """Exception for invalid constraint types.""" + # This is not a rule but is similar. + pass + + +class InvalidMLSRuleType(InvalidRuleType): + + """Exception for invalid MLS rule types.""" + pass + + +class InvalidRBACRuleType(InvalidRuleType): + + """Exception for invalid RBAC rule types.""" + pass + + +class InvalidTERuleType(InvalidRuleType): + + """Exception for invalid TE rule types.""" + pass + + +# +# Object use errors +# +class SymbolUseError(PolicyrepException): + + """ + Base class for incorrectly using an object. Typically this is + for classes with strong similarities, but with slight variances in + functionality, e.g. allow vs type_transition rules. + """ + pass + + +class RuleUseError(SymbolUseError): + + """ + Base class for incorrect parameters for a rule. For + example, trying to get the permissions of a rule that has no + permissions. + """ + pass + + +class ConstraintUseError(SymbolUseError): + + """Exception when getting permissions from a validatetrans.""" + pass + + +class NoStatement(SymbolUseError): + + """ + Exception for objects that have no inherent statement, such + as conditional expressions and MLS ranges. + """ + pass + + +# +# Other exceptions +# +class NoCommon(PolicyrepException): + + """ + Exception when a class does not inherit a common permission set. + """ + pass + + +class NoDefaults(InvalidSymbol): + + """Exception for classes that have no default_* statements.""" + pass + + +class RuleNotConditional(PolicyrepException): + + """ + Exception when getting the conditional expression for rules + that are unconditional (not conditional). + """ + pass + + +class TERuleNoFilename(PolicyrepException): + + """ + Exception when getting the file name of a + type_transition rule that has no file name. + """ + pass diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/fscontext.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/fscontext.py new file mode 100644 index 0000000..a17b0bc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/fscontext.py @@ -0,0 +1,123 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import stat + +from . import qpol +from . import symbol +from . import context + + +def fs_use_factory(policy, name): + """Factory function for creating fs_use_* objects.""" + + if not isinstance(name, qpol.qpol_fs_use_t): + raise TypeError("fs_use_* cannot be looked-up.") + + return FSUse(policy, name) + + +def genfscon_factory(policy, name): + """Factory function for creating genfscon objects.""" + + if not isinstance(name, qpol.qpol_genfscon_t): + raise TypeError("Genfscons cannot be looked-up.") + + return Genfscon(policy, name) + + +class FSContext(symbol.PolicySymbol): + + """Base class for in-policy labeling rules.""" + + def __str__(self): + raise NotImplementedError + + @property + def fs(self): + """The filesystem type for this statement.""" + return self.qpol_symbol.name(self.policy) + + @property + def context(self): + """The context for this statement.""" + return context.context_factory(self.policy, self.qpol_symbol.context(self.policy)) + + def statement(self): + return str(self) + + +class Genfscon(FSContext): + + """A genfscon statement.""" + + _filetype_to_text = { + 0: "", + stat.S_IFBLK: "-b", + stat.S_IFCHR: "-c", + stat.S_IFDIR: "-d", + stat.S_IFIFO: "-p", + stat.S_IFREG: "--", + stat.S_IFLNK: "-l", + stat.S_IFSOCK: "-s"} + + def __str__(self): + return "genfscon {0.fs} {0.path} {1} {0.context}".format( + self, self._filetype_to_text[self.filetype]) + + def __eq__(self, other): + # Libqpol allocates new C objects in the + # genfscons iterator, so pointer comparison + # in the PolicySymbol object doesn't work. + try: + return (self.fs == other.fs and + self.path == other.path and + self.filetype == other.filetype and + self.context == other.context) + except AttributeError: + return str(self) == str(other) + + @property + def filetype(self): + """The file type (e.g. stat.S_IFBLK) for this genfscon statement.""" + return self.qpol_symbol.object_class(self.policy) + + @property + def path(self): + """The path for this genfscon statement.""" + return self.qpol_symbol.path(self.policy) + + +class FSUse(FSContext): + + """A fs_use_* statement.""" + + # there are more rule types, but modern SELinux + # only supports these three. + _ruletype_to_text = { + qpol.QPOL_FS_USE_XATTR: 'fs_use_xattr', + qpol.QPOL_FS_USE_TRANS: 'fs_use_trans', + qpol.QPOL_FS_USE_TASK: 'fs_use_task'} + + def __str__(self): + return "{0.ruletype} {0.fs} {0.context};".format(self) + + @property + def ruletype(self): + """The rule type for this fs_use_* statement.""" + return self._ruletype_to_text[self.qpol_symbol.behavior(self.policy)] diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/initsid.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/initsid.py new file mode 100644 index 0000000..0197c74 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/initsid.py @@ -0,0 +1,50 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol +from . import context + + +def initialsid_factory(policy, name): + """Factory function for creating initial sid objects.""" + + if isinstance(name, InitialSID): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_isid_t): + return InitialSID(policy, name) + + try: + return InitialSID(policy, qpol.qpol_isid_t(policy, name)) + except ValueError: + raise exception.InvalidInitialSid("{0} is not a valid initial sid".format(name)) + + +class InitialSID(symbol.PolicySymbol): + + """An initial SID statement.""" + + @property + def context(self): + """The context for this initial SID.""" + return context.context_factory(self.policy, self.qpol_symbol.context(self.policy)) + + def statement(self): + return "sid {0} {0.context}".format(self) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/mls.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/mls.py new file mode 100644 index 0000000..2541704 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/mls.py @@ -0,0 +1,463 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +# pylint: disable=protected-access +import itertools + +from . import exception +from . import qpol +from . import symbol + +# qpol does not expose an equivalent of a sensitivity declaration. +# qpol_level_t is equivalent to the level declaration: +# level s0:c0.c1023; + +# qpol_mls_level_t represents a level as used in contexts, +# such as range_transitions or labeling statements such as +# portcon and nodecon. + +# Here qpol_level_t is also used for MLSSensitivity +# since it has the sensitivity name, dominance, and there +# is a 1:1 correspondence between the sensitivity declarations +# and level declarations. + +# Hashing has to be handled below because the qpol references, +# normally used for a hash key, are not the same for multiple +# instances of the same object (except for level decl). + + +def enabled(policy): + """Determine if MLS is enabled.""" + return policy.capability(qpol.QPOL_CAP_MLS) + + +def category_factory(policy, sym): + """Factory function for creating MLS category objects.""" + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Category): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_cat_t): + if sym.isalias(policy): + raise TypeError("{0} is an alias".format(sym.name(policy))) + + return Category(policy, sym) + + try: + return Category(policy, qpol.qpol_cat_t(policy, str(sym))) + except ValueError: + raise exception.InvalidCategory("{0} is not a valid category".format(sym)) + + +def sensitivity_factory(policy, sym): + """Factory function for creating MLS sensitivity objects.""" + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Sensitivity): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_level_t): + if sym.isalias(policy): + raise TypeError("{0} is an alias".format(sym.name(policy))) + + return Sensitivity(policy, sym) + + try: + return Sensitivity(policy, qpol.qpol_level_t(policy, str(sym))) + except ValueError: + raise exception.InvalidSensitivity("{0} is not a valid sensitivity".format(sym)) + + +def level_factory(policy, sym): + """ + Factory function for creating MLS level objects (e.g. levels used + in contexts of labeling statements) + """ + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Level): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_mls_level_t): + return Level(policy, sym) + + sens_split = str(sym).split(":") + + sens = sens_split[0] + try: + semantic_level = qpol.qpol_semantic_level_t(policy, sens) + except ValueError: + raise exception.InvalidLevel("{0} is invalid ({1} is not a valid sensitivity)". + format(sym, sens)) + + try: + cats = sens_split[1] + except IndexError: + pass + else: + for group in cats.split(","): + catrange = group.split(".") + + if len(catrange) == 2: + try: + semantic_level.add_cats(policy, catrange[0], catrange[1]) + except ValueError: + raise exception.InvalidLevel( + "{0} is invalid ({1} is not a valid category range)".format(sym, group)) + elif len(catrange) == 1: + try: + semantic_level.add_cats(policy, catrange[0], catrange[0]) + except ValueError: + raise exception.InvalidLevel("{0} is invalid ({1} is not a valid category)". + format(sym, group)) + else: + raise exception.InvalidLevel("{0} is invalid (level parsing error)".format(sym)) + + # convert to level object + try: + policy_level = qpol.qpol_mls_level_t(policy, semantic_level) + except ValueError: + raise exception.InvalidLevel( + "{0} is invalid (one or more categories are not associated with the sensitivity)". + format(sym)) + + return Level(policy, policy_level) + + +def level_decl_factory(policy, sym): + """ + Factory function for creating MLS level declaration objects. + (level statements) Lookups are only by sensitivity name. + """ + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, LevelDecl): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_level_t): + if sym.isalias(policy): + raise TypeError("{0} is an alias".format(sym.name(policy))) + + return LevelDecl(policy, sym) + + try: + return LevelDecl(policy, qpol.qpol_level_t(policy, str(sym))) + except ValueError: + raise exception.InvalidLevelDecl("{0} is not a valid sensitivity".format(sym)) + + +def range_factory(policy, sym): + """Factory function for creating MLS range objects.""" + + if not enabled(policy): + raise exception.MLSDisabled + + if isinstance(sym, Range): + assert sym.policy == policy + return sym + elif isinstance(sym, qpol.qpol_mls_range_t): + return Range(policy, sym) + + # build range: + levels = str(sym).split("-") + + # strip() levels to handle ranges with spaces in them, + # e.g. s0:c1 - s0:c0.c255 + try: + low = level_factory(policy, levels[0].strip()) + except exception.InvalidLevel as ex: + raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) + + try: + high = level_factory(policy, levels[1].strip()) + except exception.InvalidLevel as ex: + raise exception.InvalidRange("{0} is not a valid range ({1}).".format(sym, ex)) + except IndexError: + high = low + + # convert to range object + try: + policy_range = qpol.qpol_mls_range_t(policy, low.qpol_symbol, high.qpol_symbol) + except ValueError: + raise exception.InvalidRange("{0} is not a valid range ({1} is not dominated by {2})". + format(sym, low, high)) + + return Range(policy, policy_range) + + +class BaseMLSComponent(symbol.PolicySymbol): + + """Base class for sensitivities and categories.""" + + @property + def _value(self): + """ + The value of the component. + + This is a low-level policy detail exposed for internal use only. + """ + return self.qpol_symbol.value(self.policy) + + def aliases(self): + """Generator that yields all aliases for this category.""" + + for alias in self.qpol_symbol.alias_iter(self.policy): + yield alias + + +class Category(BaseMLSComponent): + + """An MLS category.""" + + def statement(self): + aliases = list(self.aliases()) + stmt = "category {0}".format(self) + if aliases: + if len(aliases) > 1: + stmt += " alias {{ {0} }}".format(' '.join(aliases)) + else: + stmt += " alias {0}".format(aliases[0]) + stmt += ";" + return stmt + + +class Sensitivity(BaseMLSComponent): + + """An MLS sensitivity""" + + def __eq__(self, other): + try: + return self._value == other._value + except AttributeError: + return str(self) == str(other) + + def __ge__(self, other): + return self._value >= other._value + + def __gt__(self, other): + return self._value > other._value + + def __le__(self, other): + return self._value <= other._value + + def __lt__(self, other): + return self._value < other._value + + def statement(self): + aliases = list(self.aliases()) + stmt = "sensitivity {0}".format(self) + if aliases: + if len(aliases) > 1: + stmt += " alias {{ {0} }}".format(' '.join(aliases)) + else: + stmt += " alias {0}".format(aliases[0]) + stmt += ";" + return stmt + + +class BaseMLSLevel(symbol.PolicySymbol): + + """Base class for MLS levels.""" + + def __str__(self): + lvl = str(self.sensitivity) + + # sort by policy declaration order + cats = sorted(self.categories(), key=lambda k: k._value) + + if cats: + # generate short category notation + shortlist = [] + for _, i in itertools.groupby(cats, key=lambda k, + c=itertools.count(): k._value - next(c)): + group = list(i) + if len(group) > 1: + shortlist.append("{0}.{1}".format(group[0], group[-1])) + else: + shortlist.append(str(group[0])) + + lvl += ":" + ','.join(shortlist) + + return lvl + + @property + def sensitivity(self): + raise NotImplementedError + + def categories(self): + """ + Generator that yields all individual categories for this level. + All categories are yielded, not a compact notation such as + c0.c255 + """ + + for cat in self.qpol_symbol.cat_iter(self.policy): + yield category_factory(self.policy, cat) + + +class LevelDecl(BaseMLSLevel): + + """ + The declaration statement for MLS levels, e.g: + + level s7:c0.c1023; + """ + # below comparisons are only based on sensitivity + # dominance since, in this context, the allowable + # category set is being defined for the level. + # object type is asserted here because this cannot + # be compared to a Level instance. + + def __eq__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + + try: + return self.sensitivity == other.sensitivity + except AttributeError: + return str(self) == str(other) + + def __ge__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity >= other.sensitivity + + def __gt__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity > other.sensitivity + + def __le__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity <= other.sensitivity + + def __lt__(self, other): + assert not isinstance(other, Level), "Levels cannot be compared to level declarations" + return self.sensitivity < other.sensitivity + + @property + def sensitivity(self): + """The sensitivity of the level.""" + # since the qpol symbol for levels is also used for + # MLSSensitivity objects, use self's qpol symbol + return sensitivity_factory(self.policy, self.qpol_symbol) + + def statement(self): + return "level {0};".format(self) + + +class Level(BaseMLSLevel): + + """An MLS level used in contexts.""" + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + try: + othercats = set(other.categories()) + except AttributeError: + return str(self) == str(other) + else: + selfcats = set(self.categories()) + return self.sensitivity == other.sensitivity and selfcats == othercats + + def __ge__(self, other): + """Dom operator.""" + selfcats = set(self.categories()) + othercats = set(other.categories()) + return self.sensitivity >= other.sensitivity and selfcats >= othercats + + def __gt__(self, other): + selfcats = set(self.categories()) + othercats = set(other.categories()) + return ((self.sensitivity > other.sensitivity and selfcats >= othercats) or + (self.sensitivity >= other.sensitivity and selfcats > othercats)) + + def __le__(self, other): + """Domby operator.""" + selfcats = set(self.categories()) + othercats = set(other.categories()) + return self.sensitivity <= other.sensitivity and selfcats <= othercats + + def __lt__(self, other): + selfcats = set(self.categories()) + othercats = set(other.categories()) + return ((self.sensitivity < other.sensitivity and selfcats <= othercats) or + (self.sensitivity <= other.sensitivity and selfcats < othercats)) + + def __xor__(self, other): + """Incomp operator.""" + return not (self >= other or self <= other) + + @property + def sensitivity(self): + """The sensitivity of the level.""" + return sensitivity_factory(self.policy, self.qpol_symbol.sens_name(self.policy)) + + def statement(self): + raise exception.NoStatement + + +class Range(symbol.PolicySymbol): + + """An MLS range""" + + def __str__(self): + high = self.high + low = self.low + if high == low: + return str(low) + + return "{0} - {1}".format(low, high) + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + try: + return self.low == other.low and self.high == other.high + except AttributeError: + # remove all spaces in the string representations + # to handle cases where the other object does not + # have spaces around the '-' + other_str = str(other).replace(" ", "") + self_str = str(self).replace(" ", "") + return self_str == other_str + + def __contains__(self, other): + return self.low <= other <= self.high + + @property + def high(self): + """The high end/clearance level of this range.""" + return level_factory(self.policy, self.qpol_symbol.high_level(self.policy)) + + @property + def low(self): + """The low end/current level of this range.""" + return level_factory(self.policy, self.qpol_symbol.low_level(self.policy)) + + def statement(self): + raise exception.NoStatement diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/mlsrule.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/mlsrule.py new file mode 100644 index 0000000..5c91c59 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/mlsrule.py @@ -0,0 +1,62 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import rule +from . import typeattr +from . import mls + + +def mls_rule_factory(policy, symbol): + """Factory function for creating MLS rule objects.""" + if not isinstance(symbol, qpol.qpol_range_trans_t): + raise TypeError("MLS rules cannot be looked-up.") + + return MLSRule(policy, symbol) + + +def validate_ruletype(types): + """Validate MLS rule types.""" + for t in types: + if t not in ["range_transition"]: + raise exception.InvalidMLSRuleType("{0} is not a valid MLS rule type.".format(t)) + + +class MLSRule(rule.PolicyRule): + + """An MLS rule.""" + + def __str__(self): + # TODO: If we ever get more MLS rules, fix this format. + return "range_transition {0.source} {0.target}:{0.tclass} {0.default};".format(self) + + @property + def source(self): + """The rule's source type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.source_type(self.policy)) + + @property + def target(self): + """The rule's target type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.target_type(self.policy)) + + @property + def default(self): + """The rule's default range.""" + return mls.range_factory(self.policy, self.qpol_symbol.range(self.policy)) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/netcontext.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/netcontext.py new file mode 100644 index 0000000..5aeed5c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/netcontext.py @@ -0,0 +1,167 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import socket +from collections import namedtuple + +from . import qpol +from . import symbol +from . import context + +port_range = namedtuple("port_range", ["low", "high"]) + + +def netifcon_factory(policy, name): + """Factory function for creating netifcon objects.""" + + if not isinstance(name, qpol.qpol_netifcon_t): + raise NotImplementedError + + return Netifcon(policy, name) + + +def nodecon_factory(policy, name): + """Factory function for creating nodecon objects.""" + + if not isinstance(name, qpol.qpol_nodecon_t): + raise NotImplementedError + + return Nodecon(policy, name) + + +def portcon_factory(policy, name): + """Factory function for creating portcon objects.""" + + if not isinstance(name, qpol.qpol_portcon_t): + raise NotImplementedError + + return Portcon(policy, name) + + +class NetContext(symbol.PolicySymbol): + + """Base class for in-policy network labeling rules.""" + + def __str__(self): + raise NotImplementedError + + @property + def context(self): + """The context for this statement.""" + return context.context_factory(self.policy, self.qpol_symbol.context(self.policy)) + + def statement(self): + return str(self) + + +class Netifcon(NetContext): + + """A netifcon statement.""" + + def __str__(self): + return "netifcon {0.netif} {0.context} {0.packet}".format(self) + + @property + def netif(self): + """The network interface name.""" + return self.qpol_symbol.name(self.policy) + + @property + def context(self): + """The context for the interface.""" + return context.context_factory(self.policy, self.qpol_symbol.if_con(self.policy)) + + @property + def packet(self): + """The context for the packets.""" + return context.context_factory(self.policy, self.qpol_symbol.msg_con(self.policy)) + + +class Nodecon(NetContext): + + """A nodecon statement.""" + + def __str__(self): + return "nodecon {0.address} {0.netmask} {0.context}".format(self) + + def __eq__(self, other): + # Libqpol allocates new C objects in the + # nodecons iterator, so pointer comparison + # in the PolicySymbol object doesn't work. + try: + return (self.address == other.address and + self.netmask == other.netmask and + self.context == other.context) + except AttributeError: + return (str(self) == str(other)) + + @property + def ip_version(self): + """ + The IP version for the nodecon (socket.AF_INET or + socket.AF_INET6). + """ + return self.qpol_symbol.protocol(self.policy) + + @property + def address(self): + """The network address for the nodecon.""" + return self.qpol_symbol.addr(self.policy) + + @property + def netmask(self): + """The network mask for the nodecon.""" + return self.qpol_symbol.mask(self.policy) + + +class Portcon(NetContext): + + """A portcon statement.""" + + _proto_to_text = {socket.IPPROTO_TCP: 'tcp', + socket.IPPROTO_UDP: 'udp'} + + def __str__(self): + low, high = self.ports + proto = self._proto_to_text[self.protocol] + + if low == high: + return "portcon {0} {1} {2}".format(proto, low, self.context) + else: + return "portcon {0} {1}-{2} {3}".format(proto, low, high, self.context) + + @property + def protocol(self): + """ + The protocol number for the portcon (socket.IPPROTO_TCP + or socket.IPPROTO_UDP). + """ + return self.qpol_symbol.protocol(self.policy) + + @property + def ports(self): + """ + The port range for this portcon. + + Return: Tuple(low, high) + low The low port of the range. + high The high port of the range. + """ + low = self.qpol_symbol.low_port(self.policy) + high = self.qpol_symbol.high_port(self.policy) + return port_range(low, high) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/objclass.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/objclass.py new file mode 100644 index 0000000..bf9a553 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/objclass.py @@ -0,0 +1,110 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import symbol +from . import qpol + + +def common_factory(policy, name): + """Factory function for creating common permission set objects.""" + + if isinstance(name, Common): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_common_t): + return Common(policy, name) + + try: + return Common(policy, qpol.qpol_common_t(policy, str(name))) + except ValueError: + raise exception.InvalidCommon("{0} is not a valid common".format(name)) + + +def class_factory(policy, name): + """Factory function for creating object class objects.""" + + if isinstance(name, ObjClass): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_class_t): + return ObjClass(policy, name) + + try: + return ObjClass(policy, qpol.qpol_class_t(policy, str(name))) + except ValueError: + raise exception.InvalidClass("{0} is not a valid object class".format(name)) + + +class Common(symbol.PolicySymbol): + + """A common permission set.""" + + def __contains__(self, other): + return other in self.perms + + @property + def perms(self): + """The list of the common's permissions.""" + return set(self.qpol_symbol.perm_iter(self.policy)) + + def statement(self): + return "common {0}\n{{\n\t{1}\n}}".format(self, '\n\t'.join(self.perms)) + + +class ObjClass(Common): + + """An object class.""" + + def __contains__(self, other): + try: + if other in self.common.perms: + return True + except exception.NoCommon: + pass + + return other in self.perms + + @property + def common(self): + """ + The common that the object class inherits. + + Exceptions: + NoCommon The object class does not inherit a common. + """ + + try: + return common_factory(self.policy, self.qpol_symbol.common(self.policy)) + except ValueError: + raise exception.NoCommon("{0} does not inherit a common.".format(self)) + + def statement(self): + stmt = "class {0}\n".format(self) + + try: + stmt += "inherits {0}\n".format(self.common) + except exception.NoCommon: + pass + + # a class that inherits may not have additional permissions + perms = self.perms + if len(perms) > 0: + stmt += "{{\n\t{0}\n}}".format('\n\t'.join(perms)) + + return stmt diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/polcap.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/polcap.py new file mode 100644 index 0000000..8ab164d --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/polcap.py @@ -0,0 +1,40 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import qpol +from . import symbol + + +def polcap_factory(policy, name): + """Factory function for creating policy capability objects.""" + + if isinstance(name, PolicyCapability): + assert name.policy == policy + return name + elif isinstance(name, qpol.qpol_polcap_t): + return PolicyCapability(policy, name) + else: + raise TypeError("Policy capabilities cannot be looked up.") + + +class PolicyCapability(symbol.PolicySymbol): + + """A policy capability.""" + + def statement(self): + return "policycap {0};".format(self) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/qpol.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/qpol.py new file mode 100644 index 0000000..97e602b --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/qpol.py @@ -0,0 +1,1114 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 2.0.11 +# +# Do not make changes to this file unless you know what you are doing--modify +# the SWIG interface file instead. + + + + + +from sys import version_info +if version_info >= (2,6,0): + def swig_import_helper(): + from os.path import dirname + import imp + fp = None + try: + fp, pathname, description = imp.find_module('_qpol', [dirname(__file__)]) + except ImportError: + import _qpol + return _qpol + if fp is not None: + try: + _mod = imp.load_module('_qpol', fp, pathname, description) + finally: + fp.close() + return _mod + _qpol = swig_import_helper() + del swig_import_helper +else: + import _qpol +del version_info +try: + _swig_property = property +except NameError: + pass # Python < 2.2 doesn't have 'property'. +def _swig_setattr_nondynamic(self,class_type,name,value,static=1): + if (name == "thisown"): return self.this.own(value) + if (name == "this"): + if type(value).__name__ == 'SwigPyObject': + self.__dict__[name] = value + return + method = class_type.__swig_setmethods__.get(name,None) + if method: return method(self,value) + if (not static): + self.__dict__[name] = value + else: + raise AttributeError("You cannot add attributes to %s" % self) + +def _swig_setattr(self,class_type,name,value): + return _swig_setattr_nondynamic(self,class_type,name,value,0) + +def _swig_getattr(self,class_type,name): + if (name == "thisown"): return self.this.own() + method = class_type.__swig_getmethods__.get(name,None) + if method: return method(self) + raise AttributeError(name) + +def _swig_repr(self): + try: strthis = "proxy of " + self.this.__repr__() + except: strthis = "" + return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) + +try: + _object = object + _newclass = 1 +except AttributeError: + class _object : pass + _newclass = 0 + + + +def to_str(*args): + return _qpol.to_str(*args) +to_str = _qpol.to_str +import logging +from functools import wraps + +def QpolGenerator(cast): + """ + A decorator which converts qpol iterators into Python generators. + + Qpol iterators use void* to be generic about their contents. + The purpose of the _from_void functions below is to wrap + the pointer casting, hence the "cast" variable name here. + + Decorator parameter: + cast A wrapper function which casts the qpol iterator return pointer + to the proper C data type pointer. The Python function + reference to the C Python extension is used, for example: + + @QpolGenerator(_qpol.qpol_type_from_void) + """ + + def decorate(func): + @wraps(func) + def wrapper(*args, **kwargs): + qpol_iter = func(*args) + while not qpol_iter.isend(): + yield cast(qpol_iter.item()) + qpol_iter.next_() + + return wrapper + return decorate + +def qpol_logger(level, msg): + """Log qpol messages via Python logging.""" + logging.getLogger("libqpol").debug(msg) + +def qpol_policy_factory(path): + """Factory function for qpol policy objects.""" + # The main purpose here is to hook in the + # above logger callback. + return qpol_policy_t(path, 0, qpol_logger) + +QPOL_POLICY_OPTION_NO_NEVERALLOWS = _qpol.QPOL_POLICY_OPTION_NO_NEVERALLOWS +QPOL_POLICY_OPTION_NO_RULES = _qpol.QPOL_POLICY_OPTION_NO_RULES +QPOL_POLICY_OPTION_MATCH_SYSTEM = _qpol.QPOL_POLICY_OPTION_MATCH_SYSTEM +QPOL_POLICY_MAX_VERSION = _qpol.QPOL_POLICY_MAX_VERSION +QPOL_POLICY_MIN_VERSION = _qpol.QPOL_POLICY_MIN_VERSION +class qpol_policy_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_policy_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_policy_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_policy_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_policy_t + __del__ = lambda self : None; + def version(self): return _qpol.qpol_policy_t_version(self) + def handle_unknown(self): return _qpol.qpol_policy_t_handle_unknown(self) + def capability(self, *args): return _qpol.qpol_policy_t_capability(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def type_iter(self): return _qpol.qpol_policy_t_type_iter(self) + def type_count(self): return _qpol.qpol_policy_t_type_count(self) + @QpolGenerator(_qpol.qpol_role_from_void) + def role_iter(self): return _qpol.qpol_policy_t_role_iter(self) + def role_count(self): return _qpol.qpol_policy_t_role_count(self) + @QpolGenerator(_qpol.qpol_level_from_void) + def level_iter(self): return _qpol.qpol_policy_t_level_iter(self) + def level_count(self): return _qpol.qpol_policy_t_level_count(self) + @QpolGenerator(_qpol.qpol_cat_from_void) + def cat_iter(self): return _qpol.qpol_policy_t_cat_iter(self) + def cat_count(self): return _qpol.qpol_policy_t_cat_count(self) + @QpolGenerator(_qpol.qpol_user_from_void) + def user_iter(self): return _qpol.qpol_policy_t_user_iter(self) + def user_count(self): return _qpol.qpol_policy_t_user_count(self) + @QpolGenerator(_qpol.qpol_bool_from_void) + def bool_iter(self): return _qpol.qpol_policy_t_bool_iter(self) + def bool_count(self): return _qpol.qpol_policy_t_bool_count(self) + @QpolGenerator(_qpol.qpol_class_from_void) + def class_iter(self, perm=None): return _qpol.qpol_policy_t_class_iter(self, perm) + def class_count(self): return _qpol.qpol_policy_t_class_count(self) + @QpolGenerator(_qpol.qpol_common_from_void) + def common_iter(self, perm=None): return _qpol.qpol_policy_t_common_iter(self, perm) + def common_count(self): return _qpol.qpol_policy_t_common_count(self) + @QpolGenerator(_qpol.qpol_fs_use_from_void) + def fs_use_iter(self): return _qpol.qpol_policy_t_fs_use_iter(self) + def fs_use_count(self): return _qpol.qpol_policy_t_fs_use_count(self) + @QpolGenerator(_qpol.qpol_genfscon_from_void) + def genfscon_iter(self): return _qpol.qpol_policy_t_genfscon_iter(self) + def genfscon_count(self): return _qpol.qpol_policy_t_genfscon_count(self) + @QpolGenerator(_qpol.qpol_isid_from_void) + def isid_iter(self): return _qpol.qpol_policy_t_isid_iter(self) + def isid_count(self): return _qpol.qpol_policy_t_isid_count(self) + @QpolGenerator(_qpol.qpol_netifcon_from_void) + def netifcon_iter(self): return _qpol.qpol_policy_t_netifcon_iter(self) + def netifcon_count(self): return _qpol.qpol_policy_t_netifcon_count(self) + @QpolGenerator(_qpol.qpol_nodecon_from_void) + def nodecon_iter(self): return _qpol.qpol_policy_t_nodecon_iter(self) + def nodecon_count(self): return _qpol.qpol_policy_t_nodecon_count(self) + @QpolGenerator(_qpol.qpol_portcon_from_void) + def portcon_iter(self): return _qpol.qpol_policy_t_portcon_iter(self) + def portcon_count(self): return _qpol.qpol_policy_t_portcon_count(self) + @QpolGenerator(_qpol.qpol_constraint_from_void) + def constraint_iter(self): return _qpol.qpol_policy_t_constraint_iter(self) + def constraint_count(self): return _qpol.qpol_policy_t_constraint_count(self) + @QpolGenerator(_qpol.qpol_validatetrans_from_void) + def validatetrans_iter(self): return _qpol.qpol_policy_t_validatetrans_iter(self) + def validatetrans_count(self): return _qpol.qpol_policy_t_validatetrans_count(self) + @QpolGenerator(_qpol.qpol_role_allow_from_void) + def role_allow_iter(self): return _qpol.qpol_policy_t_role_allow_iter(self) + def role_allow_count(self): return _qpol.qpol_policy_t_role_allow_count(self) + @QpolGenerator(_qpol.qpol_role_trans_from_void) + def role_trans_iter(self): return _qpol.qpol_policy_t_role_trans_iter(self) + def role_trans_count(self): return _qpol.qpol_policy_t_role_trans_count(self) + @QpolGenerator(_qpol.qpol_range_trans_from_void) + def range_trans_iter(self): return _qpol.qpol_policy_t_range_trans_iter(self) + def range_trans_count(self): return _qpol.qpol_policy_t_range_trans_count(self) + @QpolGenerator(_qpol.qpol_avrule_from_void) + def avrule_iter(self): return _qpol.qpol_policy_t_avrule_iter(self) + def avrule_allow_count(self): return _qpol.qpol_policy_t_avrule_allow_count(self) + def avrule_auditallow_count(self): return _qpol.qpol_policy_t_avrule_auditallow_count(self) + def avrule_neverallow_count(self): return _qpol.qpol_policy_t_avrule_neverallow_count(self) + def avrule_dontaudit_count(self): return _qpol.qpol_policy_t_avrule_dontaudit_count(self) + @QpolGenerator(_qpol.qpol_terule_from_void) + def terule_iter(self): return _qpol.qpol_policy_t_terule_iter(self) + def terule_trans_count(self): return _qpol.qpol_policy_t_terule_trans_count(self) + def terule_change_count(self): return _qpol.qpol_policy_t_terule_change_count(self) + def terule_member_count(self): return _qpol.qpol_policy_t_terule_member_count(self) + def cond_iter(self): return _qpol.qpol_policy_t_cond_iter(self) + def cond_count(self): return _qpol.qpol_policy_t_cond_count(self) + @QpolGenerator(_qpol.qpol_filename_trans_from_void) + def filename_trans_iter(self): return _qpol.qpol_policy_t_filename_trans_iter(self) + def filename_trans_count(self): return _qpol.qpol_policy_t_filename_trans_count(self) + @QpolGenerator(_qpol.qpol_type_from_void) + def permissive_iter(self): return _qpol.qpol_policy_t_permissive_iter(self) + def permissive_count(self): return _qpol.qpol_policy_t_permissive_count(self) + def typebounds_iter(self): return _qpol.qpol_policy_t_typebounds_iter(self) + def typebounds_count(self): return _qpol.qpol_policy_t_typebounds_count(self) + @QpolGenerator(_qpol.qpol_polcap_from_void) + def polcap_iter(self): return _qpol.qpol_policy_t_polcap_iter(self) + def polcap_count(self): return _qpol.qpol_policy_t_polcap_count(self) + @QpolGenerator(_qpol.qpol_default_object_from_void) + def default_iter(self): return _qpol.qpol_policy_t_default_iter(self) +qpol_policy_t_swigregister = _qpol.qpol_policy_t_swigregister +qpol_policy_t_swigregister(qpol_policy_t) + +QPOL_CAP_ATTRIB_NAMES = _qpol.QPOL_CAP_ATTRIB_NAMES +QPOL_CAP_SYN_RULES = _qpol.QPOL_CAP_SYN_RULES +QPOL_CAP_LINE_NUMBERS = _qpol.QPOL_CAP_LINE_NUMBERS +QPOL_CAP_CONDITIONALS = _qpol.QPOL_CAP_CONDITIONALS +QPOL_CAP_MLS = _qpol.QPOL_CAP_MLS +QPOL_CAP_MODULES = _qpol.QPOL_CAP_MODULES +QPOL_CAP_RULES_LOADED = _qpol.QPOL_CAP_RULES_LOADED +QPOL_CAP_SOURCE = _qpol.QPOL_CAP_SOURCE +QPOL_CAP_NEVERALLOW = _qpol.QPOL_CAP_NEVERALLOW +QPOL_CAP_POLCAPS = _qpol.QPOL_CAP_POLCAPS +QPOL_CAP_BOUNDS = _qpol.QPOL_CAP_BOUNDS +QPOL_CAP_DEFAULT_OBJECTS = _qpol.QPOL_CAP_DEFAULT_OBJECTS +QPOL_CAP_DEFAULT_TYPE = _qpol.QPOL_CAP_DEFAULT_TYPE +QPOL_CAP_PERMISSIVE = _qpol.QPOL_CAP_PERMISSIVE +QPOL_CAP_FILENAME_TRANS = _qpol.QPOL_CAP_FILENAME_TRANS +QPOL_CAP_ROLETRANS = _qpol.QPOL_CAP_ROLETRANS +class qpol_iterator_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_iterator_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_iterator_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_iterator_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_iterator_t + __del__ = lambda self : None; + def item(self): return _qpol.qpol_iterator_t_item(self) + def next_(self): return _qpol.qpol_iterator_t_next_(self) + def isend(self): return _qpol.qpol_iterator_t_isend(self) + def size(self): return _qpol.qpol_iterator_t_size(self) +qpol_iterator_t_swigregister = _qpol.qpol_iterator_t_swigregister +qpol_iterator_t_swigregister(qpol_iterator_t) + +class qpol_type_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_type_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_type_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_type_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_type_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_type_t_name(self, *args) + def value(self, *args): return _qpol.qpol_type_t_value(self, *args) + def isalias(self, *args): return _qpol.qpol_type_t_isalias(self, *args) + def isattr(self, *args): return _qpol.qpol_type_t_isattr(self, *args) + def ispermissive(self, *args): return _qpol.qpol_type_t_ispermissive(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def type_iter(self, *args): return _qpol.qpol_type_t_type_iter(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def attr_iter(self, *args): return _qpol.qpol_type_t_attr_iter(self, *args) + @QpolGenerator(_qpol.to_str) + def alias_iter(self, *args): return _qpol.qpol_type_t_alias_iter(self, *args) +qpol_type_t_swigregister = _qpol.qpol_type_t_swigregister +qpol_type_t_swigregister(qpol_type_t) + + +def qpol_type_from_void(*args): + return _qpol.qpol_type_from_void(*args) +qpol_type_from_void = _qpol.qpol_type_from_void +class qpol_role_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_role_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_role_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_role_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_role_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_role_t_value(self, *args) + def name(self, *args): return _qpol.qpol_role_t_name(self, *args) + @QpolGenerator(_qpol.qpol_type_from_void) + def type_iter(self, *args): return _qpol.qpol_role_t_type_iter(self, *args) + def dominate_iter(self, *args): return _qpol.qpol_role_t_dominate_iter(self, *args) +qpol_role_t_swigregister = _qpol.qpol_role_t_swigregister +qpol_role_t_swigregister(qpol_role_t) + + +def qpol_role_from_void(*args): + return _qpol.qpol_role_from_void(*args) +qpol_role_from_void = _qpol.qpol_role_from_void +class qpol_level_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_level_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_level_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_level_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_level_t + __del__ = lambda self : None; + def isalias(self, *args): return _qpol.qpol_level_t_isalias(self, *args) + def value(self, *args): return _qpol.qpol_level_t_value(self, *args) + def name(self, *args): return _qpol.qpol_level_t_name(self, *args) + @QpolGenerator(_qpol.qpol_cat_from_void) + def cat_iter(self, *args): return _qpol.qpol_level_t_cat_iter(self, *args) + @QpolGenerator(_qpol.to_str) + def alias_iter(self, *args): return _qpol.qpol_level_t_alias_iter(self, *args) +qpol_level_t_swigregister = _qpol.qpol_level_t_swigregister +qpol_level_t_swigregister(qpol_level_t) + + +def qpol_level_from_void(*args): + return _qpol.qpol_level_from_void(*args) +qpol_level_from_void = _qpol.qpol_level_from_void +class qpol_cat_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_cat_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_cat_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_cat_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_cat_t + __del__ = lambda self : None; + def isalias(self, *args): return _qpol.qpol_cat_t_isalias(self, *args) + def value(self, *args): return _qpol.qpol_cat_t_value(self, *args) + def name(self, *args): return _qpol.qpol_cat_t_name(self, *args) + @QpolGenerator(_qpol.to_str) + def alias_iter(self, *args): return _qpol.qpol_cat_t_alias_iter(self, *args) +qpol_cat_t_swigregister = _qpol.qpol_cat_t_swigregister +qpol_cat_t_swigregister(qpol_cat_t) + + +def qpol_cat_from_void(*args): + return _qpol.qpol_cat_from_void(*args) +qpol_cat_from_void = _qpol.qpol_cat_from_void +class qpol_mls_range_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_mls_range_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_mls_range_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_mls_range_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_mls_range_t + __del__ = lambda self : None; + def high_level(self, *args): return _qpol.qpol_mls_range_t_high_level(self, *args) + def low_level(self, *args): return _qpol.qpol_mls_range_t_low_level(self, *args) +qpol_mls_range_t_swigregister = _qpol.qpol_mls_range_t_swigregister +qpol_mls_range_t_swigregister(qpol_mls_range_t) + + +def qpol_mls_range_from_void(*args): + return _qpol.qpol_mls_range_from_void(*args) +qpol_mls_range_from_void = _qpol.qpol_mls_range_from_void +class qpol_semantic_level_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_semantic_level_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_semantic_level_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_semantic_level_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_semantic_level_t + __del__ = lambda self : None; + def add_cats(self, *args): return _qpol.qpol_semantic_level_t_add_cats(self, *args) +qpol_semantic_level_t_swigregister = _qpol.qpol_semantic_level_t_swigregister +qpol_semantic_level_t_swigregister(qpol_semantic_level_t) + +class qpol_mls_level_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_mls_level_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_mls_level_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_mls_level_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_mls_level_t + __del__ = lambda self : None; + def sens_name(self, *args): return _qpol.qpol_mls_level_t_sens_name(self, *args) + @QpolGenerator(_qpol.qpol_cat_from_void) + def cat_iter(self, *args): return _qpol.qpol_mls_level_t_cat_iter(self, *args) +qpol_mls_level_t_swigregister = _qpol.qpol_mls_level_t_swigregister +qpol_mls_level_t_swigregister(qpol_mls_level_t) + + +def qpol_mls_level_from_void(*args): + return _qpol.qpol_mls_level_from_void(*args) +qpol_mls_level_from_void = _qpol.qpol_mls_level_from_void +class qpol_user_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_user_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_user_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_user_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_user_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_user_t_value(self, *args) + @QpolGenerator(_qpol.qpol_role_from_void) + def role_iter(self, *args): return _qpol.qpol_user_t_role_iter(self, *args) + def range(self, *args): return _qpol.qpol_user_t_range(self, *args) + def name(self, *args): return _qpol.qpol_user_t_name(self, *args) + def dfltlevel(self, *args): return _qpol.qpol_user_t_dfltlevel(self, *args) +qpol_user_t_swigregister = _qpol.qpol_user_t_swigregister +qpol_user_t_swigregister(qpol_user_t) + + +def qpol_user_from_void(*args): + return _qpol.qpol_user_from_void(*args) +qpol_user_from_void = _qpol.qpol_user_from_void +class qpol_bool_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_bool_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_bool_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_bool_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_bool_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_bool_t_value(self, *args) + def state(self, *args): return _qpol.qpol_bool_t_state(self, *args) + def name(self, *args): return _qpol.qpol_bool_t_name(self, *args) +qpol_bool_t_swigregister = _qpol.qpol_bool_t_swigregister +qpol_bool_t_swigregister(qpol_bool_t) + + +def qpol_bool_from_void(*args): + return _qpol.qpol_bool_from_void(*args) +qpol_bool_from_void = _qpol.qpol_bool_from_void +class qpol_context_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_context_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_context_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_context_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_context_t + __del__ = lambda self : None; + def user(self, *args): return _qpol.qpol_context_t_user(self, *args) + def role(self, *args): return _qpol.qpol_context_t_role(self, *args) + def type_(self, *args): return _qpol.qpol_context_t_type_(self, *args) + def range(self, *args): return _qpol.qpol_context_t_range(self, *args) +qpol_context_t_swigregister = _qpol.qpol_context_t_swigregister +qpol_context_t_swigregister(qpol_context_t) + + +def qpol_context_from_void(*args): + return _qpol.qpol_context_from_void(*args) +qpol_context_from_void = _qpol.qpol_context_from_void +class qpol_class_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_class_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_class_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_class_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_class_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_class_t_value(self, *args) + def common(self, *args): return _qpol.qpol_class_t_common(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_class_t_perm_iter(self, *args) + @QpolGenerator(_qpol.qpol_constraint_from_void) + def constraint_iter(self, *args): return _qpol.qpol_class_t_constraint_iter(self, *args) + @QpolGenerator(_qpol.qpol_validatetrans_from_void) + def validatetrans_iter(self, *args): return _qpol.qpol_class_t_validatetrans_iter(self, *args) + def name(self, *args): return _qpol.qpol_class_t_name(self, *args) +qpol_class_t_swigregister = _qpol.qpol_class_t_swigregister +qpol_class_t_swigregister(qpol_class_t) + + +def qpol_class_from_void(*args): + return _qpol.qpol_class_from_void(*args) +qpol_class_from_void = _qpol.qpol_class_from_void +class qpol_common_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_common_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_common_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_common_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_common_t + __del__ = lambda self : None; + def value(self, *args): return _qpol.qpol_common_t_value(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_common_t_perm_iter(self, *args) + def name(self, *args): return _qpol.qpol_common_t_name(self, *args) +qpol_common_t_swigregister = _qpol.qpol_common_t_swigregister +qpol_common_t_swigregister(qpol_common_t) + + +def qpol_common_from_void(*args): + return _qpol.qpol_common_from_void(*args) +qpol_common_from_void = _qpol.qpol_common_from_void +QPOL_FS_USE_XATTR = _qpol.QPOL_FS_USE_XATTR +QPOL_FS_USE_TRANS = _qpol.QPOL_FS_USE_TRANS +QPOL_FS_USE_TASK = _qpol.QPOL_FS_USE_TASK +QPOL_FS_USE_GENFS = _qpol.QPOL_FS_USE_GENFS +QPOL_FS_USE_NONE = _qpol.QPOL_FS_USE_NONE +QPOL_FS_USE_PSID = _qpol.QPOL_FS_USE_PSID +class qpol_fs_use_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_fs_use_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_fs_use_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_fs_use_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_fs_use_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_fs_use_t_name(self, *args) + def behavior(self, *args): return _qpol.qpol_fs_use_t_behavior(self, *args) + def context(self, *args): return _qpol.qpol_fs_use_t_context(self, *args) +qpol_fs_use_t_swigregister = _qpol.qpol_fs_use_t_swigregister +qpol_fs_use_t_swigregister(qpol_fs_use_t) + + +def qpol_fs_use_from_void(*args): + return _qpol.qpol_fs_use_from_void(*args) +qpol_fs_use_from_void = _qpol.qpol_fs_use_from_void +QPOL_CLASS_ALL = _qpol.QPOL_CLASS_ALL +QPOL_CLASS_BLK_FILE = _qpol.QPOL_CLASS_BLK_FILE +QPOL_CLASS_CHR_FILE = _qpol.QPOL_CLASS_CHR_FILE +QPOL_CLASS_DIR = _qpol.QPOL_CLASS_DIR +QPOL_CLASS_FIFO_FILE = _qpol.QPOL_CLASS_FIFO_FILE +QPOL_CLASS_FILE = _qpol.QPOL_CLASS_FILE +QPOL_CLASS_LNK_FILE = _qpol.QPOL_CLASS_LNK_FILE +QPOL_CLASS_SOCK_FILE = _qpol.QPOL_CLASS_SOCK_FILE +class qpol_genfscon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_genfscon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_genfscon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_genfscon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_genfscon_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_genfscon_t_name(self, *args) + def path(self, *args): return _qpol.qpol_genfscon_t_path(self, *args) + def object_class(self, *args): return _qpol.qpol_genfscon_t_object_class(self, *args) + def context(self, *args): return _qpol.qpol_genfscon_t_context(self, *args) +qpol_genfscon_t_swigregister = _qpol.qpol_genfscon_t_swigregister +qpol_genfscon_t_swigregister(qpol_genfscon_t) + + +def qpol_genfscon_from_void(*args): + return _qpol.qpol_genfscon_from_void(*args) +qpol_genfscon_from_void = _qpol.qpol_genfscon_from_void +class qpol_isid_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_isid_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_isid_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_isid_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_isid_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_isid_t_name(self, *args) + def context(self, *args): return _qpol.qpol_isid_t_context(self, *args) +qpol_isid_t_swigregister = _qpol.qpol_isid_t_swigregister +qpol_isid_t_swigregister(qpol_isid_t) + + +def qpol_isid_from_void(*args): + return _qpol.qpol_isid_from_void(*args) +qpol_isid_from_void = _qpol.qpol_isid_from_void +class qpol_netifcon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_netifcon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_netifcon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_netifcon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_netifcon_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_netifcon_t_name(self, *args) + def msg_con(self, *args): return _qpol.qpol_netifcon_t_msg_con(self, *args) + def if_con(self, *args): return _qpol.qpol_netifcon_t_if_con(self, *args) +qpol_netifcon_t_swigregister = _qpol.qpol_netifcon_t_swigregister +qpol_netifcon_t_swigregister(qpol_netifcon_t) + + +def qpol_netifcon_from_void(*args): + return _qpol.qpol_netifcon_from_void(*args) +qpol_netifcon_from_void = _qpol.qpol_netifcon_from_void +QPOL_IPV4 = _qpol.QPOL_IPV4 +QPOL_IPV6 = _qpol.QPOL_IPV6 +class qpol_nodecon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_nodecon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_nodecon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_nodecon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_nodecon_t + __del__ = lambda self : None; + def addr(self, *args): return _qpol.qpol_nodecon_t_addr(self, *args) + def mask(self, *args): return _qpol.qpol_nodecon_t_mask(self, *args) + def protocol(self, *args): return _qpol.qpol_nodecon_t_protocol(self, *args) + def context(self, *args): return _qpol.qpol_nodecon_t_context(self, *args) +qpol_nodecon_t_swigregister = _qpol.qpol_nodecon_t_swigregister +qpol_nodecon_t_swigregister(qpol_nodecon_t) + + +def qpol_nodecon_from_void(*args): + return _qpol.qpol_nodecon_from_void(*args) +qpol_nodecon_from_void = _qpol.qpol_nodecon_from_void +IPPROTO_TCP = _qpol.IPPROTO_TCP +IPPROTO_UDP = _qpol.IPPROTO_UDP +class qpol_portcon_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_portcon_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_portcon_t, name) + __repr__ = _swig_repr + def __init__(self, *args): + this = _qpol.new_qpol_portcon_t(*args) + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_portcon_t + __del__ = lambda self : None; + def low_port(self, *args): return _qpol.qpol_portcon_t_low_port(self, *args) + def high_port(self, *args): return _qpol.qpol_portcon_t_high_port(self, *args) + def protocol(self, *args): return _qpol.qpol_portcon_t_protocol(self, *args) + def context(self, *args): return _qpol.qpol_portcon_t_context(self, *args) +qpol_portcon_t_swigregister = _qpol.qpol_portcon_t_swigregister +qpol_portcon_t_swigregister(qpol_portcon_t) + + +def qpol_portcon_from_void(*args): + return _qpol.qpol_portcon_from_void(*args) +qpol_portcon_from_void = _qpol.qpol_portcon_from_void +class qpol_constraint_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_constraint_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_constraint_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_constraint_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_constraint_t + __del__ = lambda self : None; + def object_class(self, *args): return _qpol.qpol_constraint_t_object_class(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_constraint_t_perm_iter(self, *args) + @QpolGenerator(_qpol.qpol_constraint_expr_node_from_void) + def expr_iter(self, *args): return _qpol.qpol_constraint_t_expr_iter(self, *args) +qpol_constraint_t_swigregister = _qpol.qpol_constraint_t_swigregister +qpol_constraint_t_swigregister(qpol_constraint_t) + + +def qpol_constraint_from_void(*args): + return _qpol.qpol_constraint_from_void(*args) +qpol_constraint_from_void = _qpol.qpol_constraint_from_void +class qpol_validatetrans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_validatetrans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_validatetrans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_validatetrans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_validatetrans_t + __del__ = lambda self : None; + def object_class(self, *args): return _qpol.qpol_validatetrans_t_object_class(self, *args) + @QpolGenerator(_qpol.qpol_constraint_expr_node_from_void) + def expr_iter(self, *args): return _qpol.qpol_validatetrans_t_expr_iter(self, *args) +qpol_validatetrans_t_swigregister = _qpol.qpol_validatetrans_t_swigregister +qpol_validatetrans_t_swigregister(qpol_validatetrans_t) + + +def qpol_validatetrans_from_void(*args): + return _qpol.qpol_validatetrans_from_void(*args) +qpol_validatetrans_from_void = _qpol.qpol_validatetrans_from_void +QPOL_CEXPR_TYPE_NOT = _qpol.QPOL_CEXPR_TYPE_NOT +QPOL_CEXPR_TYPE_AND = _qpol.QPOL_CEXPR_TYPE_AND +QPOL_CEXPR_TYPE_OR = _qpol.QPOL_CEXPR_TYPE_OR +QPOL_CEXPR_TYPE_ATTR = _qpol.QPOL_CEXPR_TYPE_ATTR +QPOL_CEXPR_TYPE_NAMES = _qpol.QPOL_CEXPR_TYPE_NAMES +QPOL_CEXPR_SYM_USER = _qpol.QPOL_CEXPR_SYM_USER +QPOL_CEXPR_SYM_ROLE = _qpol.QPOL_CEXPR_SYM_ROLE +QPOL_CEXPR_SYM_TYPE = _qpol.QPOL_CEXPR_SYM_TYPE +QPOL_CEXPR_SYM_TARGET = _qpol.QPOL_CEXPR_SYM_TARGET +QPOL_CEXPR_SYM_XTARGET = _qpol.QPOL_CEXPR_SYM_XTARGET +QPOL_CEXPR_SYM_L1L2 = _qpol.QPOL_CEXPR_SYM_L1L2 +QPOL_CEXPR_SYM_L1H2 = _qpol.QPOL_CEXPR_SYM_L1H2 +QPOL_CEXPR_SYM_H1L2 = _qpol.QPOL_CEXPR_SYM_H1L2 +QPOL_CEXPR_SYM_H1H2 = _qpol.QPOL_CEXPR_SYM_H1H2 +QPOL_CEXPR_SYM_L1H1 = _qpol.QPOL_CEXPR_SYM_L1H1 +QPOL_CEXPR_SYM_L2H2 = _qpol.QPOL_CEXPR_SYM_L2H2 +QPOL_CEXPR_OP_EQ = _qpol.QPOL_CEXPR_OP_EQ +QPOL_CEXPR_OP_NEQ = _qpol.QPOL_CEXPR_OP_NEQ +QPOL_CEXPR_OP_DOM = _qpol.QPOL_CEXPR_OP_DOM +QPOL_CEXPR_OP_DOMBY = _qpol.QPOL_CEXPR_OP_DOMBY +QPOL_CEXPR_OP_INCOMP = _qpol.QPOL_CEXPR_OP_INCOMP +class qpol_constraint_expr_node_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_constraint_expr_node_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_constraint_expr_node_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_constraint_expr_node_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_constraint_expr_node_t + __del__ = lambda self : None; + def expr_type(self, *args): return _qpol.qpol_constraint_expr_node_t_expr_type(self, *args) + def sym_type(self, *args): return _qpol.qpol_constraint_expr_node_t_sym_type(self, *args) + def op(self, *args): return _qpol.qpol_constraint_expr_node_t_op(self, *args) + @QpolGenerator(_qpol.to_str) + def names_iter(self, *args): return _qpol.qpol_constraint_expr_node_t_names_iter(self, *args) +qpol_constraint_expr_node_t_swigregister = _qpol.qpol_constraint_expr_node_t_swigregister +qpol_constraint_expr_node_t_swigregister(qpol_constraint_expr_node_t) + + +def qpol_constraint_expr_node_from_void(*args): + return _qpol.qpol_constraint_expr_node_from_void(*args) +qpol_constraint_expr_node_from_void = _qpol.qpol_constraint_expr_node_from_void +class qpol_role_allow_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_role_allow_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_role_allow_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_role_allow_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_role_allow_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "allow" + + def source_role(self, *args): return _qpol.qpol_role_allow_t_source_role(self, *args) + def target_role(self, *args): return _qpol.qpol_role_allow_t_target_role(self, *args) +qpol_role_allow_t_swigregister = _qpol.qpol_role_allow_t_swigregister +qpol_role_allow_t_swigregister(qpol_role_allow_t) + + +def qpol_role_allow_from_void(*args): + return _qpol.qpol_role_allow_from_void(*args) +qpol_role_allow_from_void = _qpol.qpol_role_allow_from_void +class qpol_role_trans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_role_trans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_role_trans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_role_trans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_role_trans_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "role_transition" + + def source_role(self, *args): return _qpol.qpol_role_trans_t_source_role(self, *args) + def target_type(self, *args): return _qpol.qpol_role_trans_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_role_trans_t_object_class(self, *args) + def default_role(self, *args): return _qpol.qpol_role_trans_t_default_role(self, *args) +qpol_role_trans_t_swigregister = _qpol.qpol_role_trans_t_swigregister +qpol_role_trans_t_swigregister(qpol_role_trans_t) + + +def qpol_role_trans_from_void(*args): + return _qpol.qpol_role_trans_from_void(*args) +qpol_role_trans_from_void = _qpol.qpol_role_trans_from_void +class qpol_range_trans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_range_trans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_range_trans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_range_trans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_range_trans_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "range_transition" + + def source_type(self, *args): return _qpol.qpol_range_trans_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_range_trans_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_range_trans_t_object_class(self, *args) + def range(self, *args): return _qpol.qpol_range_trans_t_range(self, *args) +qpol_range_trans_t_swigregister = _qpol.qpol_range_trans_t_swigregister +qpol_range_trans_t_swigregister(qpol_range_trans_t) + + +def qpol_range_trans_from_void(*args): + return _qpol.qpol_range_trans_from_void(*args) +qpol_range_trans_from_void = _qpol.qpol_range_trans_from_void +QPOL_RULE_ALLOW = _qpol.QPOL_RULE_ALLOW +QPOL_RULE_NEVERALLOW = _qpol.QPOL_RULE_NEVERALLOW +QPOL_RULE_AUDITALLOW = _qpol.QPOL_RULE_AUDITALLOW +QPOL_RULE_DONTAUDIT = _qpol.QPOL_RULE_DONTAUDIT +class qpol_avrule_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_avrule_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_avrule_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_avrule_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_avrule_t + __del__ = lambda self : None; + def rule_type(self, *args): return _qpol.qpol_avrule_t_rule_type(self, *args) + def source_type(self, *args): return _qpol.qpol_avrule_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_avrule_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_avrule_t_object_class(self, *args) + @QpolGenerator(_qpol.to_str) + def perm_iter(self, *args): return _qpol.qpol_avrule_t_perm_iter(self, *args) + def cond(self, *args): return _qpol.qpol_avrule_t_cond(self, *args) + def is_enabled(self, *args): return _qpol.qpol_avrule_t_is_enabled(self, *args) + def which_list(self, *args): return _qpol.qpol_avrule_t_which_list(self, *args) + def syn_avrule_iter(self, *args): return _qpol.qpol_avrule_t_syn_avrule_iter(self, *args) +qpol_avrule_t_swigregister = _qpol.qpol_avrule_t_swigregister +qpol_avrule_t_swigregister(qpol_avrule_t) + + +def qpol_avrule_from_void(*args): + return _qpol.qpol_avrule_from_void(*args) +qpol_avrule_from_void = _qpol.qpol_avrule_from_void +QPOL_RULE_TYPE_TRANS = _qpol.QPOL_RULE_TYPE_TRANS +QPOL_RULE_TYPE_CHANGE = _qpol.QPOL_RULE_TYPE_CHANGE +QPOL_RULE_TYPE_MEMBER = _qpol.QPOL_RULE_TYPE_MEMBER +class qpol_terule_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_terule_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_terule_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_terule_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_terule_t + __del__ = lambda self : None; + def rule_type(self, *args): return _qpol.qpol_terule_t_rule_type(self, *args) + def source_type(self, *args): return _qpol.qpol_terule_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_terule_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_terule_t_object_class(self, *args) + def default_type(self, *args): return _qpol.qpol_terule_t_default_type(self, *args) + def cond(self, *args): return _qpol.qpol_terule_t_cond(self, *args) + def is_enabled(self, *args): return _qpol.qpol_terule_t_is_enabled(self, *args) + def which_list(self, *args): return _qpol.qpol_terule_t_which_list(self, *args) + def syn_terule_iter(self, *args): return _qpol.qpol_terule_t_syn_terule_iter(self, *args) +qpol_terule_t_swigregister = _qpol.qpol_terule_t_swigregister +qpol_terule_t_swigregister(qpol_terule_t) + + +def qpol_terule_from_void(*args): + return _qpol.qpol_terule_from_void(*args) +qpol_terule_from_void = _qpol.qpol_terule_from_void +class qpol_cond_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_cond_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_cond_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_cond_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_cond_t + __del__ = lambda self : None; + @QpolGenerator(_qpol.qpol_cond_expr_node_from_void) + def expr_node_iter(self, *args): return _qpol.qpol_cond_t_expr_node_iter(self, *args) + def av_true_iter(self, *args): return _qpol.qpol_cond_t_av_true_iter(self, *args) + def av_false_iter(self, *args): return _qpol.qpol_cond_t_av_false_iter(self, *args) + def te_true_iter(self, *args): return _qpol.qpol_cond_t_te_true_iter(self, *args) + def te_false_iter(self, *args): return _qpol.qpol_cond_t_te_false_iter(self, *args) + def evaluate(self, *args): return _qpol.qpol_cond_t_evaluate(self, *args) +qpol_cond_t_swigregister = _qpol.qpol_cond_t_swigregister +qpol_cond_t_swigregister(qpol_cond_t) + + +def qpol_cond_from_void(*args): + return _qpol.qpol_cond_from_void(*args) +qpol_cond_from_void = _qpol.qpol_cond_from_void +QPOL_COND_EXPR_BOOL = _qpol.QPOL_COND_EXPR_BOOL +QPOL_COND_EXPR_NOT = _qpol.QPOL_COND_EXPR_NOT +QPOL_COND_EXPR_OR = _qpol.QPOL_COND_EXPR_OR +QPOL_COND_EXPR_AND = _qpol.QPOL_COND_EXPR_AND +QPOL_COND_EXPR_XOR = _qpol.QPOL_COND_EXPR_XOR +QPOL_COND_EXPR_EQ = _qpol.QPOL_COND_EXPR_EQ +QPOL_COND_EXPR_NEQ = _qpol.QPOL_COND_EXPR_NEQ +class qpol_cond_expr_node_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_cond_expr_node_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_cond_expr_node_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_cond_expr_node_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_cond_expr_node_t + __del__ = lambda self : None; + def expr_type(self, *args): return _qpol.qpol_cond_expr_node_t_expr_type(self, *args) + def get_boolean(self, *args): return _qpol.qpol_cond_expr_node_t_get_boolean(self, *args) +qpol_cond_expr_node_t_swigregister = _qpol.qpol_cond_expr_node_t_swigregister +qpol_cond_expr_node_t_swigregister(qpol_cond_expr_node_t) + + +def qpol_cond_expr_node_from_void(*args): + return _qpol.qpol_cond_expr_node_from_void(*args) +qpol_cond_expr_node_from_void = _qpol.qpol_cond_expr_node_from_void +class qpol_filename_trans_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_filename_trans_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_filename_trans_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_filename_trans_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_filename_trans_t + __del__ = lambda self : None; + def rule_type(self,policy): + return "type_transition" + + def source_type(self, *args): return _qpol.qpol_filename_trans_t_source_type(self, *args) + def target_type(self, *args): return _qpol.qpol_filename_trans_t_target_type(self, *args) + def object_class(self, *args): return _qpol.qpol_filename_trans_t_object_class(self, *args) + def default_type(self, *args): return _qpol.qpol_filename_trans_t_default_type(self, *args) + def filename(self, *args): return _qpol.qpol_filename_trans_t_filename(self, *args) +qpol_filename_trans_t_swigregister = _qpol.qpol_filename_trans_t_swigregister +qpol_filename_trans_t_swigregister(qpol_filename_trans_t) + + +def qpol_filename_trans_from_void(*args): + return _qpol.qpol_filename_trans_from_void(*args) +qpol_filename_trans_from_void = _qpol.qpol_filename_trans_from_void +class qpol_polcap_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_polcap_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_polcap_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_polcap_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_polcap_t + __del__ = lambda self : None; + def name(self, *args): return _qpol.qpol_polcap_t_name(self, *args) +qpol_polcap_t_swigregister = _qpol.qpol_polcap_t_swigregister +qpol_polcap_t_swigregister(qpol_polcap_t) + + +def qpol_polcap_from_void(*args): + return _qpol.qpol_polcap_from_void(*args) +qpol_polcap_from_void = _qpol.qpol_polcap_from_void +class qpol_typebounds_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_typebounds_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_typebounds_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_typebounds_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_typebounds_t + __del__ = lambda self : None; + def parent_name(self, *args): return _qpol.qpol_typebounds_t_parent_name(self, *args) + def child_name(self, *args): return _qpol.qpol_typebounds_t_child_name(self, *args) +qpol_typebounds_t_swigregister = _qpol.qpol_typebounds_t_swigregister +qpol_typebounds_t_swigregister(qpol_typebounds_t) + + +def qpol_typebounds_from_void(*args): + return _qpol.qpol_typebounds_from_void(*args) +qpol_typebounds_from_void = _qpol.qpol_typebounds_from_void +class qpol_rolebounds_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_rolebounds_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_rolebounds_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_rolebounds_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_rolebounds_t + __del__ = lambda self : None; + def parent_name(self, *args): return _qpol.qpol_rolebounds_t_parent_name(self, *args) + def child_name(self, *args): return _qpol.qpol_rolebounds_t_child_name(self, *args) +qpol_rolebounds_t_swigregister = _qpol.qpol_rolebounds_t_swigregister +qpol_rolebounds_t_swigregister(qpol_rolebounds_t) + + +def qpol_rolebounds_from_void(*args): + return _qpol.qpol_rolebounds_from_void(*args) +qpol_rolebounds_from_void = _qpol.qpol_rolebounds_from_void +class qpol_userbounds_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_userbounds_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_userbounds_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_userbounds_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_userbounds_t + __del__ = lambda self : None; + def parent_name(self, *args): return _qpol.qpol_userbounds_t_parent_name(self, *args) + def child_name(self, *args): return _qpol.qpol_userbounds_t_child_name(self, *args) +qpol_userbounds_t_swigregister = _qpol.qpol_userbounds_t_swigregister +qpol_userbounds_t_swigregister(qpol_userbounds_t) + + +def qpol_userbounds_from_void(*args): + return _qpol.qpol_userbounds_from_void(*args) +qpol_userbounds_from_void = _qpol.qpol_userbounds_from_void +class qpol_default_object_t(_object): + __swig_setmethods__ = {} + __setattr__ = lambda self, name, value: _swig_setattr(self, qpol_default_object_t, name, value) + __swig_getmethods__ = {} + __getattr__ = lambda self, name: _swig_getattr(self, qpol_default_object_t, name) + __repr__ = _swig_repr + def __init__(self): + this = _qpol.new_qpol_default_object_t() + try: self.this.append(this) + except: self.this = this + __swig_destroy__ = _qpol.delete_qpol_default_object_t + __del__ = lambda self : None; + def object_class(self, *args): return _qpol.qpol_default_object_t_object_class(self, *args) + def user_default(self, *args): return _qpol.qpol_default_object_t_user_default(self, *args) + def role_default(self, *args): return _qpol.qpol_default_object_t_role_default(self, *args) + def type_default(self, *args): return _qpol.qpol_default_object_t_type_default(self, *args) + def range_default(self, *args): return _qpol.qpol_default_object_t_range_default(self, *args) +qpol_default_object_t_swigregister = _qpol.qpol_default_object_t_swigregister +qpol_default_object_t_swigregister(qpol_default_object_t) + + +def qpol_default_object_from_void(*args): + return _qpol.qpol_default_object_from_void(*args) +qpol_default_object_from_void = _qpol.qpol_default_object_from_void +# This file is compatible with both classic and new-style classes. + + diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/rbacrule.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/rbacrule.py new file mode 100644 index 0000000..aa6a0d0 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/rbacrule.py @@ -0,0 +1,92 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import rule +from . import role +from . import typeattr + + +def rbac_rule_factory(policy, name): + """Factory function for creating RBAC rule objects.""" + + if isinstance(name, qpol.qpol_role_allow_t): + return RoleAllow(policy, name) + elif isinstance(name, qpol.qpol_role_trans_t): + return RoleTransition(policy, name) + else: + raise TypeError("RBAC rules cannot be looked up.") + + +def validate_ruletype(types): + """Validate RBAC rule types.""" + for t in types: + if t not in ["allow", "role_transition"]: + raise exception.InvalidRBACRuleType("{0} is not a valid RBAC rule type.".format(t)) + + +class RoleAllow(rule.PolicyRule): + + """A role allow rule.""" + + def __str__(self): + return "allow {0.source} {0.target};".format(self) + + @property + def source(self): + """The rule's source role.""" + return role.role_factory(self.policy, self.qpol_symbol.source_role(self.policy)) + + @property + def target(self): + """The rule's target role.""" + return role.role_factory(self.policy, self.qpol_symbol.target_role(self.policy)) + + @property + def tclass(self): + """The rule's object class.""" + raise exception.RuleUseError("Role allow rules do not have an object class.") + + @property + def default(self): + """The rule's default role.""" + raise exception.RuleUseError("Role allow rules do not have a default role.") + + +class RoleTransition(rule.PolicyRule): + + """A role_transition rule.""" + + def __str__(self): + return "role_transition {0.source} {0.target}:{0.tclass} {0.default};".format(self) + + @property + def source(self): + """The rule's source role.""" + return role.role_factory(self.policy, self.qpol_symbol.source_role(self.policy)) + + @property + def target(self): + """The rule's target type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.target_type(self.policy)) + + @property + def default(self): + """The rule's default role.""" + return role.role_factory(self.policy, self.qpol_symbol.default_role(self.policy)) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/role.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/role.py new file mode 100644 index 0000000..1d9fbe1 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/role.py @@ -0,0 +1,81 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol +from . import typeattr + + +def role_factory(qpol_policy, name): + """Factory function for creating Role objects.""" + + if isinstance(name, Role): + assert name.policy == qpol_policy + return name + elif isinstance(name, qpol.qpol_role_t): + return Role(qpol_policy, name) + + try: + return Role(qpol_policy, qpol.qpol_role_t(qpol_policy, str(name))) + except ValueError: + raise exception.InvalidRole("{0} is not a valid role".format(name)) + + +class BaseRole(symbol.PolicySymbol): + + """Role/role attribute base class.""" + + def expand(self): + raise NotImplementedError + + def types(self): + raise NotImplementedError + + +class Role(BaseRole): + + """A role.""" + + def expand(self): + """Generator that expands this into its member roles.""" + yield self + + def types(self): + """Generator which yields the role's set of types.""" + + for type_ in self.qpol_symbol.type_iter(self.policy): + yield typeattr.type_or_attr_factory(self.policy, type_) + + def statement(self): + types = list(str(t) for t in self.types()) + stmt = "role {0}".format(self) + if types: + if (len(types) > 1): + stmt += " types {{ {0} }}".format(' '.join(types)) + else: + stmt += " types {0}".format(types[0]) + stmt += ";" + return stmt + + +class RoleAttribute(BaseRole): + + """A role attribute.""" + + pass diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/rule.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/rule.py new file mode 100644 index 0000000..73fc812 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/rule.py @@ -0,0 +1,72 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import symbol +from . import objclass + + +class PolicyRule(symbol.PolicySymbol): + + """This is base class for policy rules.""" + + def __str__(self): + raise NotImplementedError + + @property + def ruletype(self): + """The rule type for the rule.""" + return self.qpol_symbol.rule_type(self.policy) + + @property + def source(self): + """ + The source for the rule. This should be overridden by + subclasses. + """ + raise NotImplementedError + + @property + def target(self): + """ + The target for the rule. This should be overridden by + subclasses. + """ + raise NotImplementedError + + @property + def tclass(self): + """The object class for the rule.""" + return objclass.class_factory(self.policy, self.qpol_symbol.object_class(self.policy)) + + @property + def default(self): + """ + The default for the rule. This should be overridden by + subclasses. + """ + raise NotImplementedError + + @property + def conditional(self): + """The conditional expression for this rule.""" + # Most rules cannot be conditional. + raise exception.RuleNotConditional + + def statement(self): + return str(self) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/symbol.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/symbol.py new file mode 100644 index 0000000..4712d7f --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/symbol.py @@ -0,0 +1,74 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# + + +class PolicySymbol(object): + + """This is a base class for all policy objects.""" + + def __init__(self, policy, qpol_symbol): + """ + Parameters: + policy The low-level policy object. + qpol_symbol The low-level policy symbol object. + """ + + assert qpol_symbol + + self.policy = policy + self.qpol_symbol = qpol_symbol + + def __str__(self): + return self.qpol_symbol.name(self.policy) + + def __hash__(self): + return hash(self.qpol_symbol.name(self.policy)) + + def __eq__(self, other): + try: + return self.qpol_symbol.this == other.qpol_symbol.this + except AttributeError: + return str(self) == str(other) + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + """Comparison used by Python sorting functions.""" + return str(self) < str(other) + + def __repr__(self): + return "<{0.__class__.__name__}(,\"{0}\")>".format( + self, id(self.policy)) + + def __deepcopy__(self, memo): + # shallow copy as all of the members are immutable + cls = self.__class__ + newobj = cls.__new__(cls) + newobj.policy = self.policy + newobj.qpol_symbol = self.qpol_symbol + memo[id(self)] = newobj + return newobj + + def statement(self): + """ + A rendering of the policy statement. This should be + overridden by subclasses. + """ + raise NotImplementedError diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/terule.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/terule.py new file mode 100644 index 0000000..d8a9e94 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/terule.py @@ -0,0 +1,155 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import rule +from . import typeattr +from . import boolcond + + +def te_rule_factory(policy, symbol): + """Factory function for creating TE rule objects.""" + + if isinstance(symbol, qpol.qpol_avrule_t): + return AVRule(policy, symbol) + elif isinstance(symbol, (qpol.qpol_terule_t, qpol.qpol_filename_trans_t)): + return TERule(policy, symbol) + else: + raise TypeError("TE rules cannot be looked-up.") + + +def validate_ruletype(types): + """Validate TE Rule types.""" + for t in types: + if t not in ["allow", "auditallow", "dontaudit", "neverallow", + "type_transition", "type_member", "type_change"]: + raise exception.InvalidTERuleType("{0} is not a valid TE rule type.".format(t)) + + +class BaseTERule(rule.PolicyRule): + + """A type enforcement rule.""" + + @property + def source(self): + """The rule's source type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.source_type(self.policy)) + + @property + def target(self): + """The rule's target type/attribute.""" + return typeattr.type_or_attr_factory(self.policy, self.qpol_symbol.target_type(self.policy)) + + @property + def filename(self): + raise NotImplementedError + + @property + def conditional(self): + """The rule's conditional expression.""" + try: + return boolcond.condexpr_factory(self.policy, self.qpol_symbol.cond(self.policy)) + except (AttributeError, ValueError): + # AttributeError: name filetrans rules cannot be conditional + # so no member function + # ValueError: The rule is not conditional + raise exception.RuleNotConditional + + +class AVRule(BaseTERule): + + """An access vector type enforcement rule.""" + + def __str__(self): + rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} ".format( + self) + + perms = self.perms + + # allow/dontaudit/auditallow/neverallow rules + if len(perms) > 1: + rule_string += "{{ {0} }};".format(' '.join(perms)) + else: + # convert to list since sets cannot be indexed + rule_string += "{0};".format(list(perms)[0]) + + try: + rule_string += " [ {0} ]".format(self.conditional) + except exception.RuleNotConditional: + pass + + return rule_string + + @property + def perms(self): + """The rule's permission set.""" + return set(self.qpol_symbol.perm_iter(self.policy)) + + @property + def default(self): + """The rule's default type.""" + raise exception.RuleUseError("{0} rules do not have a default type.".format(self.ruletype)) + + @property + def filename(self): + raise exception.RuleUseError("{0} rules do not have file names".format(self.ruletype)) + + +class TERule(BaseTERule): + + """A type_* type enforcement rule.""" + + def __str__(self): + rule_string = "{0.ruletype} {0.source} {0.target}:{0.tclass} {0.default}".format(self) + + try: + rule_string += " \"{0}\";".format(self.filename) + except (exception.TERuleNoFilename, exception.RuleUseError): + # invalid use for type_change/member + rule_string += ";" + + try: + rule_string += " [ {0} ]".format(self.conditional) + except exception.RuleNotConditional: + pass + + return rule_string + + @property + def perms(self): + """The rule's permission set.""" + raise exception.RuleUseError( + "{0} rules do not have a permission set.".format(self.ruletype)) + + @property + def default(self): + """The rule's default type.""" + return typeattr.type_factory(self.policy, self.qpol_symbol.default_type(self.policy)) + + @property + def filename(self): + """The type_transition rule's file name.""" + try: + return self.qpol_symbol.filename(self.policy) + except AttributeError: + if self.ruletype == "type_transition": + raise exception.TERuleNoFilename + else: + raise exception.RuleUseError("{0} rules do not have file names". + format(self.ruletype)) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/typeattr.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/typeattr.py new file mode 100644 index 0000000..a52c69a --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/typeattr.py @@ -0,0 +1,174 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import symbol + + +def _symbol_lookup(qpol_policy, name): + """Look up the low-level qpol policy reference""" + if isinstance(name, qpol.qpol_type_t): + return name + + try: + return qpol.qpol_type_t(qpol_policy, str(name)) + except ValueError: + raise exception.InvalidType("{0} is not a valid type/attribute".format(name)) + + +def attribute_factory(qpol_policy, name): + """Factory function for creating attribute objects.""" + + if isinstance(name, TypeAttribute): + assert name.policy == qpol_policy + return name + + qpol_symbol = _symbol_lookup(qpol_policy, name) + + if not qpol_symbol.isattr(qpol_policy): + raise TypeError("{0} is a type".format(qpol_symbol.name(qpol_policy))) + + return TypeAttribute(qpol_policy, qpol_symbol) + + +def type_factory(qpol_policy, name, deref=False): + """Factory function for creating type objects.""" + + if isinstance(name, Type): + assert name.policy == qpol_policy + return name + + qpol_symbol = _symbol_lookup(qpol_policy, name) + + if qpol_symbol.isattr(qpol_policy): + raise TypeError("{0} is an attribute".format(qpol_symbol.name(qpol_policy))) + elif qpol_symbol.isalias(qpol_policy) and not deref: + raise TypeError("{0} is an alias.".format(qpol_symbol.name(qpol_policy))) + + return Type(qpol_policy, qpol_symbol) + + +def type_or_attr_factory(qpol_policy, name, deref=False): + """Factory function for creating type or attribute objects.""" + + if isinstance(name, (Type, TypeAttribute)): + assert name.policy == qpol_policy + return name + + qpol_symbol = _symbol_lookup(qpol_policy, name) + + if qpol_symbol.isalias(qpol_policy) and not deref: + raise TypeError("{0} is an alias.".format(qpol_symbol.name(qpol_policy))) + + if qpol_symbol.isattr(qpol_policy): + return TypeAttribute(qpol_policy, qpol_symbol) + else: + return Type(qpol_policy, qpol_symbol) + + +class BaseType(symbol.PolicySymbol): + + """Type/attribute base class.""" + + @property + def ispermissive(self): + raise NotImplementedError + + def expand(self): + """Generator that expands this attribute into its member types.""" + raise NotImplementedError + + def attributes(self): + """Generator that yields all attributes for this type.""" + raise NotImplementedError + + def aliases(self): + """Generator that yields all aliases for this type.""" + raise NotImplementedError + + +class Type(BaseType): + + """A type.""" + + @property + def ispermissive(self): + """(T/F) the type is permissive.""" + return self.qpol_symbol.ispermissive(self.policy) + + def expand(self): + """Generator that expands this into its member types.""" + yield self + + def attributes(self): + """Generator that yields all attributes for this type.""" + for attr in self.qpol_symbol.attr_iter(self.policy): + yield attribute_factory(self.policy, attr) + + def aliases(self): + """Generator that yields all aliases for this type.""" + for alias in self.qpol_symbol.alias_iter(self.policy): + yield alias + + def statement(self): + attrs = list(self.attributes()) + aliases = list(self.aliases()) + stmt = "type {0}".format(self) + if aliases: + if len(aliases) > 1: + stmt += " alias {{ {0} }}".format(' '.join(aliases)) + else: + stmt += " alias {0}".format(aliases[0]) + for attr in attrs: + stmt += ", {0}".format(attr) + stmt += ";" + return stmt + + +class TypeAttribute(BaseType): + + """An attribute.""" + + def __contains__(self, other): + for type_ in self.expand(): + if other == type_: + return True + + return False + + def expand(self): + """Generator that expands this attribute into its member types.""" + for type_ in self.qpol_symbol.type_iter(self.policy): + yield type_factory(self.policy, type_) + + def attributes(self): + """Generator that yields all attributes for this type.""" + raise TypeError("{0} is an attribute, thus does not have attributes.".format(self)) + + def aliases(self): + """Generator that yields all aliases for this type.""" + raise TypeError("{0} is an attribute, thus does not have aliases.".format(self)) + + @property + def ispermissive(self): + """(T/F) the type is permissive.""" + raise TypeError("{0} is an attribute, thus cannot be permissive.".format(self)) + + def statement(self): + return "attribute {0};".format(self) diff --git a/lib/python2.7/site-packages/setoolsgui/setools/policyrep/user.py b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/user.py new file mode 100644 index 0000000..94f81bc --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/policyrep/user.py @@ -0,0 +1,86 @@ +# Copyright 2014, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +from . import exception +from . import qpol +from . import role +from . import mls +from . import symbol + + +def user_factory(qpol_policy, name): + """Factory function for creating User objects.""" + + if isinstance(name, User): + assert name.policy == qpol_policy + return name + elif isinstance(name, qpol.qpol_user_t): + return User(qpol_policy, name) + + try: + return User(qpol_policy, qpol.qpol_user_t(qpol_policy, str(name))) + except ValueError: + raise exception.InvalidUser("{0} is not a valid user".format(name)) + + +class User(symbol.PolicySymbol): + + """A user.""" + + @property + def roles(self): + """The user's set of roles.""" + + roleset = set() + + for role_ in self.qpol_symbol.role_iter(self.policy): + item = role.role_factory(self.policy, role_) + + # object_r is implicitly added to all roles by the compiler. + # technically it is incorrect to skip it, but policy writers + # and analysts don't expect to see it in results, and it + # will confuse, especially for role set equality user queries. + if item != "object_r": + roleset.add(item) + + return roleset + + @property + def mls_level(self): + """The user's default MLS level.""" + return mls.level_factory(self.policy, self.qpol_symbol.dfltlevel(self.policy)) + + @property + def mls_range(self): + """The user's MLS range.""" + return mls.range_factory(self.policy, self.qpol_symbol.range(self.policy)) + + def statement(self): + roles = list(str(r) for r in self.roles) + stmt = "user {0} roles ".format(self) + if len(roles) > 1: + stmt += "{{ {0} }}".format(' '.join(roles)) + else: + stmt += roles[0] + + try: + stmt += " level {0.mls_level} range {0.mls_range};".format(self) + except exception.MLSDisabled: + stmt += ";" + + return stmt diff --git a/lib/python2.7/site-packages/setoolsgui/setools/portconquery.py b/lib/python2.7/site-packages/setoolsgui/setools/portconquery.py new file mode 100644 index 0000000..798a828 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/portconquery.py @@ -0,0 +1,146 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +from socket import IPPROTO_TCP, IPPROTO_UDP + +from . import contextquery +from .policyrep.netcontext import port_range + + +class PortconQuery(contextquery.ContextQuery): + + """ + Port context query. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + protocol The protocol to match (socket.IPPROTO_TCP for + TCP or socket.IPPROTO_UDP for UDP) + + ports A 2-tuple of the port range to match. (Set both to + the same value for a single port) + ports_subset If true, the criteria will match if it is a subset + of the portcon's range. + ports_overlap If true, the criteria will match if it overlaps + any of the portcon's range. + ports_superset If true, the criteria will match if it is a superset + of the portcon's range. + ports_proper If true, use proper superset/subset operations. + No effect if not using set operations. + + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + _protocol = None + _ports = None + ports_subset = False + ports_overlap = False + ports_superset = False + ports_proper = False + + @property + def ports(self): + return self._ports + + @ports.setter + def ports(self, value): + pending_ports = port_range(*value) + + if all(pending_ports): + if pending_ports.low < 1 or pending_ports.high < 1: + raise ValueError("Port numbers must be positive: {0.low}-{0.high}". + format(pending_ports)) + + if pending_ports.low > pending_ports.high: + raise ValueError( + "The low port must be smaller than the high port: {0.low}-{0.high}". + format(pending_ports)) + + self._ports = pending_ports + else: + self._ports = None + + @property + def protocol(self): + return self._protocol + + @protocol.setter + def protocol(self, value): + if value: + if not (value == IPPROTO_TCP or value == IPPROTO_UDP): + raise ValueError( + "The protocol must be {0} for TCP or {1} for UDP.". + format(IPPROTO_TCP, IPPROTO_UDP)) + + self._protocol = value + else: + self._protocol = None + + def results(self): + """Generator which yields all matching portcons.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ports: {0.ports}, overlap: {0.ports_overlap}, " + "subset: {0.ports_subset}, superset: {0.ports_superset}, " + "proper: {0.ports_proper}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for portcon in self.policy.portcons(): + + if self.ports and not self._match_range( + portcon.ports, + self.ports, + self.ports_subset, + self.ports_overlap, + self.ports_superset, + self.ports_proper): + continue + + if self.protocol and self.protocol != portcon.protocol: + continue + + if not self._match_context(portcon.context): + continue + + yield portcon diff --git a/lib/python2.7/site-packages/setoolsgui/setools/query.py b/lib/python2.7/site-packages/setoolsgui/setools/query.py new file mode 100644 index 0000000..358a095 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/query.py @@ -0,0 +1,192 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + + +class PolicyQuery(object): + + """Base class for SELinux policy queries.""" + + def __init__(self, policy, **kwargs): + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + + # keys are sorted in reverse order so regex settings + # are set before the criteria, e.g. name_regex + # is set before name. This ensures correct behavior + # since the criteria descriptors are sensitve to + # regex settings. + for name in sorted(kwargs.keys(), reverse=True): + attr = getattr(self, name, None) # None is not callable + if callable(attr): + raise ValueError("Keyword parameter {0} conflicts with a callable.".format(name)) + + setattr(self, name, kwargs[name]) + + @staticmethod + def _match_regex(obj, criteria, regex): + """ + Match the object with optional regular expression. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + """ + + if regex: + return bool(criteria.search(str(obj))) + else: + return obj == criteria + + @staticmethod + def _match_set(obj, criteria, equal): + """ + Match the object (a set) with optional set equality. + + Parameters: + obj The object to match. (a set) + criteria The criteria to match. (a set) + equal If set equality should be used. Otherwise + any set intersection will match. + """ + + if equal: + return obj == criteria + else: + return bool(obj.intersection(criteria)) + + @staticmethod + def _match_in_set(obj, criteria, regex): + """ + Match if the criteria is in the list, with optional + regular expression matching. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + """ + + if regex: + return [m for m in obj if criteria.search(str(m))] + else: + return criteria in obj + + @staticmethod + def _match_indirect_regex(obj, criteria, indirect, regex): + """ + Match the object with optional regular expression and indirection. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + indirect If object indirection should be used, e.g. + expanding an attribute. + """ + + if indirect: + return PolicyQuery._match_in_set((obj.expand()), criteria, regex) + else: + return PolicyQuery._match_regex(obj, criteria, regex) + + @staticmethod + def _match_regex_or_set(obj, criteria, equal, regex): + """ + Match the object (a set) with either set comparisons + (equality or intersection) or by regex matching of the + set members. Regular expression matching will override + the set equality option. + + Parameters: + obj The object to match. (a set) + criteria The criteria to match. + equal If set equality should be used. Otherwise + any set intersection will match. Ignored + if regular expression matching is used. + regex If regular expression matching should be used. + """ + + if regex: + return [m for m in obj if criteria.search(str(m))] + else: + return PolicyQuery._match_set(obj, set(criteria), equal) + + @staticmethod + def _match_range(obj, criteria, subset, overlap, superset, proper): + """ + Match ranges of objects. + + obj An object with attributes named "low" and "high", representing the range. + criteria An object with attributes named "low" and "high", representing the criteria. + subset If true, the criteria will match if it is a subset obj's range. + overlap If true, the criteria will match if it overlaps any of the obj's range. + superset If true, the criteria will match if it is a superset of the obj's range. + proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + if overlap: + return ((obj.low <= criteria.low <= obj.high) or ( + obj.low <= criteria.high <= obj.high) or ( + criteria.low <= obj.low and obj.high <= criteria.high)) + elif subset: + if proper: + return ((obj.low < criteria.low and criteria.high <= obj.high) or ( + obj.low <= criteria.low and criteria.high < obj.high)) + else: + return obj.low <= criteria.low and criteria.high <= obj.high + elif superset: + if proper: + return ((criteria.low < obj.low and obj.high <= criteria.high) or ( + criteria.low <= obj.low and obj.high < criteria.high)) + else: + return (criteria.low <= obj.low and obj.high <= criteria.high) + else: + return criteria.low == obj.low and obj.high == criteria.high + + @staticmethod + def _match_level(obj, criteria, dom, domby, incomp): + """ + Match the an MLS level. + + obj The level to match. + criteria The criteria to match. (a level) + dom If true, the criteria will match if it dominates obj. + domby If true, the criteria will match if it is dominated by obj. + incomp If true, the criteria will match if it is incomparable to obj. + """ + + if dom: + return (criteria >= obj) + elif domby: + return (criteria <= obj) + elif incomp: + return (criteria ^ obj) + else: + return (criteria == obj) + + def results(self): + """ + Generator which returns the matches for the query. This method + should be overridden by subclasses. + """ + raise NotImplementedError diff --git a/lib/python2.7/site-packages/setoolsgui/setools/rbacrulequery.py b/lib/python2.7/site-packages/setoolsgui/setools/rbacrulequery.py new file mode 100644 index 0000000..240b921 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/rbacrulequery.py @@ -0,0 +1,147 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor +from .policyrep.exception import InvalidType, RuleUseError + + +class RBACRuleQuery(mixins.MatchObjClass, query.PolicyQuery): + + """ + Query the RBAC rules. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source role/attribute to match. + source_indirect If true, members of an attribute will be + matched rather than the attribute itself. + source_regex If true, regular expression matching will + be used on the source role/attribute. + Obeys the source_indirect option. + target The name of the target role/attribute to match. + target_indirect If true, members of an attribute will be + matched rather than the attribute itself. + target_regex If true, regular expression matching will + be used on the target role/attribute. + Obeys target_indirect option. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + default The name of the default role to match. + default_regex If true, regular expression matching will + be used on the default role. + """ + + ruletype = RuletypeDescriptor("validate_rbac_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_role") + source_regex = False + source_indirect = True + _target = None + target_regex = False + target_indirect = True + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + default = CriteriaDescriptor("default_regex", "lookup_role") + default_regex = False + + @property + def target(self): + return self._target + + @target.setter + def target(self, value): + if not value: + self._target = None + elif self.target_regex: + self._target = re.compile(value) + else: + try: + self._target = self.policy.lookup_type_or_attr(value) + except InvalidType: + self._target = self.policy.lookup_role(value) + + def results(self): + """Generator which yields all matching RBAC rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Source: {0.source!r}, indirect: {0.source_indirect}, " + "regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, indirect: {0.target_indirect}, " + "regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Default: {0.default!r}, regex: {0.default_regex}".format(self)) + + for rule in self.policy.rbacrules(): + # + # Matching on rule type + # + if self.ruletype: + if rule.ruletype not in self.ruletype: + continue + + # + # Matching on source role + # + if self.source and not self._match_indirect_regex( + rule.source, + self.source, + self.source_indirect, + self.source_regex): + continue + + # + # Matching on target type (role_transition)/role(allow) + # + if self.target and not self._match_indirect_regex( + rule.target, + self.target, + self.target_indirect, + self.target_regex): + continue + + # + # Matching on object class + # + try: + if not self._match_object_class(rule): + continue + except RuleUseError: + continue + + # + # Matching on default role + # + if self.default: + try: + if not self._match_regex( + rule.default, + self.default, + self.default_regex): + continue + except RuleUseError: + continue + + # if we get here, we have matched all available criteria + yield rule diff --git a/lib/python2.7/site-packages/setoolsgui/setools/rolequery.py b/lib/python2.7/site-packages/setoolsgui/setools/rolequery.py new file mode 100644 index 0000000..e95dfa6 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/rolequery.py @@ -0,0 +1,77 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaSetDescriptor + + +class RoleQuery(compquery.ComponentQuery): + + """ + Query SELinux policy roles. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The role name to match. + name_regex If true, regular expression matching + will be used on the role names. + types The type to match. + types_equal If true, only roles with type sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + types_regex If true, regular expression matching + will be used on the type names instead + of set logic. + """ + + types = CriteriaSetDescriptor("types_regex", "lookup_type") + types_equal = False + types_regex = False + + def results(self): + """Generator which yields all matching roles.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Types: {0.types!r}, regex: {0.types_regex}, " + "eq: {0.types_equal}".format(self)) + + for r in self.policy.roles(): + if r == "object_r": + # all types are implicitly added to object_r by the compiler. + # technically it is incorrect to skip it, but policy writers + # and analysts don't expect to see it in results, and it + # will confuse, especially for set equality type queries. + continue + + if not self._match_name(r): + continue + + if self.types and not self._match_regex_or_set( + set(r.types()), + self.types, + self.types_equal, + self.types_regex): + continue + + yield r diff --git a/lib/python2.7/site-packages/setoolsgui/setools/sensitivityquery.py b/lib/python2.7/site-packages/setoolsgui/setools/sensitivityquery.py new file mode 100644 index 0000000..a102836 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/sensitivityquery.py @@ -0,0 +1,74 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging + +from . import compquery +from . import mixins +from .descriptors import CriteriaDescriptor + + +class SensitivityQuery(mixins.MatchAlias, compquery.ComponentQuery): + + """ + Query MLS Sensitivities + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The name of the category to match. + name_regex If true, regular expression matching will + be used for matching the name. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + sens The criteria to match the sensitivity by dominance. + sens_dom If true, the criteria will match if it dominates + the sensitivity. + sens_domby If true, the criteria will match if it is dominated + by the sensitivity. + """ + + sens = CriteriaDescriptor(lookup_function="lookup_sensitivity") + sens_dom = False + sens_domby = False + + def results(self): + """Generator which yields all matching sensitivities.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + self.log.debug("Sens: {0.sens!r}, dom: {0.sens_dom}, domby: {0.sens_domby}".format(self)) + + for s in self.policy.sensitivities(): + if not self._match_name(s): + continue + + if not self._match_alias(s): + continue + + if self.sens and not self._match_level( + s, + self.sens, + self.sens_dom, + self.sens_domby, + False): + continue + + yield s diff --git a/lib/python2.7/site-packages/setoolsgui/setools/terulequery.py b/lib/python2.7/site-packages/setoolsgui/setools/terulequery.py new file mode 100644 index 0000000..7f3eccf --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/terulequery.py @@ -0,0 +1,178 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor +from .policyrep.exception import RuleUseError, RuleNotConditional + + +class TERuleQuery(mixins.MatchObjClass, mixins.MatchPermission, query.PolicyQuery): + + """ + Query the Type Enforcement rules. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source type/attribute to match. + source_indirect If true, members of an attribute will be + matched rather than the attribute itself. + Default is true. + source_regex If true, regular expression matching will + be used on the source type/attribute. + Obeys the source_indirect option. + Default is false. + target The name of the target type/attribute to match. + target_indirect If true, members of an attribute will be + matched rather than the attribute itself. + Default is true. + target_regex If true, regular expression matching will + be used on the target type/attribute. + Obeys target_indirect option. + Default is false. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + Default is false. + perms The set of permission(s) to match. + perms_equal If true, the permission set of the rule + must exactly match the permissions + criteria. If false, any set intersection + will match. + Default is false. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + default The name of the default type to match. + default_regex If true, regular expression matching will be + used on the default type. + Default is false. + boolean The set of boolean(s) to match. + boolean_regex If true, regular expression matching will be + used on the booleans. + Default is false. + boolean_equal If true, the booleans in the conditional + expression of the rule must exactly match the + criteria. If false, any set intersection + will match. Default is false. + """ + + ruletype = RuletypeDescriptor("validate_te_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_type_or_attr") + source_regex = False + source_indirect = True + target = CriteriaDescriptor("target_regex", "lookup_type_or_attr") + target_regex = False + target_indirect = True + default = CriteriaDescriptor("default_regex", "lookup_type") + default_regex = False + boolean = CriteriaSetDescriptor("boolean_regex", "lookup_boolean") + boolean_regex = False + boolean_equal = False + + def results(self): + """Generator which yields all matching TE rules.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Ruletypes: {0.ruletype}".format(self)) + self.log.debug("Source: {0.source!r}, indirect: {0.source_indirect}, " + "regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, indirect: {0.target_indirect}, " + "regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + self.log.debug("Default: {0.default!r}, regex: {0.default_regex}".format(self)) + self.log.debug("Boolean: {0.boolean!r}, eq: {0.boolean_equal}, " + "regex: {0.boolean_regex}".format(self)) + + for rule in self.policy.terules(): + # + # Matching on rule type + # + if self.ruletype: + if rule.ruletype not in self.ruletype: + continue + + # + # Matching on source type + # + if self.source and not self._match_indirect_regex( + rule.source, + self.source, + self.source_indirect, + self.source_regex): + continue + + # + # Matching on target type + # + if self.target and not self._match_indirect_regex( + rule.target, + self.target, + self.target_indirect, + self.target_regex): + continue + + # + # Matching on object class + # + if not self._match_object_class(rule): + continue + + # + # Matching on permission set + # + try: + if not self._match_perms(rule): + continue + except RuleUseError: + continue + + # + # Matching on default type + # + if self.default: + try: + if not self._match_regex( + rule.default, + self.default, + self.default_regex): + continue + except RuleUseError: + continue + + # + # Match on Boolean in conditional expression + # + if self.boolean: + try: + if not self._match_regex_or_set( + rule.conditional.booleans, + self.boolean, + self.boolean_equal, + self.boolean_regex): + continue + except RuleNotConditional: + continue + + # if we get here, we have matched all available criteria + yield rule diff --git a/lib/python2.7/site-packages/setoolsgui/setools/typeattrquery.py b/lib/python2.7/site-packages/setoolsgui/setools/typeattrquery.py new file mode 100644 index 0000000..a91026c --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/typeattrquery.py @@ -0,0 +1,70 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaSetDescriptor + + +class TypeAttributeQuery(compquery.ComponentQuery): + + """ + Query SELinux policy type attributes. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The type name to match. + name_regex If true, regular expression matching + will be used on the type names. + types The type to match. + types_equal If true, only attributes with type sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + types_regex If true, regular expression matching + will be used on the type names instead + of set logic. + """ + + types = CriteriaSetDescriptor("types_regex", "lookup_type") + types_equal = False + types_regex = False + + def results(self): + """Generator which yields all matching types.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Types: {0.types!r}, regex: {0.types_regex}, " + "eq: {0.types_equal}".format(self)) + + for attr in self.policy.typeattributes(): + if not self._match_name(attr): + continue + + if self.types and not self._match_regex_or_set( + set(attr.expand()), + self.types, + self.types_equal, + self.types_regex): + continue + + yield attr diff --git a/lib/python2.7/site-packages/setoolsgui/setools/typequery.py b/lib/python2.7/site-packages/setoolsgui/setools/typequery.py new file mode 100644 index 0000000..6634f76 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/typequery.py @@ -0,0 +1,96 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from . import mixins +from .descriptors import CriteriaSetDescriptor + + +class TypeQuery(mixins.MatchAlias, compquery.ComponentQuery): + + """ + Query SELinux policy types. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The type name to match. + name_regex If true, regular expression matching + will be used on the type names. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + attrs The attribute to match. + attrs_equal If true, only types with attribute sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + attrs_regex If true, regular expression matching + will be used on the attribute names instead + of set logic. + permissive The permissive state to match. If this + is None, the state is not matched. + """ + + attrs = CriteriaSetDescriptor("attrs_regex", "lookup_typeattr") + attrs_regex = False + attrs_equal = False + _permissive = None + + @property + def permissive(self): + return self._permissive + + @permissive.setter + def permissive(self, value): + if value is None: + self._permissive = None + else: + self._permissive = bool(value) + + def results(self): + """Generator which yields all matching types.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + self.log.debug("Attrs: {0.attrs!r}, regex: {0.attrs_regex}, " + "eq: {0.attrs_equal}".format(self)) + self.log.debug("Permissive: {0.permissive}".format(self)) + + for t in self.policy.types(): + if not self._match_name(t): + continue + + if not self._match_alias(t): + continue + + if self.attrs and not self._match_regex_or_set( + set(t.attributes()), + self.attrs, + self.attrs_equal, + self.attrs_regex): + continue + + if self.permissive is not None and t.ispermissive != self.permissive: + continue + + yield t diff --git a/lib/python2.7/site-packages/setoolsgui/setools/userquery.py b/lib/python2.7/site-packages/setoolsgui/setools/userquery.py new file mode 100644 index 0000000..00910cf --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/setools/userquery.py @@ -0,0 +1,116 @@ +# Copyright 2014-2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import logging +import re + +from . import compquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + + +class UserQuery(compquery.ComponentQuery): + + """ + Query SELinux policy users. + + Parameter: + policy The policy to query. + + Keyword Parameters/Class attributes: + name The user name to match. + name_regex If true, regular expression matching + will be used on the user names. + roles The attribute to match. + roles_equal If true, only types with role sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + roles_regex If true, regular expression matching + will be used on the role names instead + of set logic. + level The criteria to match the user's default level. + level_dom If true, the criteria will match if it dominates + the user's default level. + level_domby If true, the criteria will match if it is dominated + by the user's default level. + level_incomp If true, the criteria will match if it is incomparable + to the user's default level. + range_ The criteria to match the user's range. + range_subset If true, the criteria will match if it is a subset + of the user's range. + range_overlap If true, the criteria will match if it overlaps + any of the user's range. + range_superset If true, the criteria will match if it is a superset + of the user's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + level = CriteriaDescriptor(lookup_function="lookup_level") + level_dom = False + level_domby = False + level_incomp = False + range_ = CriteriaDescriptor(lookup_function="lookup_range") + range_overlap = False + range_subset = False + range_superset = False + range_proper = False + roles = CriteriaSetDescriptor("roles_regex", "lookup_role") + roles_equal = False + roles_regex = False + + def results(self): + """Generator which yields all matching users.""" + self.log.info("Generating results from {0.policy}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Roles: {0.roles!r}, regex: {0.roles_regex}, " + "eq: {0.roles_equal}".format(self)) + self.log.debug("Level: {0.level!r}, dom: {0.level_dom}, domby: {0.level_domby}, " + "incomp: {0.level_incomp}".format(self)) + self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " + "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) + + for user in self.policy.users(): + if not self._match_name(user): + continue + + if self.roles and not self._match_regex_or_set( + user.roles, + self.roles, + self.roles_equal, + self.roles_regex): + continue + + if self.level and not self._match_level( + user.mls_level, + self.level, + self.level_dom, + self.level_domby, + self.level_incomp): + continue + + if self.range_ and not self._match_range( + user.mls_range, + self.range_, + self.range_subset, + self.range_overlap, + self.range_superset, + self.range_proper): + continue + + yield user diff --git a/lib/python2.7/site-packages/setoolsgui/widget.py b/lib/python2.7/site-packages/setoolsgui/widget.py new file mode 100644 index 0000000..25067a2 --- /dev/null +++ b/lib/python2.7/site-packages/setoolsgui/widget.py @@ -0,0 +1,37 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools 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. +# +# SETools is distributed in the hope that 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 SETools. If not, see +# . +# +import sys +from errno import ENOENT + +from PyQt5.uic import loadUi + + +class SEToolsWidget(object): + def load_ui(self, filename): + # If we are in the git repo, look at the local + # UI file, otherwise look at the installed file. + for path in ["data/", sys.prefix + "/share/setools/"]: + try: + loadUi(path + filename, self) + break + except (IOError, OSError) as err: + if err.errno != ENOENT: + raise + else: + raise RuntimeError("Unable to load Qt UI file \"{0}\"".format(filename)) -- GitLab

#D3B<{coS}4a2PR0S8poMbW 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