├── jgrapht ├── _internals │ ├── __init__.py │ ├── _anyhashableg_clustering.py │ ├── _anyhashableg_planar.py │ ├── _anyhashableg_wrappers.py │ ├── _planar.py │ ├── _clustering.py │ ├── _anyhashableg_drawing.py │ ├── _callbacks.py │ ├── _mapping.py │ ├── _attributes.py │ ├── _drawing.py │ ├── _ioutils.py │ └── _anyhashableg_mapping.py ├── drawing │ └── __init__.py ├── io │ └── __init__.py ├── algorithms │ ├── __init__.py │ ├── partition.py │ ├── independent.py │ ├── planar.py │ ├── clustering.py │ ├── connectivity.py │ ├── isomorphism.py │ ├── linkprediction.py │ ├── cuts.py │ ├── vertexcover.py │ ├── spanning.py │ └── cliques.py ├── __version__.py ├── utils.py └── metrics.py ├── requirements ├── docself.txt ├── default.txt ├── optional.txt ├── release.txt ├── test.txt ├── doc.txt └── README.md ├── requirements.txt ├── docs ├── _static │ └── logo.png ├── api │ ├── drawing │ │ ├── draw_matplotlib.rst │ │ └── index.rst │ ├── io │ │ ├── exporters.rst │ │ ├── index.rst │ │ ├── edgelist.rst │ │ └── importers.rst │ ├── algorithms │ │ ├── matching.rst │ │ ├── spanning.rst │ │ ├── coloring.rst │ │ ├── cliques.rst │ │ ├── independent.rst │ │ ├── scoring.rst │ │ ├── linkprediction.rst │ │ ├── drawing.rst │ │ ├── clustering.rst │ │ ├── cuts.rst │ │ ├── isomorphism.rst │ │ ├── planar.rst │ │ ├── cycles.rst │ │ ├── connectivity.rst │ │ ├── vertexcover.rst │ │ ├── index.rst │ │ ├── tour.rst │ │ ├── shortestpaths.rst │ │ └── flow.rst │ ├── utils.rst │ ├── traversals.rst │ ├── convert.rst │ ├── metrics.rst │ ├── properties.rst │ ├── views.rst │ ├── index.rst │ ├── generators.rst │ ├── interfaces.rst │ ├── graph_factories.rst │ └── introduction.rst ├── tutorials │ ├── index.rst │ ├── succinct_graph.rst │ └── sparse_graph.rst ├── credits.rst ├── Makefile ├── license.rst ├── install.rst ├── make.bat ├── citing.rst └── index.rst ├── .gitmodules ├── .coveragerc ├── examples ├── io │ ├── README.rst │ ├── plot_export_json.py │ ├── plot_import_sparse_graph.py │ └── plot_import_graphml.py ├── algorithms │ ├── README.rst │ ├── plot_enumerate_cliques.py │ ├── plot_pagerank.py │ ├── plot_vertex_cover.py │ ├── plot_connected_components.py │ ├── plot_coloring.py │ ├── plot_metric_tsp.py │ ├── plot_prim.py │ ├── plot_label_propagation.py │ └── plot_dijkstra.py ├── README.rst └── draw_matplotlib │ ├── README.rst │ ├── plot_draw_graph.py │ ├── plot_edge_colormap.py │ ├── plot_node_colormap.py │ ├── plot_draw_arrow.py │ ├── plot_draw_house.py │ ├── plot_weight_edges.py │ └── plot_labels_colors.py ├── .readthedocs.yml ├── MANIFEST.in ├── tox.ini ├── .github └── scripts │ ├── upload-pypi.sh │ └── build-manylinux2010_x86_64.sh ├── tests ├── test_backend_attributes.py ├── test_backend_drawing.py ├── test_spanning.py ├── test_edgelist_csv.py ├── test_independent.py ├── test_backend_intlist.py ├── test_utils.py ├── test_gomoryhu.py ├── test_eft.py ├── test_backend_intset.py ├── test_planar.py ├── test_drawing_random_layout_2d.py ├── test_edgelist_dot.py ├── test_drawing_circular_layout_2d.py ├── test_io_lemon.py ├── test_edgelist_sparse.py ├── test_link_prediction.py ├── test_mst.py ├── test_clustering.py ├── test_vc.py ├── test_metrics.py ├── test_maxflow.py ├── test_contraction_hierarchies.py └── test_edgelist_gexf.py ├── RELEASE.md ├── CHANGELOG.md └── .gitignore /jgrapht/_internals/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements/docself.txt: -------------------------------------------------------------------------------- 1 | jgrapht 2 | -------------------------------------------------------------------------------- /requirements/default.txt: -------------------------------------------------------------------------------- 1 | pylint>=2.4 2 | -------------------------------------------------------------------------------- /requirements/optional.txt: -------------------------------------------------------------------------------- 1 | networkx>=2.5 2 | pydot>=1.4 3 | -------------------------------------------------------------------------------- /jgrapht/drawing/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | draw_matplotlib 3 | ) 4 | -------------------------------------------------------------------------------- /requirements/release.txt: -------------------------------------------------------------------------------- 1 | twine>=2.0.0 2 | wheel>=0.33.6 3 | setuptools>=38.6.0 4 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | pytest>=5.2.1 2 | pytest-cov>=2.8.1 3 | codecov>=2.0.15 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/default.txt 2 | -r requirements/test.txt 3 | -e . 4 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-michail/python-jgrapht/HEAD/docs/_static/logo.png -------------------------------------------------------------------------------- /jgrapht/io/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | importers, 3 | exporters, 4 | edgelist, 5 | ) 6 | -------------------------------------------------------------------------------- /requirements/doc.txt: -------------------------------------------------------------------------------- 1 | sphinx==3.0.1 2 | sphinx-rtd-theme >= 0.4 3 | sphinx-gallery>=0.6 4 | matplotlib 5 | pillow 6 | nb2plots 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/source/jgrapht-capi"] 2 | path = vendor/source/jgrapht-capi 3 | url = https://github.com/jgrapht/jgrapht-capi.git 4 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | jgrapht/backend.py 4 | jgrapht/types.py 5 | tests/* 6 | .tox/* 7 | 8 | [report] 9 | ignore_errors = True 10 | -------------------------------------------------------------------------------- /examples/io/README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _general_examples/io: 3 | 4 | Input/Output 5 | ------------ 6 | 7 | This gallery shows some basic uses of importer/exporters. 8 | 9 | -------------------------------------------------------------------------------- /examples/algorithms/README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _general_examples/algorithms: 3 | 4 | Algorithms 5 | ---------- 6 | 7 | This gallery shows some basic uses of algorithms. 8 | 9 | -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _examples-index: 3 | 4 | Examples 5 | ======== 6 | 7 | .. _general_examples: 8 | 9 | This gallery shows basic usages of the Python-JGraphT library. 10 | 11 | -------------------------------------------------------------------------------- /docs/api/drawing/draw_matplotlib.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _drawing/draw_matplotlib: 3 | 4 | Draw Matplotlib 5 | **************** 6 | 7 | .. automodule:: jgrapht.drawing.draw_matplotlib 8 | :members: 9 | 10 | -------------------------------------------------------------------------------- /docs/api/io/exporters.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _io/exporters: 3 | 4 | Exporters 5 | ********* 6 | 7 | The following exporters are available: 8 | 9 | .. automodule:: jgrapht.io.exporters 10 | :members: 11 | 12 | -------------------------------------------------------------------------------- /docs/api/algorithms/matching.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/matching: 3 | 4 | Matching 5 | ******** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | .. automodule:: jgrapht.algorithms.matching 11 | :members: 12 | 13 | -------------------------------------------------------------------------------- /docs/api/algorithms/spanning.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/spanning: 3 | 4 | Spanning 5 | ******** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | .. automodule:: jgrapht.algorithms.spanning 11 | :members: 12 | 13 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _general_examples/draw_matplotlib: 3 | 4 | Drawing with Matplotlib 5 | ----------------------- 6 | 7 | This gallery shows basic usages of the drawing functionality. 8 | 9 | -------------------------------------------------------------------------------- /docs/api/utils.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _utils: 3 | 4 | .. currentmodule:: jgrapht.utils 5 | 6 | Utilities 7 | ********* 8 | 9 | This is just a collection of helpful utilities. 10 | 11 | .. automodule:: jgrapht.utils 12 | :members: 13 | -------------------------------------------------------------------------------- /docs/api/drawing/index.rst: -------------------------------------------------------------------------------- 1 | .. _drawing/index: 2 | 3 | Drawing 4 | ********** 5 | 6 | .. currentmodule:: jgrapht.drawing 7 | 8 | JGraphT drawing support 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | draw_matplotlib 14 | 15 | -------------------------------------------------------------------------------- /docs/api/algorithms/coloring.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/coloring: 3 | 4 | Coloring 5 | ******** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | The following algorithms are available: 11 | 12 | .. automodule:: jgrapht.algorithms.coloring 13 | :members: -------------------------------------------------------------------------------- /docs/api/algorithms/cliques.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/cliques: 3 | 4 | Cliques 5 | ******* 6 | 7 | Algorithms 8 | ---------- 9 | 10 | The following algorithms are available: 11 | 12 | .. automodule:: jgrapht.algorithms.cliques 13 | :members: 14 | 15 | -------------------------------------------------------------------------------- /docs/api/traversals.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _traversals: 3 | 4 | .. currentmodule:: jgrapht.traversal 5 | 6 | Traversals 7 | ********** 8 | 9 | Classic graph traversals in the form of a vertex iterator. 10 | 11 | .. automodule:: jgrapht.traversal 12 | :members: 13 | 14 | -------------------------------------------------------------------------------- /docs/api/convert.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _convert: 3 | 4 | .. currentmodule:: jgrapht.convert 5 | 6 | Conversions 7 | *********** 8 | 9 | A small collection of utility methods in order to convert from/to other graph formats. 10 | 11 | .. automodule:: jgrapht.convert 12 | :members: 13 | -------------------------------------------------------------------------------- /docs/api/algorithms/independent.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/independent: 3 | 4 | Independent Set 5 | *************** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | The following algorithms are available: 11 | 12 | .. automodule:: jgrapht.algorithms.independent 13 | :members: 14 | 15 | -------------------------------------------------------------------------------- /docs/api/metrics.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _metrics: 3 | 4 | .. currentmodule:: jgrapht.metrics 5 | 6 | Metrics 7 | ******* 8 | 9 | Certain graph metrics such as the diameter of a graph can be computed using the following functions. 10 | 11 | .. automodule:: jgrapht.metrics 12 | :members: 13 | -------------------------------------------------------------------------------- /docs/api/algorithms/scoring.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/scoring: 3 | 4 | Scoring 5 | ******* 6 | 7 | The algorithms in this module compute scores for the vertices of the graph. 8 | 9 | Algorithms 10 | ---------- 11 | 12 | .. automodule:: jgrapht.algorithms.scoring 13 | :members: 14 | 15 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Required 2 | version: 2 3 | 4 | # Build documentation in the docs/ directory with Sphinx 5 | sphinx: 6 | configuration: docs/conf.py 7 | 8 | python: 9 | version: 3.7 10 | install: 11 | - requirements: requirements/doc.txt 12 | - requirements: requirements/docself.txt 13 | 14 | -------------------------------------------------------------------------------- /docs/api/properties.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _properties: 3 | 4 | .. currentmodule:: jgrapht.properties 5 | 6 | Structural Properties 7 | ********************* 8 | 9 | The following functions allow the user to check for certain structural graph properties. 10 | 11 | .. automodule:: jgrapht.properties 12 | :members: 13 | -------------------------------------------------------------------------------- /docs/api/algorithms/linkprediction.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/linkprediction: 3 | 4 | Link Prediction 5 | *************** 6 | 7 | The algorithms in this module compute link prediction scores for pairs of vertices of a graph. 8 | 9 | Algorithms 10 | ---------- 11 | 12 | .. automodule:: jgrapht.algorithms.linkprediction 13 | :members: 14 | 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include license-*.txt 2 | include RELEASE.md 3 | include jgrapht/backend.h 4 | exclude jgrapht/backend.py 5 | graft docs 6 | prune docs/_build 7 | prune docs/auto_examples 8 | include requirements.txt 9 | graft requirements 10 | graft examples 11 | graft vendor/source/jgrapht-capi 12 | graft tests 13 | graft requirements 14 | global-exclude *.py[cod] 15 | -------------------------------------------------------------------------------- /docs/api/io/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _io/index: 3 | 4 | Input/Output 5 | ************ 6 | 7 | .. currentmodule:: jgrapht.io 8 | 9 | The JGraphT contains a large collection of importers/exporters. They can be used 10 | to read/write graph from files and strings. 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | importers 16 | exporters 17 | edgelist 18 | 19 | -------------------------------------------------------------------------------- /docs/api/views.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _views: 3 | 4 | .. currentmodule:: jgrapht.views 5 | 6 | Views 7 | ***** 8 | 9 | Graph views are wrappers around a graph which adjust its functionality. These 10 | views are live, meaning that changes in the original graph are reflected in the 11 | view and vice-versa. 12 | 13 | .. automodule:: jgrapht.views 14 | :members: 15 | 16 | -------------------------------------------------------------------------------- /docs/api/algorithms/drawing.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/drawing: 3 | 4 | Drawing 5 | ******* 6 | 7 | Algorithms 8 | ---------- 9 | 10 | .. automodule:: jgrapht.algorithms.drawing 11 | :members: 12 | 13 | Types 14 | ----- 15 | 16 | The result of the 2D drawing algorithms are instances of the following class. 17 | 18 | .. autoclass:: jgrapht.types.LayoutModel2D 19 | :members: -------------------------------------------------------------------------------- /docs/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | Tutorials 4 | ********* 5 | 6 | .. currentmodule:: jgrapht 7 | 8 | Three different tutorials help you start using the basic graph implementations that 9 | the |Bindings| provide. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | default_graph 15 | sparse_graph 16 | succinct_graph 17 | any_graph 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/api/algorithms/clustering.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/clustering: 3 | 4 | Clustering 5 | ********** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | .. automodule:: jgrapht.algorithms.clustering 11 | :members: 12 | 13 | Types 14 | ----- 15 | 16 | Vertex clusterings are represented using instances of the following class. 17 | 18 | .. autoclass:: jgrapht.types.Clustering 19 | :members: 20 | 21 | -------------------------------------------------------------------------------- /jgrapht/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | cliques, 3 | clustering, 4 | coloring, 5 | connectivity, 6 | cuts, 7 | cycles, 8 | drawing, 9 | flow, 10 | independent, 11 | isomorphism, 12 | linkprediction, 13 | matching, 14 | partition, 15 | planar, 16 | scoring, 17 | shortestpaths, 18 | spanning, 19 | tour, 20 | vertexcover, 21 | ) 22 | -------------------------------------------------------------------------------- /docs/api/io/edgelist.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _io/edgelist: 3 | 4 | Edgelist 5 | ******** 6 | 7 | These methods provide the importers' functionality without actually constructing a graph. 8 | They simply return an in-memory edge list. This is helpful for example in order to bulk 9 | load a sparse graph, or to perform some preprocessing before loading a graph. 10 | 11 | .. automodule:: jgrapht.io.edgelist 12 | :members: 13 | 14 | -------------------------------------------------------------------------------- /docs/api/algorithms/cuts.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/cuts: 3 | 4 | Cuts 5 | **** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | .. automodule:: jgrapht.algorithms.cuts 11 | :members: 12 | 13 | Types 14 | ----- 15 | 16 | Cuts are represented using instances of the following classes. 17 | 18 | .. autoclass:: jgrapht.types.Cut 19 | :members: 20 | 21 | .. autoclass:: jgrapht.types.GomoryHuTree 22 | :members: 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/api/algorithms/isomorphism.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/isomorphism: 3 | 4 | Isomorphism 5 | *********** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | .. automodule:: jgrapht.algorithms.isomorphism 11 | :members: 12 | 13 | 14 | Types 15 | ----- 16 | 17 | A mapping from one graph to another is represented using instances of the following class. 18 | 19 | .. autoclass:: jgrapht.types.GraphMapping 20 | :members: 21 | 22 | -------------------------------------------------------------------------------- /docs/api/algorithms/planar.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/planar: 3 | 4 | Planar 5 | ****** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | The following algorithms are available: 11 | 12 | .. automodule:: jgrapht.algorithms.planar 13 | :members: 14 | 15 | 16 | Types 17 | ----- 18 | 19 | Combinatorial embeddings are represented using instances of the following class. 20 | 21 | .. autoclass:: jgrapht.types.PlanarEmbedding 22 | :members: -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | *** 5 | 6 | :Release: |release| 7 | :Date: |today| 8 | :Backend Release: |BackendVersion| 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | introduction 14 | interfaces 15 | graph_factories 16 | generators 17 | views 18 | traversals 19 | properties 20 | metrics 21 | algorithms/index 22 | io/index 23 | convert 24 | utils 25 | drawing/index 26 | -------------------------------------------------------------------------------- /docs/api/algorithms/cycles.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/cycles: 3 | 4 | Cycles 5 | ****** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | The following algorithms are available: 11 | 12 | .. automodule:: jgrapht.algorithms.cycles 13 | :members: 14 | 15 | Types 16 | ----- 17 | 18 | Cycles are usually represented with instances of :py:class:`.GraphPath`. 19 | 20 | .. autoclass:: jgrapht.types.GraphPath 21 | :members: 22 | :noindex: 23 | 24 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36 3 | 4 | [testenv] 5 | usedevelop=True 6 | deps = pytest 7 | pytest-cov 8 | commands = pytest --cov --cov-report=term-missing --cov-config=.coveragerc {posargs} 9 | 10 | [testenv:build_docs] 11 | deps = -rrequirements/doc.txt 12 | extras = docs 13 | commands = sphinx-build docs docs/_build/html -W -b html {posargs} 14 | 15 | [testenv:lint] 16 | deps = -rrequirements/default.txt 17 | commands = pylint --rcfile=tox.ini jgrapht 18 | 19 | -------------------------------------------------------------------------------- /docs/api/algorithms/connectivity.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/connectivity: 3 | 4 | Connectivity 5 | ************ 6 | 7 | Algorithms 8 | ---------- 9 | 10 | Testing connectivity and finding connected components are classic tasks when working 11 | with graphs. Recall that depending on whether the graph is directed or not, we have 12 | two different notions of connectivity, i.e. strong and weak connectivity. 13 | 14 | .. automodule:: jgrapht.algorithms.connectivity 15 | :members: 16 | 17 | -------------------------------------------------------------------------------- /.github/scripts/upload-pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x 3 | 4 | echo "Current dir: `pwd`" 5 | echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE" 6 | 7 | ls $GITHUB_WORKSPACE/dist/* 8 | 9 | /opt/python/cp38-cp38/bin/python -m pip install -U pip 10 | /opt/python/cp38-cp38/bin/python -m pip install twine 11 | /opt/python/cp38-cp38/bin/python -m twine --version 12 | /opt/python/cp38-cp38/bin/python -m twine upload --repository testpypi --username "${PYPI_USER:-__token__}" --password "$PYPI_PASSWORD" dist/* 13 | 14 | -------------------------------------------------------------------------------- /docs/api/algorithms/vertexcover.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/vertexcover: 3 | 4 | Vertex Cover 5 | ************ 6 | 7 | Algorithms 8 | ---------- 9 | 10 | A vertex cover is a set of vertices which covers (touches) all edges of the graph. 11 | Finding a minimum vertex cover is an NP-Complete problem. This module contains various 12 | algorithms for the computation of "good" vertex covers. For more information, 13 | see `Vertex Cover `_. 14 | 15 | .. automodule:: jgrapht.algorithms.vertexcover 16 | :members: 17 | 18 | -------------------------------------------------------------------------------- /docs/api/algorithms/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/index: 3 | 4 | Algorithms 5 | ********** 6 | 7 | .. currentmodule:: jgrapht.algorithms 8 | 9 | The JGraphT contains a large collection of advanced algorithms. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | connectivity 15 | shortestpaths 16 | spanning 17 | cycles 18 | coloring 19 | clustering 20 | scoring 21 | linkprediction 22 | planar 23 | cuts 24 | matching 25 | flow 26 | drawing 27 | isomorphism 28 | tour 29 | cliques 30 | independent 31 | vertexcover 32 | 33 | -------------------------------------------------------------------------------- /tests/test_backend_attributes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht._internals._attributes import ( 4 | _JGraphTAttributeStore, 5 | _JGraphTAttributesRegistry, 6 | ) 7 | 8 | 9 | def test_store(): 10 | 11 | s = _JGraphTAttributeStore() 12 | 13 | s.put(0, 'color', 'red') 14 | s.put(1, 'color', 'blue') 15 | 16 | s.remove(0, 'color') 17 | 18 | repr(s) 19 | 20 | 21 | def test_registry(): 22 | 23 | s = _JGraphTAttributesRegistry() 24 | 25 | s.put('color', 'vertex', None, None) 26 | s.remove('color', 'vertex') 27 | 28 | repr(s) 29 | -------------------------------------------------------------------------------- /docs/credits.rst: -------------------------------------------------------------------------------- 1 | .. _credits: 2 | 3 | Credits 4 | ======= 5 | 6 | The `Python bindings of JGraphT `_ and 7 | the `JGraphT-capi `_ 8 | project have been written by `Dimitrios Michail `_. 9 | 10 | Refactoring of the build system and CI/CD pipelines for Linux, Windows, 11 | and MacOSX contributed by `Fotis Tsamis `_. 12 | 13 | For the authors of the backend JGraphT library, see https://jgrapht.org and 14 | https://github.com/jgrapht/jgrapht/blob/master/CONTRIBUTORS.md. 15 | 16 | -------------------------------------------------------------------------------- /docs/api/generators.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _generators: 3 | 4 | .. currentmodule:: jgrapht.generators 5 | 6 | Generators 7 | ********** 8 | 9 | This module contains functions which can generate graphs based on various famous models. 10 | All these generators assume that the user has already created an empty graph and calls 11 | the generator with the graph as parameter. This means that the provided graph must be 12 | able to support the particular generator. For example if the generator creates self-loops, 13 | then the provided graph object must also support self-loops. 14 | 15 | .. automodule:: jgrapht.generators 16 | :members: 17 | -------------------------------------------------------------------------------- /docs/api/io/importers.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _io/importers: 3 | 4 | Importers 5 | ********* 6 | 7 | The available importers can be seen below. Each of them accepts a graph object as its 8 | first parameter. The user is expected to first construct the graph object and then call the 9 | importer. This means that the graph object must be able to support the input. If for 10 | example the input contains self-loops, then the graph object must also support self-loops. 11 | This also means that reading an input file will result in different graphs if the graph 12 | is directed or undirected. 13 | 14 | .. automodule:: jgrapht.io.importers 15 | :members: 16 | 17 | -------------------------------------------------------------------------------- /docs/api/algorithms/tour.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/tour: 3 | 4 | Tour 5 | **** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | The travelling salesman problem (TSP) asks the following question: "Given a list of cities and 11 | the distances between each pair of cities, what is the shortest possible route that visits each 12 | city exactly once and returns to the origin city?". 13 | 14 | The following algorithms are available: 15 | 16 | .. automodule:: jgrapht.algorithms.tour 17 | :members: 18 | 19 | Types 20 | ----- 21 | 22 | Tours are represented with instances of :py:class:`.GraphPath`. 23 | 24 | .. autoclass:: jgrapht.types.GraphPath 25 | :members: 26 | :noindex: -------------------------------------------------------------------------------- /jgrapht/algorithms/partition.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import ( 3 | _wrap_vertex_set, 4 | ) 5 | 6 | 7 | def bipartite_partitions(graph): 8 | """Check whether a graph is bipartite and compute the partitions. 9 | 10 | The algorithm runs in linear time in the number of vertices and edges. 11 | 12 | :param graph: The input graph 13 | :returns: A tuple (result, partition1, partition2) 14 | """ 15 | res, part1, part2 = _backend.jgrapht_xx_partition_exec_bipartite(graph.handle) 16 | return ( 17 | res, 18 | _wrap_vertex_set(graph, part1), 19 | _wrap_vertex_set(graph, part2), 20 | ) 21 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = python-jgrapht 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /jgrapht/algorithms/independent.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | 3 | from .._internals._results import ( 4 | _wrap_vertex_set, 5 | ) 6 | 7 | 8 | def chordal_max_independent_set(graph): 9 | r"""Find a maximum independent set in a chordal graph. 10 | 11 | The algorithms first computes a perfect elimination ordering and then a 12 | maximum independent set. Running time :math:`\mathcal{O}(n+m)`. 13 | 14 | :param graph: the chordal graph. If the graph is not chordal an error is raised 15 | :returns: an independent set as a vertex set 16 | """ 17 | res = _backend.jgrapht_xx_independent_set_exec_chordal_max_independent_set( 18 | graph.handle 19 | ) 20 | return _wrap_vertex_set(graph, res) 21 | -------------------------------------------------------------------------------- /jgrapht/_internals/_anyhashableg_clustering.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | 3 | from ._clustering import _JGraphTIntegerClustering 4 | from ._anyhashableg_wrappers import _AnyHashableGraphVertexIterator 5 | 6 | 7 | class _AnyHashableGraphClustering(_JGraphTIntegerClustering): 8 | """An any-hashable graph vertex clustering.""" 9 | 10 | def __init__(self, handle, graph, **kwargs): 11 | super().__init__(handle=handle, **kwargs) 12 | self._graph = graph 13 | 14 | def ith_cluster(self, i): 15 | res = backend.jgrapht_xx_clustering_ith_cluster_vit(self._handle, i) 16 | return _AnyHashableGraphVertexIterator(res, self._graph) 17 | 18 | def __repr__(self): 19 | return "_AnyHashableGraphClustering(%r)" % self._handle 20 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. _license: 2 | 3 | License 4 | ======= 5 | 6 | The library follows the same licensing as the backend `JGraphT `_ library. 7 | It may be used under the terms of either the 8 | 9 | * GNU Lesser General Public License (LGPL) 2.1 https://www.gnu.org/licenses/lgpl-2.1.html 10 | 11 | or the 12 | 13 | * Eclipse Public License (EPL) https://www.eclipse.org/legal/epl-v20.html 14 | 15 | As a recipient of the |Bindings|, you may choose which license to receive the code under. 16 | Please note that the |Bindings| is distributed WITHOUT ANY WARRANTY; without even the implied warranty 17 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 18 | 19 | Please refer to the license for details. 20 | 21 | SPDX-License-Identifier: LGPL-2.1-or-later OR EPL-2.0 22 | 23 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Install 4 | ======= 5 | 6 | We automatically build 64-bit wheels for python versions 3.6, 3.7, 3.8, and 3.9 on Linux, 7 | Windows and MacOSX. For linux we use `PEP 571 `_ 8 | which means that pip version must be ``>= 19.0``. 9 | 10 | Thus, on a recent machine, installation should be as easy as:: 11 | 12 | $ pip install jgrapht 13 | 14 | If your pip version is older than 19.0 use:: 15 | 16 | $ pip install --upgrade pip 17 | $ pip install jgrapht 18 | 19 | If you want to use `virtualenv` or `venv` module:: 20 | 21 | $ python -m venv venv 22 | $ source venv/bin/activate 23 | $ pip install --upgrade pip 24 | $ pip install jgrapht 25 | 26 | Installation in the user directory is also possible:: 27 | 28 | $ pip install --user jgrapht 29 | 30 | -------------------------------------------------------------------------------- /tests/test_backend_drawing.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import jgrapht._backend as _backend 4 | 5 | from jgrapht._internals._drawing import ( 6 | _JGraphTIntegerLayoutModel2D 7 | ) 8 | 9 | 10 | def test_layout_model_2d(): 11 | handle = _backend.jgrapht_xx_drawing_layout_model_2d_create(1.0, 1.0, 9.0, 9.0) 12 | model = _JGraphTIntegerLayoutModel2D(handle) 13 | 14 | 15 | model.set_vertex_location(0, (5.0, 7.5)) 16 | assert model.get_vertex_location(0) == (5.0, 7.5) 17 | assert not model.is_fixed(0) 18 | model.set_fixed(0, True) 19 | assert model.is_fixed(0) 20 | model.set_vertex_location(0, (7.0, 8.0)) 21 | assert model.get_vertex_location(0) == (5.0, 7.5) 22 | model.set_fixed(0, False) 23 | assert not model.is_fixed(0) 24 | model.set_vertex_location(0, (7.0, 8.0)) 25 | assert model.get_vertex_location(0) == (7.0, 8.0) 26 | 27 | repr(model) 28 | -------------------------------------------------------------------------------- /jgrapht/__version__.py: -------------------------------------------------------------------------------- 1 | __title__ = "JGraphT" 2 | __description__ = "JGraphT library" 3 | __url__ = "https://github.com/d-michail/python-jgrapht" 4 | __version__ = "1.5.1.0.dev" 5 | __backend_version__ = "1.5.1" 6 | __author__ = "Dimitrios Michail" 7 | __author_email__ = "dimitrios.michail@gmail.com" 8 | __license__ = "LGPL-2.1-or-later OR EPL-2.0" 9 | __copyright__ = "2020-2021 Dimitrios Michail" 10 | __bibtex__ = """@article{jgrapht, 11 | title = {{J}{G}raph{T}--{A} {J}ava {L}ibrary for {G}raph {D}ata {S}tructures and {A}lgorithms}, 12 | author = {Michail, Dimitrios and Kinable, Joris and Naveh, Barak and Sichi, John V.}, 13 | year = {2020}, 14 | issue_date = {May 2020}, 15 | publisher = {Association for Computing Machinery}, 16 | address = {New York, NY, USA}, 17 | volume = {46}, 18 | number = {2}, 19 | journal = {ACM Trans. Math. Softw.}, 20 | month = may, 21 | articleno = {16}, 22 | numpages = {29}, 23 | }""" 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=python-jgrapht 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /jgrapht/_internals/_anyhashableg_planar.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | from ..types import PlanarEmbedding 3 | 4 | from ._wrappers import _HandleWrapper 5 | 6 | from ._anyhashableg import _vertex_anyhashableg_to_g 7 | from ._anyhashableg_wrappers import _AnyHashableGraphEdgeIterator 8 | 9 | 10 | class _AnyHashableGraphPlanarEmbedding(_HandleWrapper, PlanarEmbedding): 11 | """An any-hashable graph planar embedding.""" 12 | 13 | def __init__(self, handle, graph, **kwargs): 14 | super().__init__(handle=handle, **kwargs) 15 | self._graph = graph 16 | 17 | def edges_around(self, vertex): 18 | vertex = _vertex_anyhashableg_to_g(self._graph, vertex) 19 | res = backend.jgrapht_ix_planarity_embedding_edges_around_vertex( 20 | self._handle, vertex 21 | ) 22 | return list(_AnyHashableGraphEdgeIterator(res, self._graph)) 23 | 24 | def __repr__(self): 25 | return "_AnyHashableGraphPlanarEmbedding(%r)" % self._handle 26 | -------------------------------------------------------------------------------- /docs/citing.rst: -------------------------------------------------------------------------------- 1 | .. _citing: 2 | 3 | Citing 4 | ====== 5 | 6 | If you use this library, we would appreciate it if you cite the corresponding 7 | publication. Citing software is just as important as citing any other important 8 | sources in your research. If you’re not sure whether or not to cite something, 9 | `Shouldacite `_ can help you decide if you should. 10 | 11 | Below you can find the bibtex entry:: 12 | 13 | @article{jgrapht, 14 | title = {{J}{G}raph{T}--{A} {J}ava {L}ibrary for {G}raph {D}ata {S}tructures and {A}lgorithms}, 15 | author = {Michail, Dimitrios and Kinable, Joris and Naveh, Barak and Sichi, John V.}, 16 | year = {2020}, 17 | issue_date = {May 2020}, 18 | publisher = {Association for Computing Machinery}, 19 | address = {New York, NY, USA}, 20 | volume = {46}, 21 | number = {2}, 22 | journal = {ACM Trans. Math. Softw.}, 23 | month = may, 24 | articleno = {16}, 25 | numpages = {29}, 26 | } 27 | 28 | -------------------------------------------------------------------------------- /docs/api/algorithms/shortestpaths.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/shortestpaths: 3 | 4 | .. py:module:: jgrapht.algorithms.shortestpaths 5 | 6 | Shortest Paths 7 | ************** 8 | 9 | Algorithms 10 | ---------- 11 | 12 | 13 | All classic algorithms for shortest paths are provided. See below for details. 14 | 15 | .. automodule:: jgrapht.algorithms.shortestpaths 16 | :members: 17 | :noindex: 18 | 19 | Types 20 | ----- 21 | 22 | The shortest path module contains the following classes for the 23 | representation of the shortest path queries. All methods related to shortest paths 24 | return instances from these classes. 25 | 26 | .. autoclass:: jgrapht.types.GraphPath 27 | :members: 28 | 29 | .. autoclass:: jgrapht.types.SingleSourcePaths 30 | :members: 31 | 32 | .. autoclass:: jgrapht.types.AllPairsPaths 33 | :members: 34 | 35 | .. autoclass:: jgrapht.types.MultiObjectiveSingleSourcePaths 36 | :members: 37 | 38 | .. autoclass:: jgrapht.types.ManyToManyPaths 39 | :members: 40 | 41 | 42 | -------------------------------------------------------------------------------- /requirements/README.md: -------------------------------------------------------------------------------- 1 | # pip requirements files 2 | 3 | ## Index 4 | 5 | - [`default.txt`](default.txt) 6 | Default requirements 7 | - [`test.txt`](test.txt) 8 | Requirements for running test suite 9 | - [`optional.txt`](optional.txt) 10 | Optional requirements for integration with other libraries 11 | - [`doc.txt`](doc.txt) 12 | Requirements for building the documentation (see `../doc/`) 13 | - [`release.txt`](release.txt) 14 | Requirements for making releases 15 | 16 | ## Examples 17 | 18 | ### Installing requirements 19 | 20 | ```bash 21 | $ pip install -U -r requirements/default.txt 22 | ``` 23 | 24 | ### Running the tests 25 | 26 | ```bash 27 | $ pip install -U -r requirements/default.txt 28 | $ pip install -U -r requirements/test.txt 29 | ``` 30 | 31 | ### Running the tests including optional integration 32 | 33 | ```bash 34 | $ pip install -U -r requirements/default.txt 35 | $ pip install -U -r requirements/test.txt 36 | $ pip install -U -r requirements/optional.txt 37 | ``` 38 | 39 | 40 | -------------------------------------------------------------------------------- /jgrapht/_internals/_anyhashableg_wrappers.py: -------------------------------------------------------------------------------- 1 | from ._wrappers import _JGraphTIntegerIterator 2 | 3 | 4 | class _AnyHashableGraphVertexIterator(_JGraphTIntegerIterator): 5 | """A vertex iterator.""" 6 | 7 | def __init__(self, handle, graph, **kwargs): 8 | super().__init__(handle=handle, **kwargs) 9 | self._graph = graph 10 | 11 | def __next__(self): 12 | v = super().__next__() 13 | return self._graph._vertex_id_to_hash[v] 14 | 15 | def __repr__(self): 16 | return "_AnyHashableGraphVertexIterator(%r)" % self._handle 17 | 18 | 19 | class _AnyHashableGraphEdgeIterator(_JGraphTIntegerIterator): 20 | """An edge iterator.""" 21 | 22 | def __init__(self, handle, graph, **kwargs): 23 | super().__init__(handle=handle, **kwargs) 24 | self._graph = graph 25 | 26 | def __next__(self): 27 | e = super().__next__() 28 | return self._graph._edge_id_to_hash[e] 29 | 30 | def __repr__(self): 31 | return "_AnyHashableGraphEdgeIterator(%r)" % self._handle 32 | -------------------------------------------------------------------------------- /examples/algorithms/plot_enumerate_cliques.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Clique Enumeration 5 | ================== 6 | 7 | In this example we execute the Bron-Kerbosch algorithm for enumeration 8 | of maximal cliques. This particular implementations uses pivoting and 9 | the degeneracy ordering. 10 | 11 | """ 12 | 13 | # %% 14 | # Start by importing the package 15 | 16 | import jgrapht 17 | import jgrapht.algorithms.cliques as cliques 18 | 19 | # %% 20 | # We first create a graph an undirected graph. 21 | 22 | g = jgrapht.create_graph(directed=False) 23 | 24 | for i in range(0, 6): 25 | g.add_vertex(i) 26 | 27 | g.add_edge(0, 1) 28 | g.add_edge(0, 2) 29 | g.add_edge(1, 2) 30 | g.add_edge(3, 4) 31 | g.add_edge(3, 5) 32 | g.add_edge(4, 5) 33 | g.add_edge(2, 3) 34 | 35 | print(g) 36 | 37 | # %% 38 | # We execute the clique enumeration algorithm which returns an iterator 39 | # over all maximal cliques in the graph. 40 | 41 | clique_it = cliques.bron_kerbosch_with_degeneracy_ordering(g) 42 | 43 | # %% 44 | # Finally we iterate over all cliques 45 | 46 | for clique in clique_it: 47 | print(clique) 48 | 49 | -------------------------------------------------------------------------------- /tests/test_spanning.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.spanning as spanning 5 | 6 | 7 | def build_graph(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=False, 11 | allowing_multiple_edges=False, 12 | weighted=True, 13 | ) 14 | 15 | for i in range(0, 10): 16 | g.add_vertex(i) 17 | 18 | g.add_edge(0, 1) 19 | g.add_edge(0, 2) 20 | g.add_edge(0, 3) 21 | g.add_edge(0, 4) 22 | g.add_edge(0, 5) 23 | g.add_edge(0, 6) 24 | g.add_edge(0, 7) 25 | g.add_edge(0, 8) 26 | g.add_edge(0, 9) 27 | 28 | g.add_edge(1, 2) 29 | g.add_edge(2, 3) 30 | g.add_edge(3, 4) 31 | g.add_edge(4, 5) 32 | g.add_edge(5, 6) 33 | g.add_edge(6, 7) 34 | g.add_edge(7, 8) 35 | g.add_edge(8, 9) 36 | g.add_edge(9, 1) 37 | 38 | return g 39 | 40 | 41 | def test_greedy_multiplicative(): 42 | g = build_graph() 43 | weight, edges = spanning.multiplicative_greedy(g, 3) 44 | assert weight == 9.0 45 | expected = set([0, 1, 2, 3, 4, 5, 6, 7, 8]) 46 | solution = set(edges) 47 | assert expected == solution 48 | -------------------------------------------------------------------------------- /tests/test_edgelist_csv.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | from jgrapht.io.edgelist import read_edgelist_csv, parse_edgelist_csv 5 | 6 | 7 | def test_input_csv_from_string(): 8 | 9 | input_string = """1,2 10 | 2,3 11 | 3,4 12 | 4,1 13 | """ 14 | 15 | edgelist = parse_edgelist_csv(input_string) 16 | 17 | assert list(edgelist) == [('1', '2', 1.0), ('2', '3', 1.0), ('3', '4', 1.0), ('4', '1', 1.0)] 18 | 19 | 20 | def test_input_csv_from_string_with_weights(): 21 | 22 | input_string = """1,2,4.0 23 | 2,3,5.5 24 | 3,4,2.2 25 | 4,1,1.3 26 | """ 27 | 28 | edgelist = parse_edgelist_csv(input_string, import_edge_weights=True) 29 | 30 | assert list(edgelist) == [('1', '2', 4.0), ('2', '3', 5.5), ('3', '4', 2.2), ('4', '1', 1.3)] 31 | 32 | 33 | def test_input_csv_from_file(tmpdir): 34 | 35 | input_string = """1,2 36 | 2,3 37 | 3,4 38 | 4,1 39 | """ 40 | 41 | tmpfile = tmpdir.join("csv.out") 42 | tmpfilename = str(tmpfile) 43 | 44 | with open(tmpfilename, "w") as f: 45 | f.write(input_string) 46 | 47 | edgelist = read_edgelist_csv(tmpfilename) 48 | 49 | assert list(edgelist) == [('1', '2', 1.0), ('2', '3', 1.0), ('3', '4', 1.0), ('4', '1', 1.0)] 50 | -------------------------------------------------------------------------------- /.github/scripts/build-manylinux2010_x86_64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x 3 | 4 | echo "Current dir: `pwd`" 5 | echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE" 6 | 7 | # Build wheels for Python 3.6, 3.7, 3.8, 3.9 8 | # Although we have a manylinux compatible wheel generated directly from 9 | # setup.py, PyPI requires that the platform tag is set to a manylinux one 10 | # (e.g. manylinux_x86_64 instead of linux_x86_64). 11 | # Because auditwheel repair unecessarily bundles in zlib and breaks our 12 | # RPATH we don't use it, instead we directly specify the tag with --plat-name 13 | for PYBIN in /opt/python/cp3{6..9}*/bin; do 14 | "${PYBIN}/python" setup.py bdist_wheel --plat-name=manylinux2010_x86_64 15 | done 16 | 17 | # Show if our wheels are consistent with auditwheel (they should be) 18 | for WHL in dist/*.whl 19 | do 20 | auditwheel show "$WHL" 21 | done 22 | 23 | # Generate source distribution with sdist so we can upload it to PyPI 24 | /opt/python/cp38-cp38/bin/python setup.py sdist 25 | 26 | # Install generated wheels and run the tests 27 | for PYBIN in /opt/python/cp3{6..9}*/bin; do 28 | "${PYBIN}/pip" install -r requirements/test.txt 29 | "${PYBIN}/pip" install jgrapht --no-index -f $GITHUB_WORKSPACE/dist 30 | "${PYBIN}/pytest" 31 | done 32 | -------------------------------------------------------------------------------- /jgrapht/_internals/_planar.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | from ..types import PlanarEmbedding 3 | from ._wrappers import ( 4 | _HandleWrapper, 5 | _JGraphTIntegerIterator, 6 | _JGraphTLongIterator, 7 | ) 8 | 9 | 10 | class _JGraphTIntegerPlanarEmbedding(_HandleWrapper, PlanarEmbedding): 11 | """A JGraphT wrapped planar embedding.""" 12 | 13 | def __init__(self, handle, **kwargs): 14 | super().__init__(handle=handle, **kwargs) 15 | 16 | def edges_around(self, vertex): 17 | res = backend.jgrapht_ix_planarity_embedding_edges_around_vertex( 18 | self._handle, vertex 19 | ) 20 | return list(_JGraphTIntegerIterator(res)) 21 | 22 | def __repr__(self): 23 | return "_JGraphTIntegerPlanarEmbedding(%r)" % self._handle 24 | 25 | 26 | class _JGraphTLongPlanarEmbedding(_HandleWrapper, PlanarEmbedding): 27 | """A JGraphT wrapped planar embedding.""" 28 | 29 | def __init__(self, handle, **kwargs): 30 | super().__init__(handle=handle, **kwargs) 31 | 32 | def edges_around(self, vertex): 33 | res = backend.jgrapht_lx_planarity_embedding_edges_around_vertex( 34 | self._handle, vertex 35 | ) 36 | return list(_JGraphTLongIterator(res)) 37 | 38 | def __repr__(self): 39 | return "_JGraphTLongPlanarEmbedding(%r)" % self._handle -------------------------------------------------------------------------------- /jgrapht/algorithms/planar.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | 3 | from .._internals._results import _wrap_planar_embedding, _wrap_subgraph 4 | 5 | 6 | def _planarity_alg(name, graph, *args): 7 | alg_method_name = "jgrapht_xx_planarity_exec_" 8 | alg_method_name += name 9 | 10 | alg_method = getattr(_backend, alg_method_name) 11 | is_planar, embedding, kuratowski_subdivision = alg_method(graph.handle, *args) 12 | 13 | if is_planar: 14 | return is_planar, _wrap_planar_embedding(graph, embedding) 15 | else: 16 | return is_planar, _wrap_subgraph(graph, kuratowski_subdivision) 17 | 18 | 19 | def boyer_myrvold(graph): 20 | """The Boyer-Myrvold planarity testing algorithm. 21 | 22 | :param graph: the graph 23 | :returns: a tuple whose first element is whether the graph is planar. The second is 24 | either an embedding (:py:class:`.PlanarEmbedding`) or a Kuratowski subgraph. 25 | """ 26 | return _planarity_alg("boyer_myrvold", graph) 27 | 28 | 29 | def is_planar(graph): 30 | """The Boyer-Myrvold planarity testing algorithm. 31 | 32 | :param graph: the graph 33 | :returns: a tuple whose first element is whether the graph is planar. The second is 34 | either an embedding (:py:class:`.PlanarEmbedding`) or a Kuratowski subgraph. 35 | """ 36 | return boyer_myrvold(graph) 37 | -------------------------------------------------------------------------------- /tests/test_independent.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | 5 | import jgrapht.algorithms.independent as independent 6 | 7 | 8 | def test_chordal(): 9 | g = create_graph( 10 | directed=False, 11 | allowing_self_loops=False, 12 | allowing_multiple_edges=False, 13 | weighted=False, 14 | ) 15 | 16 | for i in range(0, 6): 17 | g.add_vertex(i) 18 | 19 | g.add_edge(0, 1) 20 | g.add_edge(1, 2) 21 | g.add_edge(2, 3) 22 | g.add_edge(4, 5) 23 | g.add_edge(5, 0) 24 | g.add_edge(0, 3) 25 | g.add_edge(0, 4) 26 | g.add_edge(1, 5) 27 | g.add_edge(1, 3) 28 | 29 | ind = independent.chordal_max_independent_set(g) 30 | 31 | assert ind == {2, 4} 32 | 33 | 34 | def test_anyhashableg_chordal(): 35 | g = create_graph( 36 | directed=False, 37 | allowing_self_loops=False, 38 | allowing_multiple_edges=False, 39 | weighted=False, 40 | any_hashable=True, 41 | ) 42 | 43 | for i in range(0, 6): 44 | g.add_vertex(i) 45 | 46 | g.add_edge(0, 1) 47 | g.add_edge(1, 2) 48 | g.add_edge(2, 3) 49 | g.add_edge(4, 5) 50 | g.add_edge(5, 0) 51 | g.add_edge(0, 3) 52 | g.add_edge(0, 4) 53 | g.add_edge(1, 5) 54 | g.add_edge(1, 3) 55 | 56 | ind = independent.chordal_max_independent_set(g) 57 | 58 | assert ind == {2, 4} -------------------------------------------------------------------------------- /jgrapht/_internals/_clustering.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | from ..types import Clustering 3 | from ._wrappers import ( 4 | _HandleWrapper, 5 | _JGraphTIntegerIterator, 6 | _JGraphTLongIterator, 7 | ) 8 | 9 | 10 | class _JGraphTIntegerClustering(_HandleWrapper, Clustering): 11 | """A vertex clustering.""" 12 | 13 | def __init__(self, handle, **kwargs): 14 | super().__init__(handle=handle, **kwargs) 15 | 16 | def number_of_clusters(self): 17 | return backend.jgrapht_xx_clustering_get_number_clusters(self._handle) 18 | 19 | def ith_cluster(self, i): 20 | res = backend.jgrapht_xx_clustering_ith_cluster_vit(self._handle, i) 21 | return _JGraphTIntegerIterator(res) 22 | 23 | def __repr__(self): 24 | return "_JGraphTIntegerClustering(%r)" % self._handle 25 | 26 | 27 | class _JGraphTLongClustering(_HandleWrapper, Clustering): 28 | """A vertex clustering.""" 29 | 30 | def __init__(self, handle, **kwargs): 31 | super().__init__(handle=handle, **kwargs) 32 | 33 | def number_of_clusters(self): 34 | return backend.jgrapht_xx_clustering_get_number_clusters(self._handle) 35 | 36 | def ith_cluster(self, i): 37 | res = backend.jgrapht_xx_clustering_ith_cluster_vit(self._handle, i) 38 | return _JGraphTLongIterator(res) 39 | 40 | def __repr__(self): 41 | return "_JGraphTLongClustering(%r)" % self._handle 42 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_draw_graph.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Draw an Undirected Graph 5 | ======================== 6 | 7 | In this example we draw an undirected graph using the Fruchterman-Reingold layout. 8 | You must have matplotlib installed for this to work. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | 14 | import jgrapht 15 | import jgrapht.drawing.draw_matplotlib as drawing 16 | import matplotlib.pyplot as plt 17 | 18 | # %% 19 | # Creating a graph 20 | 21 | g = jgrapht.create_graph(directed=False, weighted=True) 22 | 23 | # %% 24 | # Add some vertices 25 | 26 | for i in range(0,10): 27 | g.add_vertex() 28 | 29 | # %% 30 | # and some edges 31 | 32 | e1 = g.add_edge(0, 1) 33 | e2 = g.add_edge(0, 2) 34 | e3 = g.add_edge(0, 3) 35 | e4 = g.add_edge(0, 4) 36 | e5 = g.add_edge(0, 5) 37 | e6 = g.add_edge(0, 6) 38 | e7 = g.add_edge(0, 7) 39 | e8 = g.add_edge(0, 8) 40 | e9 = g.add_edge(0, 9) 41 | e10 = g.add_edge(1, 2) 42 | 43 | # %% 44 | # Compute the position of the vertices 45 | positions = drawing.layout(g, seed=10, name="fruchterman_reingold") 46 | 47 | # %% 48 | # Draw the graph using the vertex labels and the edge labels 49 | vertex_labels = {v:v for v in g.vertices} 50 | edge_labels = {e:e for e in g.edges} 51 | drawing.draw_jgrapht(g, position=positions, vertex_labels=vertex_labels, edge_labels=edge_labels, axis=False) 52 | plt.show() 53 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_edge_colormap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Edge Colormap 5 | ============================= 6 | In this example we draw a graph with matplotlib using the circular layout. We 7 | also use vertex degree to color some edges. You must have matplotlib installed for 8 | this to work. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | import jgrapht 14 | import jgrapht.drawing.draw_matplotlib as drawing 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | # %% 18 | # Creating a graph 19 | 20 | g = jgrapht.create_graph(directed=False, weighted=True,) 21 | 22 | 23 | # %% 24 | # Add some vertices 25 | 26 | for i in range(0, 10): 27 | g.add_vertex() 28 | 29 | 30 | # %% 31 | # and some edges 32 | 33 | e1 = g.add_edge(0, 1) 34 | e2 = g.add_edge(0, 2) 35 | e3 = g.add_edge(0, 3) 36 | e4 = g.add_edge(0, 4) 37 | e5 = g.add_edge(0, 5) 38 | e6 = g.add_edge(0, 6) 39 | e7 = g.add_edge(0, 7) 40 | e8 = g.add_edge(0, 8) 41 | e9 = g.add_edge(0, 9) 42 | 43 | # %% 44 | # Compute the position of the vertices 45 | positions = drawing.layout(g, name="circular") 46 | # %% 47 | # Draw the graph using the node labels and the edge line width and edge colormap 48 | drawing.draw_jgrapht( 49 | g, 50 | positions=positions, 51 | edge_linewidth=6, 52 | edge_cmap=plt.cm.Blues(np.linspace(0.1, 1, len(g.edges))), 53 | axis=False, 54 | ) 55 | plt.show() 56 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_node_colormap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Vertex Colormap 5 | =============== 6 | 7 | In this example we draw a graph with matplotlib using the circular layout, 8 | coloring vertices by degree. You must have matplotlib for this to work. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | import jgrapht 14 | import jgrapht.drawing.draw_matplotlib as draw_matplotlib 15 | import matplotlib.pyplot as plt 16 | 17 | 18 | # %% 19 | # Creating a graph 20 | 21 | g = jgrapht.create_graph(directed=False, weighted=True,) 22 | 23 | 24 | # %% 25 | # Add some vertices 26 | for v in range(10): 27 | g.add_vertex(v) 28 | 29 | 30 | # %% 31 | # and some edges 32 | e1 = g.add_edge(0, 1) 33 | e2 = g.add_edge(0, 2) 34 | e3 = g.add_edge(0, 3) 35 | e4 = g.add_edge(0, 4) 36 | e5 = g.add_edge(0, 5) 37 | e6 = g.add_edge(0, 6) 38 | e7 = g.add_edge(0, 7) 39 | e8 = g.add_edge(0, 8) 40 | e9 = g.add_edge(0, 9) 41 | 42 | # %% 43 | # Compute the position of the vertices 44 | positions = draw_matplotlib.layout(g, name="circular") 45 | 46 | # %% 47 | # Draw the graph using the vertex labels, edge labels and vertex colormap 48 | draw_matplotlib.draw_jgrapht( 49 | g, 50 | positions=positions, 51 | vertex_labels={v: v for v in g.vertices}, 52 | edge_labels={e: e for e in g.edges}, 53 | vertex_color=range(len(g.vertices)), 54 | vertex_cmap=plt.cm.Oranges, 55 | axis=False, 56 | ) 57 | plt.show() 58 | -------------------------------------------------------------------------------- /docs/api/algorithms/flow.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _algorithms/flow: 3 | 4 | Flows 5 | ***** 6 | 7 | Algorithms 8 | ---------- 9 | 10 | We are given a weighted directed or undirected graph :math:`G(V,E)`. Each edge :math:`e \in E` has 11 | an associated non-negative capacity :math:`c_e \ge 0`. The maximum flow problem involves 12 | finding a feasible flow :math:`f: E \mapsto \mathbb{R}_{0+}` of maximum value. 13 | A flow is feasible if: 14 | 15 | * the flow :math:`f(e)` of an edge :math:`e` does not exceed its capacity, and 16 | * for each vertex except the source and sink, the sum of incoming flows is equal to the sum 17 | of outgoing flows. 18 | 19 | Computing maximum s-t flows can be performed using the following function. 20 | 21 | .. autofunction:: jgrapht.algorithms.flow.max_st_flow 22 | 23 | When the user requires more advanced control over the selected 24 | algorithm, the following functions are provided. 25 | 26 | .. automodule:: jgrapht.algorithms.flow 27 | :members: push_relabel, dinic, edmonds_karp 28 | 29 | Computing maximum flow values between all pairs of vertices in an undirected network can 30 | be performed using: 31 | 32 | .. autofunction:: jgrapht.algorithms.flow.equivalent_flow_tree_gusfield 33 | 34 | 35 | Types 36 | ----- 37 | 38 | Flows are represented using instances of the following classes. 39 | 40 | .. autoclass:: jgrapht.types.Flow 41 | :members: 42 | 43 | This is a dictionary from edges to double values. 44 | 45 | .. autoclass:: jgrapht.types.EquivalentFlowTree 46 | :members: 47 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_draw_arrow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Draw a Directed Graph 5 | ===================== 6 | 7 | In this example we draw a directed graph using the Fruchterman-Reingold layout. 8 | You must have matplotlib installed for this to work. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | import jgrapht 14 | import jgrapht.drawing.draw_matplotlib as drawing 15 | import matplotlib.pyplot as plt 16 | 17 | # %% 18 | # Creating a graph 19 | 20 | g = jgrapht.create_graph(directed=True, weighted=True) 21 | 22 | # %% 23 | # Add some vertices 24 | 25 | for i in range(0, 10): 26 | g.add_vertex() 27 | 28 | # %% 29 | # and some edges 30 | 31 | e1 = g.add_edge(0, 1) 32 | e2 = g.add_edge(0, 2) 33 | e3 = g.add_edge(0, 3) 34 | e4 = g.add_edge(0, 4) 35 | e5 = g.add_edge(0, 5) 36 | e6 = g.add_edge(0, 6) 37 | e7 = g.add_edge(0, 7) 38 | e8 = g.add_edge(0, 8) 39 | e9 = g.add_edge(0, 9) 40 | 41 | # %% 42 | # Compute the position of the vertices 43 | positions = drawing.layout(g, seed=10, name="circular") 44 | 45 | # %% 46 | # Draw the graph using the node labels,arrows,node colormap,arrow line,arrow color,connection style and edge line width 47 | drawing.draw_jgrapht( 48 | g, 49 | positions=positions, 50 | node_label=True, 51 | node_color=range(len(g.vertices)), 52 | node_cmap=plt.cm.Blues, 53 | edge_linewidth=4, 54 | arrow=True, 55 | arrow_color="orange", 56 | arrow_line="dotted", 57 | connection_style="arc3,rad=-0.3", 58 | axis=False, 59 | ) 60 | plt.show() 61 | -------------------------------------------------------------------------------- /examples/io/plot_export_json.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | JSON Export 5 | =========== 6 | 7 | This is an example on how to export a graph into JSON format. Our JSON format 8 | is very simple and compatible with the `sigmajs `_ library. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package. 13 | 14 | import jgrapht 15 | from jgrapht.generators import complete_graph 16 | from jgrapht.io.exporters import generate_json 17 | 18 | # %% 19 | # Let us create an undirected graph 20 | 21 | g = jgrapht.create_graph(directed=False) 22 | 23 | # %% 24 | # and use the complete generator to populate the graph, 25 | 26 | complete_graph(g, 5) 27 | 28 | print(g) 29 | 30 | # %% 31 | # We will export the graph to string in JSON format. 32 | 33 | output = generate_json(g) 34 | print(output) 35 | 36 | # %% 37 | # Let us also export the graph using some custom attributes. 38 | 39 | vertex_attrs = {} 40 | for v in g.vertices: 41 | vertex_attrs[v] = {} 42 | vertex_attrs[v]['label'] = 'vertex {}'.format(v) 43 | 44 | edge_attrs = {} 45 | for e in g.edges: 46 | u, v, _ = g.edge_tuple(e) 47 | edge_attrs[e] = {} 48 | edge_attrs[e]['name'] = 'edge {}-{}'.format(u, v) 49 | 50 | # %% 51 | # Now call the exporter with the vertex and edge attributes dictionaries 52 | # as extra parameters. 53 | 54 | output = generate_json(g, per_vertex_attrs_dict=vertex_attrs, per_edge_attrs_dict=edge_attrs) 55 | print(output) 56 | 57 | # %% 58 | # For an export to file use function :py:meth:`jgrapht.io.exporters.write_json`. 59 | # 60 | -------------------------------------------------------------------------------- /tests/test_backend_intlist.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import jgrapht._backend as _backend 4 | from jgrapht._internals._collections import ( 5 | _JGraphTIntegerList, 6 | _JGraphTIntegerMutableList, 7 | ) 8 | 9 | 10 | 11 | def test_IntegerList(): 12 | 13 | handle = _backend.jgrapht_list_create() 14 | _backend.jgrapht_list_int_add(handle, 5) 15 | _backend.jgrapht_list_int_add(handle, 7) 16 | _backend.jgrapht_list_int_add(handle, 9) 17 | 18 | s = _JGraphTIntegerList(handle=handle) 19 | 20 | assert len(s) == 3 21 | 22 | assert 5 in s 23 | assert 6 not in s 24 | assert 7 in s 25 | assert 8 not in s 26 | assert 9 in s 27 | 28 | assert list(s) == list([5, 7, 9]) 29 | 30 | print(repr(s)) 31 | print(s) 32 | 33 | 34 | def test_IntegerMutableList(): 35 | 36 | s = _JGraphTIntegerMutableList() 37 | 38 | s.add(5) 39 | s.add(7) 40 | s.add(9) 41 | 42 | assert len(s) == 3 43 | 44 | assert 5 in s 45 | assert 6 not in s 46 | assert 7 in s 47 | assert 8 not in s 48 | assert 9 in s 49 | 50 | s.discard(7) 51 | 52 | assert 5 in s 53 | assert 6 not in s 54 | assert 7 not in s 55 | assert 8 not in s 56 | assert 9 in s 57 | 58 | assert len(s) == 2 59 | 60 | s.add(11) 61 | s.add(13) 62 | 63 | assert len(s) == 4 64 | 65 | s.discard(17) 66 | assert len(s) == 4 67 | 68 | s.discard(17) 69 | 70 | assert set(s) == set([5, 9, 11, 13]) 71 | 72 | s.clear() 73 | assert len(s) == 0 74 | 75 | str(s) 76 | repr(s) 77 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_draw_house.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Draw a house 5 | ============ 6 | 7 | In this example we draw a graph using custom positions for the vertexs. 8 | You must have matplotlib installed for this to work. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | import jgrapht 14 | import jgrapht.drawing.draw_matplotlib as drawing 15 | import matplotlib.pyplot as plt 16 | 17 | # %% 18 | # Creating a graph 19 | 20 | g = jgrapht.create_graph(directed=False, weighted=True) 21 | 22 | 23 | # %% 24 | # Add some vertices 25 | 26 | for i in range(0, 5): 27 | g.add_vertex() 28 | 29 | # %% 30 | # and some edges 31 | 32 | e1 = g.add_edge(0, 1) 33 | e2 = g.add_edge(0, 2) 34 | e3 = g.add_edge(1, 3) 35 | e4 = g.add_edge(2, 3) 36 | e5 = g.add_edge(2, 4) 37 | e4 = g.add_edge(3, 4) 38 | 39 | # %% 40 | # Compute the position of the vertices 41 | positions = [(0, 0), (1, 0), (0, 1), (1, 1), (0.5, 2.0)] 42 | # %% 43 | # Draw the graph 44 | 45 | # Draw the vertexs using the vertex list and vertex color 46 | drawing.draw_jgrapht_vertices( 47 | g, positions=positions, vertex_list=(0, 1, 2, 3), vertex_color="green" 48 | ) 49 | 50 | # Draw the vertexs using the vertex list, vertex color and vertex shape 51 | drawing.draw_jgrapht_vertices( 52 | g, positions=positions, vertex_list=[4], vertex_color="red", vertex_shape="^" 53 | ) 54 | 55 | # Draw the edges using edge color 56 | drawing.draw_jgrapht_edges(g, positions=positions, edge_color="orange") 57 | 58 | plt.tight_layout() 59 | plt.show() 60 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | 2 | # Release 3 | 4 | 1. Open `jgrapht/__version__.py` and change version from `..dev` to 5 | `.` or `.rc1` if you are going for a 6 | release candidate. 7 | 8 | 2. Make sure docs are correctly generated. Use `cd docs && make html` and inspect the 9 | output. 10 | 11 | 3. Open `requirements/docself.txt` and change the version of jgrapht. The entry should be 12 | `jgrapht==.` or similar in case of a release candidate version. 13 | 14 | 4. Commit changes 15 | `git commit -m "X.X release"` 16 | 17 | 5. Add the version number as a tag in git:: 18 | 19 | git tag -s [-u ] jgrapht-. -m 'signed . tag' 20 | 21 | (If you do not have a gpg key, use -m instead; it is important for 22 | Debian packaging that the tags are annotated) 23 | 24 | 6. Push the new meta-data to github:: 25 | 26 | git push --tags origin master 27 | 28 | (where ``origin`` is the name of the 29 | ``github.com:d-michail/python-jgrapht`` repository.) 30 | 31 | 7. PyPi packages will be uploaded by travis 32 | 33 | 8. Wait until readthedocs updates the documentation. 34 | 35 | 9. Increase the version number in `jgrapht/__version__.py` and add a `dev` suffix 36 | 37 | 10. Remove the version from `requirements/docself.txt` 38 | 39 | 11. Commit and push changes:: 40 | 41 | git add jgrapht/__version__.py 42 | git add requirements/docself.txt 43 | git commit -m "Bump release version" 44 | git push origin master 45 | 46 | 12. Done 47 | 48 | -------------------------------------------------------------------------------- /examples/algorithms/plot_pagerank.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | PageRank 5 | ======== 6 | 7 | In this example we create a directed graph and compute PageRank. The graph 8 | is the same as in the `wikipedia `_. 9 | 10 | .. image :: https://upload.wikimedia.org/wikipedia/en/thumb/8/8b/PageRanks-Example.jpg/1024px-PageRanks-Example.jpg 11 | 12 | """ 13 | 14 | # %% 15 | # Start by importing the package 16 | 17 | import jgrapht 18 | import jgrapht.algorithms.scoring as scoring 19 | 20 | # %% 21 | # Creating a graph is done using the factory method. By default graphs are directed 22 | # and weighted. 23 | 24 | g = jgrapht.create_graph() 25 | 26 | # %% 27 | # We can add vertices by providing their identifier. The method returns `True` if the 28 | # vertex was added, `False` otherwise. Identifiers of vertices and edges are always 29 | # integers. 30 | 31 | for v in range(0, 11): 32 | g.add_vertex(v) 33 | 34 | # %% 35 | # Our graph's vertex set now looks like 36 | 37 | print (g.vertices) 38 | 39 | # %% 40 | # We also add the edges 41 | 42 | g.add_edges_from([(1, 2), (2, 1), (3, 0), (3, 1), (4, 1), (4, 3), (4, 5), (5, 1), (5, 4), 43 | (6, 1), (6, 4), (7, 1), (7, 4), (8, 1), (8, 4), (9, 4), (10, 4)]) 44 | 45 | # %% 46 | # We now have the following edges 47 | 48 | print(g.edges) 49 | 50 | # %% 51 | # Execute Pagerank using 52 | pagerank = scoring.pagerank(g) 53 | 54 | # %% 55 | # The returned object is a dictionary from vertices to floating point values. 56 | # Let us store it in a list 57 | 58 | result = [pagerank[v] for v in g.vertices] 59 | print(result) 60 | 61 | -------------------------------------------------------------------------------- /docs/tutorials/succinct_graph.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials/succinct_graph: 2 | 3 | .. currentmodule:: jgrapht 4 | 5 | Succinct graph tutorial 6 | ======================= 7 | 8 | The succinct graph is a special implementation of a graph with integer vertices and edges. 9 | It is a very compact representation which is very close to the theoretical lower-bound. 10 | It has two main restrictions which are: 11 | 12 | * the graph is unmodifiable and thus needs to be bulk-loaded, 13 | * the vertices and edges are integers continously numbered in :math:`[0, n)` and 14 | :math:`[0, m)` where :math:`n` and :math:`m` are the total number of vertices 15 | and edges respectively. 16 | 17 | Compared with sparse graphs, it will be smaller but also a little bit slower. It 18 | is best suited for very large graphs and for static workloads 19 | where the user needs to load a graph and run (once or multiple times) some complex algorithm. 20 | 21 | Creating a succinct graph 22 | ------------------------- 23 | 24 | Succinct graphs must be bulk-loaded using edge lists. 25 | 26 | .. nbplot:: 27 | 28 | >>> import jgrapht 29 | >>> edgelist = [(0, 1), (0, 2), (0, 3), (1, 3), (2, 3), (2, 4), (2, 5), (0, 4), (2, 6)] 30 | >>> g = jgrapht.create_succinct_graph(edgelist, 7, directed=True) 31 | 32 | The first parameter is the list of tuples `(u,v)`. The second parameter is the number of vertices in the 33 | graph. Vertices always start from zero and increase continously. If the user does not provide one, 34 | then it will be deduced by reading the edge list. Finally, the succinct graph representation always 35 | allows self-loops but does not support multiple-edges nor edge-weights. 36 | 37 | 38 | -------------------------------------------------------------------------------- /jgrapht/_internals/_anyhashableg_drawing.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | 3 | from ._drawing import _JGraphTIntegerLayoutModel2D 4 | from ._anyhashableg import ( 5 | _is_anyhashable_graph, 6 | _vertex_anyhashableg_to_g, 7 | ) 8 | 9 | 10 | class _AnyHashableGraphLayoutModel2D(_JGraphTIntegerLayoutModel2D): 11 | """A 2D layout model.""" 12 | 13 | def __init__(self, handle, graph, **kwargs): 14 | super().__init__(handle=handle, **kwargs) 15 | self._graph = graph 16 | 17 | def get_vertex_location(self, vertex): 18 | vertex = _vertex_anyhashableg_to_g(self._graph, vertex) 19 | return super().get_vertex_location(vertex) 20 | 21 | def set_vertex_location(self, vertex, point_2d): 22 | vertex = _vertex_anyhashableg_to_g(self._graph, vertex) 23 | super().set_vertex_location(vertex, point_2d) 24 | 25 | def is_fixed(self, vertex): 26 | vertex = _vertex_anyhashableg_to_g(self._graph, vertex) 27 | return super().is_fixed(vertex) 28 | 29 | def set_fixed(self, vertex, fixed): 30 | vertex = _vertex_anyhashableg_to_g(self._graph, vertex) 31 | super().set_fixed(vertex, fixed) 32 | 33 | def __repr__(self): 34 | return "_AnyHashableGraphLayoutModel2D(%r)" % self._handle 35 | 36 | 37 | def _create_anyhashable_graph_layout_model_2d(graph, min_x, min_y, width, height): 38 | """Factory for a 2d layout model.""" 39 | if not _is_anyhashable_graph(graph): 40 | raise ValueError("Graph must be an any-hashable graph") 41 | handle = backend.jgrapht_xx_drawing_layout_model_2d_create(min_x, min_y, width, height) 42 | return _AnyHashableGraphLayoutModel2D(handle, graph) 43 | -------------------------------------------------------------------------------- /examples/algorithms/plot_vertex_cover.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Vertex Cover 5 | ============ 6 | 7 | In this example we demostrate how to compute a vertex cover in an 8 | undirected graph. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package. 13 | 14 | import jgrapht 15 | import jgrapht.algorithms.vertexcover as vc 16 | import jgrapht.drawing.draw_matplotlib as drawing 17 | import matplotlib.pyplot as plt 18 | 19 | # %% 20 | # We start by creating a "star" graph. 21 | 22 | g = jgrapht.create_graph(directed=False, weighted=False) 23 | 24 | for i in range(11): 25 | g.add_vertex(i) 26 | 27 | for i in range(10): 28 | g.add_edge(i, 10) 29 | 30 | print(g) 31 | 32 | # %% 33 | # Then, we execute the 2-approximation algorithm of R. Bar-Yehuda and S. Even. 34 | 35 | weight, vertex_cover = vc.baryehuda_even(g) 36 | 37 | # %% 38 | # The result is a tuple which contains the weight and the vertex cover. 39 | # Although it failed to find the optimum which is 1.0, the returned solution is 40 | # at most twice the optimum. 41 | # 42 | print('Vertex cover weight: {}'.format(weight)) 43 | print('Vertex cover: {}'.format(vertex_cover)) 44 | 45 | 46 | # %% 47 | # Ploting the graph using separate color for the vertices in the vertex cover. 48 | positions = drawing.layout(g, name="fruchterman_reingold", seed=17) 49 | 50 | drawing.draw_jgrapht_vertices(g, positions=positions, vertex_list=vertex_cover, vertex_color='red') 51 | 52 | non_vertex_cover = g.vertices - vertex_cover 53 | drawing.draw_jgrapht_vertices(g, positions=positions, vertex_list=non_vertex_cover, vertex_color='gray') 54 | 55 | drawing.draw_jgrapht_edges(g, positions=positions) 56 | 57 | plt.show() 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | ### Added 5 | - Added changelog file 6 | - Added support for backend version 1.5.1 7 | - Added link prediction algorithms 8 | - Added transit node routing algorithms 9 | - Added edge betweenness centrality 10 | - Added katz and eigenvector centrality 11 | 12 | ### Fixed 13 | - Fixed wrong PyPi classifier for windows 14 | 15 | ## [1.5.0.3] - 2020-11-18 16 | ### Fixed 17 | - Fixed issue with subpackage imports 18 | - Fixed operating system classifiers on PyPi 19 | 20 | ## [1.5.0.2] - 2020-11-17 21 | ### Added 22 | - Added support for reading/writing to pydot graphs 23 | - Added support for reading/writing to Networkx graphs 24 | - Added initial support for drawing using Matplotlib 25 | from [@Chris-Voliotis](https://github.com/Chris-Voliotis) 26 | - Added additional examples 27 | 28 | ### Changed 29 | - Improved support for matplotlib 30 | 31 | ## [1.5.0.1] - 2020-10-17 32 | ### Fixed 33 | - Fixed issue with travis windows build 34 | - Fixed issue with initialization from different threads (Fixes https://github.com/d-michail/python-jgrapht/issues/9) 35 | ### Changed 36 | - Changed repository name for jgrapht-capi which moved to organization 37 | 38 | ## [1.5.0.0] - 2020-06-19 39 | ### Added 40 | - First official release 41 | 42 | [Unreleased]: https://github.com/d-michail/python-jgrapht/compare/jgrapht-1.5.0.3...HEAD 43 | [1.5.0.3]: https://github.com/d-michail/python-jgrapht/compare/jgrapht-1.5.0.2...jgrapht-1.5.0.3 44 | [1.5.0.2]: https://github.com/d-michail/python-jgrapht/compare/jgrapht-1.5.0.1...jgrapht-1.5.0.2 45 | [1.5.0.1]: https://github.com/d-michail/python-jgrapht/compare/jgrapht-1.5.0.0...jgrapht-1.5.0.1 46 | [1.5.0.0]: https://github.com/d-michail/python-jgrapht/releases/tag/jgrapht-1.5.0.0 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/api/interfaces.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _interfaces: 3 | 4 | Interfaces 5 | ********** 6 | 7 | .. currentmodule:: jgrapht 8 | 9 | Graph 10 | ^^^^^ 11 | 12 | The main interface of the library is the :class:`Graph `. All 13 | graph instances returned by the library follow this interface and almost all library methods revolve 14 | around it. The graph interface contains all the necessary methods to query or modify a graph. 15 | 16 | .. autoclass:: jgrapht.types.Graph 17 | :inherited-members: 18 | :members: 19 | 20 | AttributesGraph 21 | ^^^^^^^^^^^^^^^ 22 | 23 | Some graph implementations are also attribute graphs which means that they can directly associate 24 | attributes/properties with the vertices and edges of the graph. 25 | 26 | .. autoclass:: jgrapht.types.AttributesGraph 27 | :inherited-members: 28 | :members: 29 | 30 | GraphType 31 | ^^^^^^^^^ 32 | 33 | The :class:`.GraphType` is used to represent during runtime the properties of the graph. 34 | 35 | .. autoclass:: jgrapht.types.GraphType 36 | :inherited-members: 37 | 38 | DirectedAcyclicGraph 39 | ^^^^^^^^^^^^^^^^^^^^ 40 | 41 | A special type is available for directed acyclic graphs (DAGs). 42 | 43 | .. autoclass:: jgrapht.types.DirectedAcyclicGraph 44 | :no-inherited-members: 45 | :members: 46 | :special-members: 47 | 48 | A directed acyclic graph (dag) has all the graph members and the following additional methods. 49 | 50 | ListenableGraph 51 | ^^^^^^^^^^^^^^^ 52 | 53 | In case the user wants to listen on structural change events, a special type of listenable graph is 54 | also provided. 55 | 56 | .. autoclass:: jgrapht.types.ListenableGraph 57 | :no-inherited-members: 58 | :members: 59 | 60 | A listenable graph has all the graph members and the following additional methods which 61 | allow users to register and unregister listeners. 62 | 63 | .. autoclass:: jgrapht.types.GraphEvent 64 | :members: -------------------------------------------------------------------------------- /jgrapht/utils.py: -------------------------------------------------------------------------------- 1 | class StringSupplier: 2 | """A string supplier which returns integers starting from zero with 3 | a possible custom prefix. 4 | """ 5 | 6 | def __init__(self, prefix=None, start=0): 7 | self._count = start 8 | self._prefix = prefix 9 | 10 | def __call__(self): 11 | value = self._count 12 | self._count += 1 13 | return "{}{}".format("" if self._prefix is None else self._prefix, value) 14 | 15 | 16 | class IntegerSupplier: 17 | """An integer supplier which returns integers starting from zero. 18 | """ 19 | 20 | def __init__(self, start=0): 21 | self._count = start 22 | 23 | def __call__(self): 24 | value = self._count 25 | self._count += 1 26 | return value 27 | 28 | 29 | def create_vertex_supplier(type="str", prefix="v", start=0): 30 | """Create a vertex supplier. Vertex suppliers are called whenever an 31 | any-hashable graph wants to create a new vertex. 32 | 33 | :param type: type can be either 'str' or 'int' 34 | :param prefix: if a string supplier, a prefix to use 35 | :param start: where to start counting 36 | :returns: a vertex supplier 37 | """ 38 | if type == "int": 39 | return IntegerSupplier(start=start) 40 | else: 41 | return StringSupplier(prefix=prefix, start=start) 42 | 43 | 44 | def create_edge_supplier(type="str", prefix="e", start=0): 45 | """Create an edge supplier. Εdge suppliers are called whenever an 46 | any-hashable graph wants to create a new edge. 47 | 48 | :param type: type can be either 'str' or 'int' 49 | :param prefix: if a string supplier, a prefix to use 50 | :param start: where to start counting 51 | :returns: an edge supplier 52 | """ 53 | if type == "int": 54 | return IntegerSupplier(start=start) 55 | else: 56 | return StringSupplier(prefix=prefix, start=start) 57 | -------------------------------------------------------------------------------- /examples/algorithms/plot_connected_components.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Connected Components 5 | ==================== 6 | 7 | In this example we demostrate how to find connected components in undirected 8 | graphs. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package. 13 | 14 | import jgrapht 15 | import jgrapht.algorithms.connectivity as cc 16 | import jgrapht.drawing.draw_matplotlib as drawing 17 | import matplotlib.pyplot as plt 18 | 19 | # %% 20 | # We start by creating a graph with 4 connected components. 21 | 22 | g = jgrapht.create_graph(directed=False, weighted=True) 23 | 24 | for i in range(11): 25 | g.add_vertex(i) 26 | 27 | g.add_edge(0, 1) 28 | g.add_edge(1, 2) 29 | g.add_edge(2, 3) 30 | 31 | g.add_edge(4, 5) 32 | g.add_edge(5, 6) 33 | g.add_edge(6, 7) 34 | 35 | g.add_edge(8, 9) 36 | 37 | print(g) 38 | 39 | # %% 40 | # Then, we execute the connected components algorithm. 41 | 42 | is_connected, connected_components_it = cc.is_connected(g) 43 | connected_components = list(connected_components_it) 44 | 45 | # %% 46 | # The result is a tuple which contains whether the graph is connected and 47 | # an iterator over the connected components. 48 | # 49 | print('is connected: {}'.format(is_connected)) 50 | 51 | for i, cc in enumerate(connected_components): 52 | print('Connected component {}: {}'.format(i, cc)) 53 | 54 | 55 | # %% 56 | # Ploting the graph with a circular layout and separate color per connected 57 | # component. 58 | positions = drawing.layout(g, name="circular", seed=17) 59 | 60 | vertex_labels = {v:str(v) for v in g.vertices} 61 | colors = ['red', 'blue', 'green', 'yellow'] 62 | for cc, color in zip(connected_components, colors): 63 | drawing.draw_jgrapht_vertices(g, positions=positions, vertex_list=cc, vertex_color=color) 64 | 65 | drawing.draw_jgrapht_vertex_labels(g, positions=positions, labels=vertex_labels) 66 | drawing.draw_jgrapht_edges(g, positions=positions) 67 | 68 | plt.show() 69 | -------------------------------------------------------------------------------- /jgrapht/_internals/_callbacks.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | 4 | def _create_wrapped_callback(callback, cfunctype): 5 | if callback is not None: 6 | # wrap the python callback with a ctypes function pointer 7 | f = cfunctype(callback) 8 | 9 | # get the function pointer of the ctypes wrapper by casting it to void* and taking its value 10 | # we perform the reverse using typemaps on the SWIG layer 11 | f_ptr = ctypes.cast(f, ctypes.c_void_p).value 12 | 13 | # make sure to also return the callback to avoid garbage collection 14 | return (f_ptr, f) 15 | return (0, None) 16 | 17 | 18 | def _create_wrapped_int_vertex_comparator_callback(callback): 19 | """Create a wrapper callback for a vertex comparator. This means 20 | that the function should accept two integers and return an integer. 21 | """ 22 | if callback is not None: 23 | callback_ctype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) 24 | return _create_wrapped_callback(callback, callback_ctype) 25 | else: 26 | return (0, None) 27 | 28 | 29 | def _create_wrapped_long_vertex_comparator_callback(callback): 30 | """Create a wrapper callback for a vertex comparator. This means 31 | that the function should accept two longs and return an integer. 32 | """ 33 | if callback is not None: 34 | callback_ctype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_longlong, ctypes.c_longlong) 35 | return _create_wrapped_callback(callback, callback_ctype) 36 | else: 37 | return (0, None) 38 | 39 | 40 | def _create_wrapped_long_supplier_callback(callback): 41 | """Create a wrapper callback for a vertex/edge supplier callback. This means 42 | that the function should take no arguments and return a long. 43 | """ 44 | if callback is not None: 45 | callback_ctype = ctypes.CFUNCTYPE(ctypes.c_longlong) 46 | return _create_wrapped_callback(callback, callback_ctype) 47 | else: 48 | return (0, None) 49 | -------------------------------------------------------------------------------- /docs/tutorials/sparse_graph.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials/sparse_graph: 2 | 3 | .. currentmodule:: jgrapht 4 | 5 | Sparse graph tutorial 6 | ===================== 7 | 8 | The sparse graph is a special implementation of a graph with integer vertices and edges. 9 | It has two main restrictions which are: 10 | 11 | * the graph is unmodifiable and thus needs to be bulk-loaded, 12 | * the vertices and edges are integers continously numbered in :math:`[0, n)` and 13 | :math:`[0, m)` where :math:`n` and :math:`m` are the total number of vertices 14 | and edges respectively. 15 | 16 | These two restrictions are translated to two main benefits which are: 17 | 18 | * the graph has small memory footprint, and 19 | * the graph is usually much faster, mostly due to its cache-friendly representation on 20 | modern machines. 21 | 22 | From the above discussion it should be clear that they are best suited for static workloads 23 | where the user needs to load a graph and run (once or multiple times) some complex algorithm. 24 | Note also that bulk-loading a sparse graph is usually much faster than adding edges one by 25 | one in the default graph. 26 | 27 | Creating a sparse graph 28 | ----------------------- 29 | 30 | Sparse graphs must be bulk-loaded using edge lists. 31 | 32 | .. nbplot:: 33 | 34 | >>> import jgrapht 35 | >>> edgelist = [(0, 1), (0, 2), (0, 3), (1, 3), (2, 3), (2, 4), (2, 5), (0, 4), (2, 6)] 36 | >>> g = jgrapht.create_sparse_graph(edgelist, 7, directed=True, weighted=False) 37 | 38 | The first parameter is the list of tuples `(u,v)`. In case a weighted graph is requested, then 39 | the tuples must be `(u,v,weight)`. Note that while sparse graphs are unmodifiable w.r.t. their 40 | structure, the edge weights can be modified. The second parameter is the number of vertices in the 41 | graph. Vertices always start from zero and increase continously. If the user does not provide one, 42 | then it will be deduced by reading the edge list. Finally, the sparse graph representation always 43 | allows self-loops and multiple-edges. 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/algorithms/plot_coloring.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Coloring 5 | ======== 6 | 7 | In this example we color a graph using a greedy algorithm which 8 | uses saturation degree ordering. The saturation degree of a vertex 9 | is defined as the number of different colors to which it is adjacent. 10 | The algorithm always selects the vertex with the largest saturation degree. 11 | """ 12 | 13 | # %% 14 | # Start by importing the package. 15 | 16 | import jgrapht 17 | from jgrapht.algorithms.coloring import greedy_dsatur 18 | 19 | # %% 20 | # We create a graph which has 3 communities. 21 | 22 | g = jgrapht.create_graph(directed=False) 23 | 24 | for i in range(16): 25 | g.add_vertex(i) 26 | 27 | g.add_edge(0, 1) 28 | g.add_edge(1, 2) 29 | g.add_edge(2, 3) 30 | g.add_edge(3, 4) 31 | g.add_edge(1, 3) 32 | g.add_edge(1, 4) 33 | 34 | g.add_edge(5, 6) 35 | g.add_edge(6, 7) 36 | g.add_edge(7, 8) 37 | g.add_edge(8, 9) 38 | g.add_edge(9, 10) 39 | g.add_edge(5, 8) 40 | g.add_edge(5, 10) 41 | 42 | g.add_edge(11, 12) 43 | g.add_edge(12, 13) 44 | g.add_edge(13, 14) 45 | g.add_edge(11, 15) 46 | g.add_edge(12, 15) 47 | 48 | g.add_edge(1, 5) 49 | g.add_edge(9, 11) 50 | 51 | print(g) 52 | 53 | # %% 54 | # Then, we execute the greedy coloring algorithm. 55 | 56 | num_colors, color_map = greedy_dsatur(g) 57 | 58 | print(num_colors) 59 | print(color_map) 60 | 61 | # %% 62 | # We next plot the graph with the colors. 63 | 64 | int_to_actual_color = { 0: 'orangered', 1: 'lightsteelblue', 2: 'lightgreen' } 65 | vertex_color = [ int_to_actual_color[color_map[v]] for v in g.vertices ] 66 | vertex_labels = {v:str(v) for v in g.vertices} 67 | 68 | import jgrapht.drawing.draw_matplotlib as drawing 69 | import matplotlib.pyplot as plt 70 | 71 | positions = drawing.layout(g, name="fruchterman_reingold", seed=17) 72 | 73 | drawing.draw_jgrapht_vertices(g, positions=positions, vertex_color=vertex_color) 74 | drawing.draw_jgrapht_vertex_labels(g, positions=positions, labels=vertex_labels) 75 | drawing.draw_jgrapht_edges(g, positions=positions) 76 | 77 | plt.show() 78 | -------------------------------------------------------------------------------- /examples/algorithms/plot_metric_tsp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Metric TSP 5 | ========== 6 | 7 | In this example we execute Christophides algorithm which computes a 3/2-approximate 8 | tour in the metric TSP. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package. 13 | 14 | import jgrapht 15 | from jgrapht.algorithms.tour import metric_tsp_christofides 16 | 17 | # %% 18 | # We create a complete graph where weights satisfy the triangle inequality. 19 | 20 | g = jgrapht.create_graph(directed=False, weighted=True) 21 | 22 | g.add_vertex(0) 23 | g.add_vertex(1) 24 | g.add_vertex(2) 25 | g.add_vertex(3) 26 | g.add_vertex(4) 27 | 28 | g.add_edge(0, 1, weight=1) 29 | g.add_edge(0, 2, weight=2) 30 | g.add_edge(0, 3, weight=1) 31 | g.add_edge(0, 4, weight=1) 32 | g.add_edge(1, 2, weight=1) 33 | g.add_edge(1, 3, weight=2) 34 | g.add_edge(1, 4, weight=1) 35 | g.add_edge(2, 3, weight=1) 36 | g.add_edge(2, 4, weight=1) 37 | g.add_edge(3, 4, weight=1) 38 | 39 | print(g) 40 | 41 | # %% 42 | # Then, we execute Christophides algorithm. 43 | 44 | tour = metric_tsp_christofides(g) 45 | 46 | # %% 47 | # The result is an instance of :py:class:`.GraphPath` 48 | # 49 | 50 | tour_start = tour.start_vertex 51 | tour_edges = tour.edges 52 | tour_weight = tour.weight 53 | 54 | print('Tour starts at: {}'.format(tour_start)) 55 | print('Tour edges: {}'.format(tour_edges)) 56 | print('Tour weight: {}'.format(tour_weight)) 57 | 58 | # %% 59 | # We next plot the graph and the tour. 60 | import jgrapht.drawing.draw_matplotlib as drawing 61 | import matplotlib.pyplot as plt 62 | 63 | positions = drawing.layout(g, name="fruchterman_reingold", seed=17) 64 | vertex_color = ['red' if v == tour_start else 'green' for v in g.vertices] 65 | 66 | non_tour_edges = g.edges - tour_edges 67 | 68 | drawing.draw_jgrapht_vertices(g, positions=positions, vertex_color=vertex_color) 69 | drawing.draw_jgrapht_edges(g, positions=positions, edge_list=tour_edges, edge_linewidth=3, edge_color='red') 70 | drawing.draw_jgrapht_edges(g, positions=positions, edge_list=non_tour_edges, edge_color='gray') 71 | 72 | plt.show() 73 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. jgrapht documentation master file, created by 2 | sphinx-quickstart on Thu Apr 23 10:34:24 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | The JGraphT library 7 | =================== 8 | 9 | Release v\ |version|. 10 | 11 | Python bindings for the `JGraphT graph library `_. 12 | 13 | The JGraphT library is a highly efficient graph library, written in Java, containing 14 | state-of-the-art graph data-structures as well as a multitude of sophisticated 15 | graph algorithms. It has been in development for many years already and is currently used 16 | in production both in research and industry. 17 | 18 | The |Bindings| is a pure python/native package having no dependency on the JVM. During the 19 | build process the backend JGraphT library is compiled as a shared library and bundled 20 | inside the python package. 21 | 22 | Backend version v\ |BackendVersion|. 23 | 24 | Audience 25 | -------- 26 | 27 | The audience of the library is any user who needs to use graphs or networks in their every 28 | day work, including engineers and researchers either from the industry or academia. 29 | 30 | Why another library? 31 | -------------------- 32 | 33 | Well, this is not really a new library. This is just making a library cross boundaries between 34 | two different programming worlds. 35 | 36 | Development 37 | ----------- 38 | 39 | Development happens in the following places. 40 | 41 | * https://github.com/d-michail/python-jgrapht 42 | * https://github.com/jgrapht/jgrapht-capi 43 | * https://github.com/jgrapht/jgrapht 44 | 45 | 46 | Documentation 47 | ------------- 48 | 49 | .. toctree:: 50 | :maxdepth: 2 51 | :caption: JGraphT 52 | 53 | install 54 | tutorials/index 55 | api/index 56 | license 57 | citing 58 | credits 59 | 60 | .. toctree:: 61 | :maxdepth: 2 62 | :caption: Example galleries 63 | 64 | auto_examples/index 65 | 66 | 67 | Indices and tables 68 | ================== 69 | 70 | * :ref:`genindex` 71 | * :ref:`modindex` 72 | * :ref:`search` 73 | -------------------------------------------------------------------------------- /examples/algorithms/plot_prim.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Minimum Spanning Tree using Prim 5 | ================================ 6 | 7 | In this example we create an undirected graph and compute a minimum spanning tree 8 | (MST) using Prim's algorithm. The graph is constructed using the Watts-Strogatz model. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | 14 | import random 15 | import jgrapht 16 | import jgrapht.generators as gen 17 | import jgrapht.algorithms.spanning as spanning 18 | import jgrapht.drawing.draw_matplotlib as drawing 19 | import matplotlib.pyplot as plt 20 | 21 | # %% 22 | # Creating a graph is done using the factory method. We create an undirected 23 | # graph with support for weights. 24 | 25 | g = jgrapht.create_graph(directed=False, weighted=True) 26 | 27 | # %% 28 | # We use the Watts-Strogatz generator to populate a graph. We start with the 29 | # ring of 30 vertices each connected to its 4 nearest neighbors and rewiring with 30 | # probability 0.1. 31 | 32 | gen.watts_strogatz_graph(g, 10, 4, 0.2, seed=17) 33 | 34 | # %% 35 | # We also assign some random weights from [0, 1) to the edges. 36 | 37 | rng = random.Random(17) 38 | for e in g.edges: 39 | g.set_edge_weight(e, rng.random()) 40 | 41 | # %% 42 | # Let us print the graph 43 | 44 | print(g) 45 | 46 | # %% 47 | # Then, we execute Prim's algorithm. 48 | 49 | mst_weight, mst_edges = spanning.prim(g) 50 | 51 | # %% 52 | # The result is a tuple which contains the weight and the minimum spanning tree. 53 | # 54 | 55 | print('mst weight: {}'.format(mst_weight)) 56 | print('mst tree: {}'.format(mst_edges)) 57 | 58 | # %% 59 | # Ploting the graph with highlighted the MST edges 60 | # 61 | positions = drawing.layout(g, name="fruchterman_reingold", seed=17) 62 | 63 | drawing.draw_jgrapht_vertices(g, positions=positions) 64 | 65 | non_mst_edges = g.edges - mst_edges 66 | drawing.draw_jgrapht_edges(g, positions=positions, edge_list=mst_edges, 67 | edge_color='blue', edge_linewidth=3) 68 | drawing.draw_jgrapht_edges(g, positions=positions, edge_list=non_mst_edges, 69 | edge_linewidth=1) 70 | 71 | plt.show() 72 | -------------------------------------------------------------------------------- /examples/algorithms/plot_label_propagation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Communities Detection using Label Propagation 5 | ============================================= 6 | 7 | In this example we discover communities using label propagation. 8 | """ 9 | 10 | # %% 11 | # Start by importing the package. 12 | 13 | import jgrapht 14 | from jgrapht.algorithms.clustering import label_propagation 15 | 16 | # %% 17 | # We start by creating a graph which has 3 communities. 18 | 19 | g = jgrapht.create_graph(directed=False, weighted=True) 20 | 21 | for i in range(16): 22 | g.add_vertex(i) 23 | 24 | g.add_edge(0, 1) 25 | g.add_edge(1, 2) 26 | g.add_edge(2, 3) 27 | g.add_edge(3, 4) 28 | g.add_edge(1, 3) 29 | g.add_edge(1, 4) 30 | 31 | g.add_edge(5, 6) 32 | g.add_edge(6, 7) 33 | g.add_edge(7, 8) 34 | g.add_edge(8, 9) 35 | g.add_edge(9, 10) 36 | g.add_edge(5, 8) 37 | g.add_edge(5, 10) 38 | 39 | g.add_edge(11, 12) 40 | g.add_edge(12, 13) 41 | g.add_edge(13, 14) 42 | g.add_edge(11, 15) 43 | g.add_edge(12, 15) 44 | 45 | g.add_edge(1, 5) 46 | g.add_edge(9, 11) 47 | 48 | print(g) 49 | 50 | # %% 51 | # Then, we execute the label propagation algorithm. 52 | 53 | clustering = label_propagation(g, seed=17) 54 | 55 | # %% 56 | # The result is an instance of :py:class:`.Clustering`. 57 | # We convert the actual clusters to lists as they are returned as 58 | # iterators. 59 | # 60 | clusters = [] 61 | for i in range(clustering.number_of_clusters()): 62 | clusters.append(list(clustering.ith_cluster(i))) 63 | 64 | print('# of clusters: {}'.format(len(clusters))) 65 | print(clusters) 66 | 67 | # %% 68 | # Ploting the graph with a separate color per community. 69 | 70 | import jgrapht.drawing.draw_matplotlib as drawing 71 | import matplotlib.pyplot as plt 72 | 73 | positions = drawing.layout(g, name="fruchterman_reingold", seed=17) 74 | 75 | vertex_labels = {v:str(v) for v in g.vertices} 76 | colors = ['red', 'yellow', 'green'] 77 | for cluster, color in zip(clusters, colors): 78 | drawing.draw_jgrapht_vertices(g, positions=positions, vertex_list=cluster, vertex_color=color) 79 | 80 | drawing.draw_jgrapht_vertex_labels(g, positions=positions, labels=vertex_labels) 81 | drawing.draw_jgrapht_edges(g, positions=positions) 82 | 83 | plt.show() 84 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import jgrapht.utils as utils 4 | 5 | def test_integer_supplier(): 6 | 7 | s = utils.IntegerSupplier() 8 | 9 | assert s() == 0 10 | assert s() == 1 11 | assert s() == 2 12 | assert s() == 3 13 | assert s() == 4 14 | 15 | s = utils.IntegerSupplier(start=5) 16 | 17 | assert s() == 5 18 | assert s() == 6 19 | assert s() == 7 20 | assert s() == 8 21 | assert s() == 9 22 | 23 | 24 | def test_string_supplier(): 25 | 26 | s = utils.StringSupplier() 27 | 28 | assert s() == '0' 29 | assert s() == '1' 30 | assert s() == '2' 31 | assert s() == '3' 32 | assert s() == '4' 33 | 34 | s = utils.StringSupplier(start=5) 35 | 36 | assert s() == '5' 37 | assert s() == '6' 38 | assert s() == '7' 39 | assert s() == '8' 40 | assert s() == '9' 41 | 42 | 43 | s = utils.StringSupplier(prefix='prefix', start=5) 44 | 45 | assert s() == 'prefix5' 46 | assert s() == 'prefix6' 47 | assert s() == 'prefix7' 48 | assert s() == 'prefix8' 49 | assert s() == 'prefix9' 50 | 51 | def test_edge_supplier(): 52 | 53 | es = utils.create_edge_supplier() 54 | 55 | assert es() == 'e0' 56 | assert es() == 'e1' 57 | assert es() == 'e2' 58 | 59 | es = utils.create_edge_supplier(prefix='edge') 60 | assert es() == 'edge0' 61 | assert es() == 'edge1' 62 | assert es() == 'edge2' 63 | 64 | es = utils.create_edge_supplier(type='int', prefix='edge') 65 | assert es() == 0 66 | assert es() == 1 67 | assert es() == 2 68 | 69 | es = utils.create_edge_supplier(type='int', start=3) 70 | assert es() == 3 71 | assert es() == 4 72 | assert es() == 5 73 | 74 | 75 | def test_vertex_supplier(): 76 | 77 | es = utils.create_vertex_supplier() 78 | 79 | assert es() == 'v0' 80 | assert es() == 'v1' 81 | assert es() == 'v2' 82 | 83 | es = utils.create_vertex_supplier(prefix='vertex') 84 | assert es() == 'vertex0' 85 | assert es() == 'vertex1' 86 | assert es() == 'vertex2' 87 | 88 | es = utils.create_vertex_supplier(type='int', prefix='vertex') 89 | assert es() == 0 90 | assert es() == 1 91 | assert es() == 2 92 | 93 | es = utils.create_vertex_supplier(type='int', start=3) 94 | assert es() == 3 95 | assert es() == 4 96 | assert es() == 5 97 | 98 | -------------------------------------------------------------------------------- /jgrapht/_internals/_mapping.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | from ..types import GraphMapping 3 | from ._wrappers import ( 4 | _HandleWrapper, 5 | _JGraphTObjectIterator, 6 | ) 7 | 8 | 9 | class _JGraphTIntegerGraphMapping(_HandleWrapper, GraphMapping): 10 | """A JGraphT mapping between two graphs g1 and g2.""" 11 | 12 | def __init__(self, handle, graph1, graph2, **kwargs): 13 | super().__init__(handle=handle, **kwargs) 14 | self._graph1 = graph1 15 | self._graph2 = graph2 16 | 17 | def vertex_correspondence(self, vertex, forward=True): 18 | ( 19 | exists, 20 | other, 21 | ) = backend.jgrapht_ix_isomorphism_graph_mapping_vertex_correspondence( 22 | self._handle, vertex, forward 23 | ) 24 | return other if exists else None 25 | 26 | def edge_correspondence(self, edge, forward=True): 27 | ( 28 | exists, 29 | other, 30 | ) = backend.jgrapht_xi_isomorphism_graph_mapping_edge_correspondence( 31 | self._handle, edge, forward 32 | ) 33 | return other if exists else None 34 | 35 | def vertices_correspondence(self, forward=True): 36 | vertices = self._graph1.vertices if forward else self._graph2.vertices 37 | result = dict() 38 | for v in vertices: 39 | result[v] = self.vertex_correspondence(v, forward=forward) 40 | return result 41 | 42 | def edges_correspondence(self, forward=True): 43 | edges = self._graph1.edges if forward else self._graph2.edges 44 | result = dict() 45 | for e in edges: 46 | result[e] = self.edge_correspondence(e, forward=forward) 47 | return result 48 | 49 | def __repr__(self): 50 | return "_JGraphTIntegerGraphMapping(%r)" % self._handle 51 | 52 | 53 | class _JGraphTIntegerGraphMappingIterator(_JGraphTObjectIterator): 54 | """A graph mapping iterator""" 55 | 56 | def __init__(self, handle, graph1, graph2, **kwargs): 57 | super().__init__(handle=handle, **kwargs) 58 | self._graph1 = graph1 59 | self._graph2 = graph2 60 | 61 | def __next__(self): 62 | return _JGraphTIntegerGraphMapping(super().__next__(), self._graph1, self._graph2) 63 | 64 | def __repr__(self): 65 | return "_JGraphTIntegerGraphMappingIterator(%r)" % self._handle 66 | -------------------------------------------------------------------------------- /tests/test_gomoryhu.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.cuts as cuts 5 | 6 | 7 | 8 | def test_gomory_hu_tree(): 9 | g = create_graph( 10 | directed=False, 11 | allowing_self_loops=False, 12 | allowing_multiple_edges=False, 13 | weighted=True, 14 | ) 15 | 16 | g.add_vertex(0) 17 | g.add_vertex(1) 18 | g.add_vertex(2) 19 | g.add_vertex(3) 20 | 21 | e01 = g.add_edge(0, 1, weight=20) 22 | e02 = g.add_edge(0, 2, weight=10) 23 | g.add_edge(1, 2, weight=30) 24 | g.add_edge(1, 3, weight=10) 25 | g.add_edge(2, 3, weight=20) 26 | 27 | ght = cuts.gomory_hu_gusfield(g) 28 | 29 | mincut = ght.min_cut() 30 | assert mincut.capacity == 30.0 31 | assert mincut.edges == {0, 1} 32 | 33 | cut_1_3 = ght.min_st_cut(1, 3) 34 | assert cut_1_3.capacity == 30.0 35 | assert cut_1_3.edges == {3, 4} 36 | 37 | cut_1_2 = ght.min_st_cut(1, 2) 38 | assert cut_1_2.capacity == 50.0 39 | assert cut_1_2.edges == {1, 2, 3} 40 | 41 | tree = ght.as_graph() 42 | edge_tuples = [tree.edge_tuple(e) for e in tree.edges] 43 | assert edge_tuples == [(1,0,30.0), (2,1,50.0), (3,2,30.0)] 44 | 45 | 46 | def test_anyhashableg_gomory_hu_tree(): 47 | g = create_graph( 48 | directed=False, 49 | allowing_self_loops=False, 50 | allowing_multiple_edges=False, 51 | weighted=True, 52 | any_hashable=True, 53 | ) 54 | 55 | g.add_vertex(0) 56 | g.add_vertex("1") 57 | g.add_vertex(2) 58 | g.add_vertex(3) 59 | 60 | e0 = g.add_edge(0, "1", weight=20) 61 | e1 = g.add_edge(0, 2, weight=10) 62 | e2 = g.add_edge("1", 2, weight=30) 63 | e3 = g.add_edge("1", 3, weight=10) 64 | e4 = g.add_edge(2, 3, weight=20) 65 | 66 | ght = cuts.gomory_hu_gusfield(g) 67 | 68 | mincut = ght.min_cut() 69 | assert mincut.capacity == 30.0 70 | assert mincut.edges == {e0, e1} 71 | 72 | cut_1_3 = ght.min_st_cut("1", 3) 73 | assert cut_1_3.capacity == 30.0 74 | assert cut_1_3.edges == {e3, e4} 75 | 76 | cut_1_2 = ght.min_st_cut("1", 2) 77 | assert cut_1_2.capacity == 50.0 78 | assert cut_1_2.edges == {e1, e2, e3} 79 | 80 | tree = ght.as_graph() 81 | edge_tuples = [tree.edge_tuple(e) for e in tree.edges] 82 | assert edge_tuples == [("1",0,30.0), (2,"1",50.0), (3,2,30.0)] -------------------------------------------------------------------------------- /tests/test_eft.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.flow as flow 5 | 6 | 7 | 8 | def test_eft(): 9 | g = create_graph( 10 | directed=False, 11 | allowing_self_loops=False, 12 | allowing_multiple_edges=False, 13 | weighted=True, 14 | ) 15 | 16 | g.add_vertex(0) 17 | g.add_vertex(1) 18 | g.add_vertex(2) 19 | g.add_vertex(3) 20 | g.add_vertex(4) 21 | 22 | e01 = g.add_edge(0, 1, weight=20) 23 | e02 = g.add_edge(0, 2, weight=10) 24 | g.add_edge(1, 2, weight=30) 25 | g.add_edge(1, 3, weight=10) 26 | g.add_edge(2, 3, weight=20) 27 | g.add_edge(3, 4, weight=10) 28 | 29 | eft = flow.equivalent_flow_tree_gusfield(g) 30 | 31 | assert eft.max_st_flow_value(2,3) == 30.0 32 | assert eft.max_st_flow_value(1,3) == 30.0 33 | assert eft.max_st_flow_value(0,1) == 30.0 34 | assert eft.max_st_flow_value(0,2) == 30.0 35 | assert eft.max_st_flow_value(0,3) == 30.0 36 | assert eft.max_st_flow_value(0,4) == 10.0 37 | 38 | tree = eft.as_graph() 39 | edge_tuples = [tree.edge_tuple(e) for e in tree.edges] 40 | assert edge_tuples == [(1,0,30.0), (2,1,50.0), (3,2,30.0), (4,3,10.0)] 41 | 42 | 43 | def test_anyhashableg_eft(): 44 | g = create_graph( 45 | directed=False, 46 | allowing_self_loops=False, 47 | allowing_multiple_edges=False, 48 | weighted=True, 49 | any_hashable=True, 50 | ) 51 | 52 | g.add_vertex(0) 53 | g.add_vertex(1) 54 | g.add_vertex(2) 55 | g.add_vertex(3) 56 | g.add_vertex(4) 57 | 58 | e01 = g.add_edge(0, 1, weight=20, edge='e01') 59 | e02 = g.add_edge(0, 2, weight=10, edge='e02') 60 | g.add_edge(1, 2, weight=30) 61 | g.add_edge(1, 3, weight=10) 62 | g.add_edge(2, 3, weight=20) 63 | g.add_edge(3, 4, weight=10) 64 | 65 | eft = flow.equivalent_flow_tree_gusfield(g) 66 | 67 | assert eft.max_st_flow_value(2,3) == 30.0 68 | assert eft.max_st_flow_value(1,3) == 30.0 69 | assert eft.max_st_flow_value(0,1) == 30.0 70 | assert eft.max_st_flow_value(0,2) == 30.0 71 | assert eft.max_st_flow_value(0,3) == 30.0 72 | assert eft.max_st_flow_value(0,4) == 10.0 73 | 74 | tree = eft.as_graph() 75 | edge_tuples = [tree.edge_tuple(e) for e in tree.edges] 76 | assert edge_tuples == [(1,0,30.0), (2,1,50.0), (3,2,30.0), (4,3,10.0)] 77 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_weight_edges.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Weighted Graph 5 | ============================= 6 | 7 | In this example we draw a graph as a weighted .You must have matplotlib for this to work. 8 | """ 9 | 10 | # %% 11 | # Start by importing the package 12 | import jgrapht 13 | import jgrapht.drawing.draw_matplotlib as draw_matplotlib 14 | import matplotlib.pyplot as plt 15 | 16 | 17 | # %% 18 | # Add some vertices 19 | 20 | g = jgrapht.create_graph(directed=False, weighted=True,) 21 | 22 | 23 | # %% 24 | # add vertex 25 | 26 | g.add_vertex() 27 | g.add_vertex() 28 | g.add_vertex() 29 | g.add_vertex() 30 | g.add_vertex() 31 | g.add_vertex() 32 | g.add_vertex() 33 | g.add_vertex() 34 | g.add_vertex() 35 | g.add_vertex() 36 | 37 | 38 | # %% 39 | # and some edges 40 | 41 | e1 = g.add_edge(0, 1, weight=0.5) 42 | e2 = g.add_edge(0, 2, weight=0.4) 43 | e3 = g.add_edge(0, 3, weight=0.6) 44 | e4 = g.add_edge(0, 4, weight=0.8) 45 | e5 = g.add_edge(0, 5, weight=0.1) 46 | e6 = g.add_edge(0, 6, weight=1) 47 | e7 = g.add_edge(0, 7, weight=0.9) 48 | e8 = g.add_edge(0, 8, weight=0.2) 49 | e9 = g.add_edge(0, 9, weight=0.7) 50 | 51 | # %% 52 | # create list of edges depending on their weights 53 | weight_large = [e for e in g.edges if g.get_edge_weight(e) > 0.5] 54 | weight_small = [e for e in g.edges if g.get_edge_weight(e) <= 0.5] 55 | 56 | 57 | # %% 58 | # Compute the position of the vertices 59 | positions = draw_matplotlib.layout(g, seed=10, name="fruchterman_reingold") 60 | # %% 61 | # Draw the graph 62 | 63 | # Draw nodes 64 | draw_matplotlib.draw_jgrapht_vertices(g, positions=positions) 65 | 66 | # Draw the edges using edge list,edge color,edge title and line style 67 | draw_matplotlib.draw_jgrapht_edges( 68 | g, 69 | positions=positions, 70 | edge_list=weight_large, 71 | edge_color="red", 72 | edge_title="weight>0.5", 73 | line_style="dashed", 74 | ) 75 | 76 | draw_matplotlib.draw_jgrapht_edges( 77 | g, 78 | positions=positions, 79 | edge_list=weight_small, 80 | edge_color="orange", 81 | edge_title="weight<=0.5", 82 | ) 83 | 84 | # Draw node labels 85 | draw_matplotlib.draw_jgrapht_vertex_labels(g, positions=positions) 86 | # Draw edge labels with their weight 87 | draw_matplotlib.draw_jgrapht_edge_labels(g, positions=positions, draw_edge_weights=True) 88 | 89 | plt.show() 90 | -------------------------------------------------------------------------------- /examples/draw_matplotlib/plot_labels_colors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Labels And Colors 5 | ============================= 6 | 7 | In this example we draw a graph with vertex labels. You must have matplotlib for this to work. 8 | """ 9 | 10 | # %% 11 | # Start by importing the package 12 | import jgrapht 13 | import jgrapht.drawing.draw_matplotlib as draw_matplotlib 14 | import matplotlib.pyplot as plt 15 | 16 | # %% 17 | # Creating a graph 18 | 19 | g = jgrapht.create_graph(directed=False, weighted=True) 20 | 21 | # %% 22 | # Add some vertices 23 | 24 | for v in range(10): 25 | g.add_vertex(v) 26 | 27 | # %% 28 | # and some edges 29 | 30 | e0 = g.add_edge(0, 1) 31 | e1 = g.add_edge(0, 2) 32 | e2 = g.add_edge(0, 3) 33 | e3 = g.add_edge(0, 4) 34 | e4 = g.add_edge(0, 5) 35 | e5 = g.add_edge(0, 6) 36 | e6 = g.add_edge(0, 7) 37 | e7 = g.add_edge(0, 8) 38 | e8 = g.add_edge(0, 9) 39 | 40 | 41 | # %% 42 | # Compute the position of the vertices 43 | positions = draw_matplotlib.layout(g, seed=10, name="fruchterman_reingold") 44 | # %% 45 | # Draw the graph 46 | 47 | # Draw vertices with title 48 | draw_matplotlib.draw_jgrapht_vertices( 49 | g, positions=positions, node_list=(0, 1, 2, 3, 4), node_title="green nodes" 50 | ) 51 | draw_matplotlib.draw_jgrapht_vertices( 52 | g, positions=positions, node_list=(5, 6, 7, 8, 9), node_color="red", node_title="red nodes" 53 | ) 54 | 55 | # Draw the edges with edge list, edge color and edge title 56 | draw_matplotlib.draw_jgrapht_edges( 57 | g, 58 | positions=positions, 59 | edge_list=(e0, e1, e2, e3, e4), 60 | edge_color="orange", 61 | edge_title="orange edges", 62 | ) 63 | draw_matplotlib.draw_jgrapht_edges( 64 | g, 65 | positions=positions, 66 | edge_list=(e5, e6, e7, e8), 67 | edge_color="blue", 68 | edge_title="blue edges", 69 | ) 70 | 71 | # Draw the vertex labels with custom vertex names 72 | draw_matplotlib.draw_jgrapht_vertex_labels( 73 | g, 74 | positions=positions, 75 | labels={ 76 | 0: "a", 77 | 1: "b", 78 | 2: "c", 79 | 3: "d", 80 | 4: "e", 81 | 5: "f", 82 | 6: "g", 83 | 7: "h", 84 | 8: "i", 85 | 9: "j", 86 | }, 87 | ) 88 | 89 | # Draw the edge labels with custom edge names 90 | draw_matplotlib.draw_jgrapht_edge_labels( 91 | g, 92 | positions=positions, 93 | edge_names={0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h", 8: "i"}, 94 | ) 95 | 96 | plt.show() 97 | -------------------------------------------------------------------------------- /tests/test_backend_intset.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import jgrapht._backend as _backend 4 | 5 | from jgrapht._internals._collections import ( 6 | _JGraphTIntegerSet, 7 | _JGraphTIntegerMutableSet, 8 | ) 9 | 10 | 11 | def test_IntegerSet(): 12 | 13 | handle = _backend.jgrapht_set_linked_create() 14 | _backend.jgrapht_set_int_add(handle, 5) 15 | _backend.jgrapht_set_int_add(handle, 7) 16 | _backend.jgrapht_set_int_add(handle, 9) 17 | 18 | s = _JGraphTIntegerSet(handle=handle) 19 | 20 | assert len(s) == 3 21 | 22 | assert 5 in s 23 | assert 6 not in s 24 | assert 7 in s 25 | assert 8 not in s 26 | assert 9 in s 27 | 28 | assert set(s) == set([5, 7, 9]) 29 | 30 | print(repr(s)) 31 | print(s) 32 | 33 | 34 | 35 | def test_IntegerMutableSet(): 36 | 37 | s = _JGraphTIntegerMutableSet(linked=False) 38 | 39 | s.add(5) 40 | s.add(7) 41 | s.add(9) 42 | 43 | assert len(s) == 3 44 | 45 | assert 5 in s 46 | assert 6 not in s 47 | assert 7 in s 48 | assert 8 not in s 49 | assert 9 in s 50 | 51 | s.remove(7) 52 | 53 | assert 5 in s 54 | assert 6 not in s 55 | assert 7 not in s 56 | assert 8 not in s 57 | assert 9 in s 58 | 59 | assert len(s) == 2 60 | 61 | s.add(11) 62 | s.add(13) 63 | 64 | assert len(s) == 4 65 | 66 | s.discard(17) 67 | assert len(s) == 4 68 | 69 | with pytest.raises(KeyError): 70 | s.remove(17) 71 | 72 | assert set(s) == set([5, 9, 11, 13]) 73 | 74 | print(repr(s)) 75 | print(s) 76 | 77 | 78 | def test_IntegerMutableSet_linked(): 79 | 80 | s = _JGraphTIntegerMutableSet(linked=True) 81 | 82 | s.add(5) 83 | s.add(7) 84 | s.add(9) 85 | 86 | assert len(s) == 3 87 | 88 | assert 5 in s 89 | assert 6 not in s 90 | assert 7 in s 91 | assert 8 not in s 92 | assert 9 in s 93 | 94 | s.remove(7) 95 | 96 | assert 5 in s 97 | assert 6 not in s 98 | assert 7 not in s 99 | assert 8 not in s 100 | assert 9 in s 101 | 102 | assert len(s) == 2 103 | 104 | s.add(11) 105 | s.add(13) 106 | 107 | assert len(s) == 4 108 | 109 | s.discard(17) 110 | assert len(s) == 4 111 | 112 | with pytest.raises(KeyError): 113 | s.remove(17) 114 | 115 | assert set(s) == set([5, 9, 11, 13]) 116 | 117 | s.clear() 118 | 119 | assert len(s) == 0 120 | 121 | assert repr(s) == '_JGraphTIntegerMutableSet(%r)' % (s.handle) 122 | 123 | -------------------------------------------------------------------------------- /examples/algorithms/plot_dijkstra.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Shortest Paths using Dijkstra 5 | ============================= 6 | 7 | In this example we create an undirected graph and compute single-source shortest 8 | paths using Dijkstra. The graph is constructed using the Barabasi-Albert model. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | 14 | import jgrapht 15 | import jgrapht.generators as gen 16 | import jgrapht.algorithms.shortestpaths as sp 17 | import random 18 | 19 | # %% 20 | # Creating a graph is done using the factory method. 21 | 22 | g = jgrapht.create_graph(directed=False, weighted=True) 23 | 24 | 25 | # %% 26 | # We use the Barabasi-Albert generator to populate the graph. We start with the 27 | # complete graph of 3 vertices and reach 10 vertices. Each of the last 7 vertices 28 | # gets connected with the previous ones using preferential attachment. 29 | 30 | gen.barabasi_albert(g, 3, 3, 10, seed=17) 31 | 32 | # %% 33 | # We also assign some random weights from [0, 100) to the edges. 34 | 35 | rng = random.Random(17) 36 | for e in g.edges: 37 | g.set_edge_weight(e, 100 * rng.random()) 38 | 39 | # %% 40 | # Let us print the graph 41 | 42 | print(g) 43 | 44 | # %% 45 | # Then, we execute Dijkstra starting from vertex 6. 46 | 47 | tree = sp.dijkstra(g, source_vertex=6) 48 | 49 | # %% 50 | # The result represents all shortest paths starting from the source vertex. They 51 | # are instances of :py:class:`~jgrapht.types.SingleSourcePaths`. 52 | # To build specific paths to target vertices you call method :py:meth:`~jgrapht.types.SingleSourcePaths.get_path`. 53 | # None is returned if no such path exists. 54 | 55 | path = tree.get_path(8) 56 | 57 | 58 | # %% 59 | # Paths are instances of :py:class:`~jgrapht.types.GraphPath`. 60 | 61 | print('path start: {}'.format(path.start_vertex)) 62 | print('path end: {}'.format(path.end_vertex)) 63 | print('path edges: {}'.format(path.edges)) 64 | print('path vertices: {}'.format(path.vertices)) 65 | print('path weight: {}'.format(path.weight)) 66 | 67 | 68 | # %% 69 | # Dijkstra's algorithm is faster in practice if you also know the target. In that 70 | # case you can also perform a bidirectional search which usually results in faster 71 | # running times. 72 | 73 | other_path = sp.dijkstra(g, source_vertex=4, target_vertex=7) 74 | 75 | print('path start: {}'.format(other_path.start_vertex)) 76 | print('path end: {}'.format(other_path.end_vertex)) 77 | print('path edges: {}'.format(other_path.edges)) 78 | print('path vertices: {}'.format(other_path.vertices)) 79 | print('path weight: {}'.format(other_path.weight)) 80 | 81 | 82 | -------------------------------------------------------------------------------- /tests/test_planar.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.planar as planar 5 | import jgrapht.generators as generators 6 | 7 | 8 | def build_graph(): 9 | g = create_graph( 10 | directed=False, 11 | allowing_self_loops=False, 12 | allowing_multiple_edges=False, 13 | weighted=True, 14 | ) 15 | 16 | g.add_vertex(0) 17 | g.add_vertex(1) 18 | g.add_vertex(2) 19 | g.add_vertex(3) 20 | g.add_vertex(4) 21 | 22 | g.add_edge(0, 1) 23 | g.add_edge(1, 2) 24 | g.add_edge(2, 3) 25 | g.add_edge(3, 4) 26 | g.add_edge(4, 0) 27 | g.add_edge(2, 0) 28 | 29 | return g 30 | 31 | 32 | def build_anyhashableg_graph(): 33 | g = create_graph( 34 | directed=False, 35 | allowing_self_loops=False, 36 | allowing_multiple_edges=False, 37 | weighted=True, 38 | any_hashable=True, 39 | ) 40 | 41 | g.add_vertex(0) 42 | g.add_vertex(1) 43 | g.add_vertex("2") 44 | g.add_vertex(3) 45 | g.add_vertex(4) 46 | 47 | g.add_edge(0, 1, edge=0) 48 | g.add_edge(1, "2", edge=1) 49 | g.add_edge("2", 3, edge=2) 50 | g.add_edge(3, 4, edge="3") 51 | g.add_edge(4, 0, edge=4) 52 | g.add_edge("2", 0, edge="5") 53 | 54 | return g 55 | 56 | 57 | def test_planar(): 58 | g = build_graph() 59 | 60 | res, aux = planar.is_planar(g) 61 | 62 | assert res == True 63 | assert aux.edges_around(0) == list([5, 0, 4]) 64 | 65 | 66 | def test_anyhashableg_planar(): 67 | g = build_anyhashableg_graph() 68 | 69 | res, aux = planar.is_planar(g) 70 | 71 | assert res == True 72 | assert aux.edges_around(0) == list(["5", 0, 4]) 73 | 74 | 75 | def test_non_planar(): 76 | g = create_graph( 77 | directed=False, 78 | allowing_self_loops=False, 79 | allowing_multiple_edges=False, 80 | weighted=False, 81 | ) 82 | generators.complete_graph(g, 5) 83 | 84 | res, aux = planar.is_planar(g) 85 | 86 | assert res == False 87 | assert aux.vertices == set([0, 1, 2, 3, 4]) 88 | 89 | 90 | def test_anyhashableg_non_planar(): 91 | g = create_graph( 92 | directed=False, 93 | allowing_self_loops=False, 94 | allowing_multiple_edges=False, 95 | weighted=False, 96 | any_hashable=True, 97 | ) 98 | generators.complete_graph(g, 5) 99 | 100 | g.add_vertex("extra") 101 | 102 | res, kuratowski = planar.is_planar(g) 103 | 104 | assert res == False 105 | assert kuratowski.vertices == set(g.vertices).difference({"extra"}) 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | *.o 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | docs/auto_examples/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # vendor source 134 | vendor/install 135 | vendor/build 136 | 137 | # SWIG autogenerated 138 | jgrapht/backend.py 139 | jgrapht/backend_wrap.c 140 | 141 | -------------------------------------------------------------------------------- /tests/test_drawing_random_layout_2d.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.drawing as drawing 5 | 6 | 7 | def build_graph(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=False, 11 | allowing_multiple_edges=False, 12 | weighted=False, 13 | ) 14 | 15 | for i in range(0, 6): 16 | g.add_vertex(i) 17 | 18 | g.add_edge(0, 1) 19 | g.add_edge(0, 2) 20 | g.add_edge(1, 2) 21 | 22 | g.add_edge(3, 4) 23 | g.add_edge(3, 5) 24 | g.add_edge(4, 5) 25 | 26 | g.add_edge(2, 3) 27 | 28 | return g 29 | 30 | 31 | def test_random_layout(): 32 | g = build_graph() 33 | 34 | area = (0, 0, 10, 20) 35 | model = drawing.random_layout_2d(g, area, seed=17) 36 | 37 | assert model.area == area 38 | 39 | locations = [model.get_vertex_location(v) for v in g.vertices] 40 | 41 | assert locations == [ 42 | (7.323115139597316, 13.947409567214994), 43 | (0.8295611145017068, 16.324729022114614), 44 | (0.443859375038691, 4.794732258729857), 45 | (7.07454821689446, 13.189673845180147), 46 | (8.58996580616418, 0.0750948516482719), 47 | (4.416485026111072, 16.991675942396792), 48 | ] 49 | 50 | 51 | area = (0, 0, 10, 20) 52 | model = drawing.random_layout_2d(g, area) 53 | assert model.area == area 54 | locations = [model.get_vertex_location(v) for v in g.vertices] 55 | assert len(locations) == 6 56 | 57 | 58 | def build_anyhashableg_graph(): 59 | g = create_graph( 60 | directed=False, 61 | allowing_self_loops=False, 62 | allowing_multiple_edges=False, 63 | weighted=False, 64 | any_hashable=True, 65 | ) 66 | 67 | for i in range(0, 6): 68 | g.add_vertex(str(i)) 69 | 70 | g.add_edge("0", "1") 71 | g.add_edge("0", "2") 72 | g.add_edge("1", "2") 73 | 74 | g.add_edge("3", "4") 75 | g.add_edge("3", "5") 76 | g.add_edge("4", "5") 77 | 78 | g.add_edge("2", "3") 79 | 80 | return g 81 | 82 | 83 | def test_anyhashableg_random_layout(): 84 | g = build_anyhashableg_graph() 85 | 86 | area = (0, 0, 10, 20) 87 | model = drawing.random_layout_2d(g, area, seed=17) 88 | 89 | assert model.area == area 90 | 91 | locations = [model.get_vertex_location(v) for v in g.vertices] 92 | 93 | assert locations == [ 94 | (7.323115139597316, 13.947409567214994), 95 | (0.8295611145017068, 16.324729022114614), 96 | (0.443859375038691, 4.794732258729857), 97 | (7.07454821689446, 13.189673845180147), 98 | (8.58996580616418, 0.0750948516482719), 99 | (4.416485026111072, 16.991675942396792), 100 | ] 101 | 102 | -------------------------------------------------------------------------------- /examples/io/plot_import_sparse_graph.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Importing a Sparse Graph 5 | ======================== 6 | 7 | In this example we import an input file into a sparse graph and execute a greedy 8 | coloring algorithm. 9 | """ 10 | 11 | # %% 12 | # Start by importing the package 13 | 14 | import jgrapht 15 | import jgrapht.io.edgelist as edgelist 16 | 17 | 18 | # %% 19 | # A sparse graph can be constructed from edge lists. Assume we would like to 20 | # read the following graph which is in gexf format. 21 | 22 | input_graph=r""" 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | """ 41 | 42 | # %% 43 | # We use the edge list importers to get the graph into an edge list. 44 | # 45 | 46 | edges = edgelist.parse_edgelist_gexf(input_graph) 47 | print(edges) 48 | 49 | # %% 50 | # We need to convert to integer vertices in order to bulk-load a sparse graph. We provide 51 | # a function which does the conversion. 52 | # 53 | 54 | import re 55 | 56 | def convert_id(id): 57 | m = re.match(r'v([0-9]+)', id) 58 | vid = int(m.group(1)) 59 | return vid 60 | 61 | edges = [(convert_id(s), convert_id(t), w) for s, t, w in edges] 62 | 63 | print(edges) 64 | 65 | # %% 66 | # Now that we have all our edges, we need to figure out the number of vertices. Sparse graphs 67 | # contain all vertices from :math:`[0, n)` where :math:`n` is the number of vertices. 68 | # Then we call the :py:meth:`jgrapht.create_sparse_graph()` factory. 69 | 70 | sparse = jgrapht.create_sparse_graph(edges, num_of_vertices=5, directed=True, weighted=True) 71 | 72 | print(sparse) 73 | 74 | # %% 75 | # Note that in the above call the graph must be weighted, as our edges also have a weight. 76 | # Let us calculate a graph coloring using the greedy algorithm using saturation degree ordering. 77 | 78 | import jgrapht.algorithms.coloring as coloring 79 | 80 | num_colors, color_map = coloring.greedy_dsatur(sparse) 81 | 82 | print('Total number of colors: {}'.format(num_colors)) 83 | print('Color map: {}'.format(color_map)) 84 | 85 | -------------------------------------------------------------------------------- /tests/test_edgelist_dot.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | from jgrapht.io.edgelist import read_edgelist_dot, parse_edgelist_dot 5 | 6 | input1 = """digraph G {\n 0 [label="node 0"];\n 1;\n 2;\n 3;\n 0 -> 1 [cost="33.0"];\n 0 -> 2;\n 0 -> 3;\n 2 -> 3;\n}\n""" 7 | 8 | def test_input_dot(tmpdir): 9 | tmpfile = tmpdir.join("dot.out") 10 | tmpfilename = str(tmpfile) 11 | 12 | # write file json with escaped characters 13 | with open(tmpfilename, "w") as f: 14 | f.write(input1) 15 | 16 | v_attrs = dict() 17 | e_attrs = dict() 18 | 19 | # test that you read back unescaped 20 | def va_cb(vertex, attribute_name, attribute_value): 21 | if vertex not in v_attrs: 22 | v_attrs[vertex] = {} 23 | v_attrs[vertex][attribute_name] = attribute_value 24 | 25 | def ea_cb(edge, attribute_name, attribute_value): 26 | if edge not in e_attrs: 27 | e_attrs[edge] = {} 28 | e_attrs[edge][attribute_name] = attribute_value 29 | 30 | edgelist = read_edgelist_dot( 31 | tmpfilename, 32 | vertex_attribute_cb=va_cb, 33 | edge_attribute_cb=ea_cb, 34 | ) 35 | 36 | assert v_attrs == {'0': {'label': 'node 0'}} 37 | assert e_attrs == {0: {'cost': '33.0'}} 38 | 39 | assert list(edgelist) == [('0','1',1.0), ('0','2',1.0), ('0','3',1.0), ('2','3',1.0)] 40 | 41 | 42 | def test_input_dot_no_attrs(tmpdir): 43 | tmpfile = tmpdir.join("dot.out") 44 | tmpfilename = str(tmpfile) 45 | 46 | with open(tmpfilename, "w") as f: 47 | f.write(input1) 48 | 49 | edgelist = read_edgelist_dot(tmpfilename) 50 | 51 | assert list(edgelist) == [('0','1',1.0), ('0','2',1.0), ('0','3',1.0), ('2','3',1.0)] 52 | 53 | 54 | def test_input_dot_from_string_no_attrs(tmpdir): 55 | edgelist = parse_edgelist_dot(input1) 56 | 57 | assert list(edgelist) == [('0','1',1.0), ('0','2',1.0), ('0','3',1.0), ('2','3',1.0)] 58 | 59 | 60 | def test_input_dot_from_string(tmpdir): 61 | v_attrs = dict() 62 | e_attrs = dict() 63 | 64 | # test that you read back unescaped 65 | def va_cb(vertex, attribute_name, attribute_value): 66 | if vertex not in v_attrs: 67 | v_attrs[vertex] = {} 68 | v_attrs[vertex][attribute_name] = attribute_value 69 | 70 | def ea_cb(edge, attribute_name, attribute_value): 71 | if edge not in e_attrs: 72 | e_attrs[edge] = {} 73 | e_attrs[edge][attribute_name] = attribute_value 74 | 75 | edgelist = parse_edgelist_dot( 76 | input1, 77 | vertex_attribute_cb=va_cb, 78 | edge_attribute_cb=ea_cb, 79 | ) 80 | 81 | assert v_attrs == {'0': {'label': 'node 0'}} 82 | assert e_attrs == {0: {'cost': '33.0'}} 83 | 84 | assert list(edgelist) == [('0','1',1.0), ('0','2',1.0), ('0','3',1.0), ('2','3',1.0)] 85 | 86 | -------------------------------------------------------------------------------- /jgrapht/_internals/_attributes.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | from ._wrappers import _HandleWrapper 3 | 4 | 5 | class _JGraphTAttributeStore(_HandleWrapper): 6 | """Attribute Store. 7 | 8 | This attribute store is used by the exporters. Users 9 | provide attributes as strings which are automatically converted 10 | to UTF-8 encoded bytearrays and passed inside the Java context. 11 | """ 12 | 13 | def __init__(self, handle=None, **kwargs): 14 | if handle is None: 15 | handle = backend.jgrapht_xx_attributes_store_create() 16 | super().__init__(handle=handle, **kwargs) 17 | 18 | def put(self, element, key, value): 19 | encoded_key = bytearray(key, encoding="utf-8") 20 | encoded_value = bytearray(value, encoding="utf-8") 21 | backend.jgrapht_ii_attributes_store_put_string_attribute( 22 | self._handle, element, encoded_key, encoded_value 23 | ) 24 | 25 | def remove(self, element, key): 26 | encoded_key = bytearray(key, encoding="utf-8") 27 | backend.jgrapht_ii_attributes_store_remove_attribute( 28 | self._handle, element, encoded_key 29 | ) 30 | 31 | def __repr__(self): 32 | return "_JGraphTAttributeStore(%r)" % self._handle 33 | 34 | 35 | class _JGraphTAttributesRegistry(_HandleWrapper): 36 | """Attribute Registry. Used to keep a list of registered attributes 37 | for the exporters. 38 | """ 39 | 40 | def __init__(self, handle=None, **kwargs): 41 | if handle is None: 42 | handle = backend.jgrapht_attributes_registry_create() 43 | super().__init__(handle=handle, **kwargs) 44 | 45 | def put(self, name, category, attr_type=None, default_value=None): 46 | encoded_name = bytearray(name, encoding="utf-8") 47 | encoded_category = bytearray(category, encoding="utf-8") 48 | encoded_attr_type = ( 49 | bytearray(attr_type, encoding="utf-8") if attr_type is not None else None 50 | ) 51 | encoded_default_value = ( 52 | bytearray(default_value, encoding="utf-8") 53 | if default_value is not None 54 | else None 55 | ) 56 | backend.jgrapht_attributes_registry_register_attribute( 57 | self._handle, 58 | encoded_name, 59 | encoded_category, 60 | encoded_attr_type, 61 | encoded_default_value, 62 | ) 63 | 64 | def remove(self, name, category): 65 | encoded_name = bytearray(name, encoding="utf-8") 66 | encoded_category = bytearray(category, encoding="utf-8") 67 | backend.jgrapht_attributes_registry_unregister_attribute( 68 | self._handle, encoded_name, encoded_category, None, None 69 | ) 70 | 71 | def __repr__(self): 72 | return "_JGraphTAttributesRegistry(%r)" % self._handle 73 | -------------------------------------------------------------------------------- /docs/api/graph_factories.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _graph_factories: 3 | 4 | Graph Factories 5 | *************** 6 | 7 | .. currentmodule:: jgrapht 8 | 9 | Creating graphs can be accomplished using factory methods. During runtime the 10 | type of the graph can be queried using :py:attr:`.Graph.type` which returns a 11 | :py:class:`GraphType ` instance. This allows algorithms to adjust their 12 | behavior depending on the graph they are working on. 13 | 14 | 15 | Graph 16 | ^^^^^ 17 | 18 | The main factory function which creates graphs is :py:meth:`jgrapht.create_graph`. 19 | Depending on the given parameters different types of graphs can be represented. All graphs 20 | returned by this function are instances of :py:class:`jgrapht.types.Graph`. 21 | Most users should create graphs using this function: 22 | 23 | .. autofunction:: jgrapht.create_graph 24 | 25 | 26 | Sparse Graph 27 | ^^^^^^^^^^^^ 28 | 29 | The following function creates a special *sparse* graph representation which has certain 30 | benefits and certain drawbacks. The benefits are that (a) it is much smaller w.r.t memory 31 | consumption and (b) it is also usually much faster. The drawbacks are that the sparse 32 | representation (a) cannot be modified after construction and (b) is forces a user to 33 | use vertex and edges that are integers starting from 0 in a continuous range. Since modification 34 | is not possible, a user needs to bulk-load the graph by providing both the number of vertices 35 | and all edges before hand. 36 | 37 | .. autofunction:: jgrapht.create_sparse_graph 38 | 39 | A helper function :py:meth:`jgrapht.copy_to_sparse_graph` can help in order to create a sparse 40 | graph from another graph. 41 | 42 | .. autofunction:: jgrapht.copy_to_sparse_graph 43 | 44 | Building sparse graphs can be performed by using edge lists. See the section 45 | :ref:`edge list importers `. 46 | 47 | 48 | Succinct Graph 49 | ^^^^^^^^^^^^^^ 50 | 51 | The following function creates a special *succinct* graph representation. Succinct graphs 52 | are very compact representations close to the theoretical lower-bound. This means that it 53 | is going to be smaller than a sparse representation, but also a little bit slower. 54 | Similarly to the sparse representation they (a) cannot be modified after construction and (b) force 55 | the user to use vertex and edges that are integers starting from 0 in a continuous range. 56 | Since modification is not possible, a user needs to bulk-load the graph by providing both the number 57 | of vertices and all edges before hand. 58 | 59 | .. autofunction:: jgrapht.create_succinct_graph 60 | 61 | A helper function :py:meth:`jgrapht.copy_to_succinct_graph` can help in order to create a succinct 62 | graph from another graph. 63 | 64 | .. autofunction:: jgrapht.copy_to_succinct_graph 65 | 66 | Building succinct graphs can be performed by using edge lists. See the section 67 | :ref:`edge list importers `. 68 | 69 | 70 | -------------------------------------------------------------------------------- /jgrapht/algorithms/clustering.py: -------------------------------------------------------------------------------- 1 | import time 2 | from .. import backend as _backend 3 | from .._internals._results import _wrap_vertex_clustering 4 | 5 | 6 | def k_spanning_tree(graph, k): 7 | r"""The k spanning tree clustering algorithm. 8 | 9 | The algorithm finds a minimum spanning tree T using Prim's algorithm, then executes Kruskal's 10 | algorithm only on the edges of T until k trees are formed. The resulting trees are the final 11 | clusters. The total running time is :math:`\mathcal{O}(m + n \log n)`. 12 | 13 | The algorithm is strongly related to single linkage cluster analysis, also known as single-link 14 | clustering. For more information see: J. C. Gower and G. J. S. Ross. Minimum Spanning Trees and 15 | Single Linkage Cluster Analysis. Journal of the Royal Statistical Society. Series C (Applied 16 | Statistics), 18(1):54--64, 1969. 17 | 18 | :param graph: the graph. Needs to be undirected 19 | :param k: integer k, denoting the number of clusters 20 | :returns: a clustering as an instance of :py:class:`.Clustering` 21 | """ 22 | handle = _backend.jgrapht_xx_clustering_exec_k_spanning_tree(graph.handle, k) 23 | return _wrap_vertex_clustering(graph, handle) 24 | 25 | 26 | def label_propagation(graph, max_iterations=None, seed=None): 27 | r"""Label propagation clustering. 28 | 29 | The algorithm is a near linear time algorithm capable of discovering communities in large graphs. 30 | It is described in detail in the following `paper `_: 31 | 32 | * Raghavan, U. N., Albert, R., and Kumara, S. (2007). Near linear time algorithm to detect 33 | community structures in large-scale networks. Physical review E, 76(3), 036106. 34 | 35 | As the paper title suggests the running time is close to linear. The algorithm runs in iterations, 36 | each of which runs in :math:`\mathcal{O}(n + m)` where :math:`n` is the number of vertices and 37 | :math:`m` is the number of edges. The authors found experimentally that in most cases, 95% of the 38 | nodes or more are classified correctly by the end of iteration five. See the paper for more details. 39 | 40 | The algorithm is randomized, meaning that two runs on the same graph may return different results. 41 | If the user requires deterministic behavior, a random generator seed can be provided as a parameter. 42 | 43 | :param graph: the graph. Needs to be undirected 44 | :param max_iterations: maximum number of iterations (None means no limit) 45 | :param seed: seed for the random number generator, if None then the system time is used 46 | :returns: a clustering as an instance of :py:class:`.Clustering` 47 | """ 48 | if seed is None: 49 | seed = int(time.time()) 50 | if max_iterations is None: 51 | max_iterations = 0 52 | args = [max_iterations, seed] 53 | 54 | handle = _backend.jgrapht_xx_clustering_exec_label_propagation(graph.handle, *args) 55 | return _wrap_vertex_clustering(graph, handle) 56 | -------------------------------------------------------------------------------- /jgrapht/algorithms/connectivity.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import _wrap_vertex_set_iterator 3 | 4 | 5 | def is_weakly_connected(graph): 6 | r"""Computes weakly connected components in a directed graph or 7 | connected components in an undirected graph. 8 | 9 | This is a simple BFS based implementation. 10 | 11 | Running time :math:`\mathcal{O}(n+m)`. 12 | 13 | :param graph: the graph. 14 | :returns: a tuple containing a boolean value on whether the graph is connected 15 | and an iterator over all connected components. Each component is represented 16 | as a vertex set 17 | """ 18 | connected, sets = _backend.jgrapht_xx_connectivity_weak_exec_bfs(graph.handle) 19 | return connected, _wrap_vertex_set_iterator(graph, sets) 20 | 21 | 22 | def is_strongly_connected_gabow(graph): 23 | r"""Computes strongly connected components in a directed graph. 24 | 25 | This is Cheriyan-Mehlhorn/Gabow's algorithm and can be found at: 26 | 27 | * Gabow, Harold N. "Path-Based Depth-First Search for Strong and Biconnected 28 | Components; CU-CS-890-99." (1999). 29 | 30 | Running time :math:`\mathcal{O}(n+m)`. 31 | 32 | :param graph: the graph. Must be directed 33 | :returns: a tuple containing a boolean value on whether the graph is strongly connected 34 | and an iterator over all connected components. Each component is represented as a 35 | vertex set 36 | """ 37 | connected, sets = _backend.jgrapht_xx_connectivity_strong_exec_gabow(graph.handle) 38 | return connected, _wrap_vertex_set_iterator(graph, sets) 39 | 40 | 41 | def is_strongly_connected_kosaraju(graph): 42 | r"""Computes strongly connected components in a directed graph. 43 | 44 | This is an implementation based on: 45 | 46 | * Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction 47 | to algorithms. MIT press. 48 | 49 | Running time :math:`\mathcal{O}(n+m)`. 50 | 51 | :param graph: the graph. Must be directed 52 | :returns: a tuple containing a boolean value on whether the graph is strongly connected 53 | and an iterator over all connected components. Each component is represented as a 54 | vertex set 55 | """ 56 | connected, sets = _backend.jgrapht_xx_connectivity_strong_exec_kosaraju(graph.handle) 57 | return connected, _wrap_vertex_set_iterator(graph, sets) 58 | 59 | 60 | def is_connected(graph): 61 | """Compute connected components of a graph. 62 | 63 | For directed graphs this method computed strongly connected components. 64 | 65 | :param graph: the graph. Must be directed 66 | :returns: a tuple containing a boolean value on whether the graph is connected 67 | and an iterator over all connected components. Each component is represented as a 68 | vertex set 69 | """ 70 | if graph.type.directed: 71 | return is_strongly_connected_kosaraju(graph) 72 | else: 73 | return is_weakly_connected(graph) 74 | -------------------------------------------------------------------------------- /tests/test_drawing_circular_layout_2d.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.drawing as drawing 5 | 6 | 7 | def test_circular_layout(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=False, 11 | allowing_multiple_edges=False, 12 | weighted=False, 13 | ) 14 | g.add_vertices_from(range(0, 4)) 15 | g.add_edge(0, 1) 16 | g.add_edge(1, 2) 17 | g.add_edge(2, 3) 18 | g.add_edge(3, 0) 19 | 20 | area = (0, 0, 2, 2) 21 | model = drawing.circular_layout_2d(g, area, 1.0) 22 | 23 | assert model.area == area 24 | 25 | locations = [model.get_vertex_location(v) for v in g.vertices] 26 | 27 | assert locations == [ 28 | (2.0, 1.0), 29 | (1.0, 2.0), 30 | (0.0, 1.0000000000000002), 31 | (0.9999999999999998, 0.0), 32 | ] 33 | 34 | 35 | def test_circular_layout_with_comparator(): 36 | g = create_graph( 37 | directed=False, 38 | allowing_self_loops=False, 39 | allowing_multiple_edges=False, 40 | weighted=False, 41 | ) 42 | g.add_vertices_from(range(0, 4)) 43 | g.add_edge(0, 1) 44 | g.add_edge(1, 2) 45 | g.add_edge(2, 3) 46 | g.add_edge(3, 0) 47 | 48 | def vertex_comparator(v1, v2): 49 | if v1 > v2: 50 | return -1 51 | if v1 < v2: 52 | return 1 53 | return 0 54 | 55 | area = (0, 0, 2, 2) 56 | model = drawing.circular_layout_2d( 57 | g, area, 1.0, vertex_comparator_cb=vertex_comparator 58 | ) 59 | 60 | assert model.area == area 61 | 62 | locations = [model.get_vertex_location(v) for v in g.vertices] 63 | 64 | assert locations == [ 65 | (0.9999999999999998, 0.0), 66 | (0.0, 1.0000000000000002), 67 | (1.0, 2.0), 68 | (2.0, 1.0), 69 | ] 70 | 71 | 72 | def test_anyhashableg_circular_layout_with_comparator(): 73 | g = create_graph( 74 | directed=False, 75 | allowing_self_loops=False, 76 | allowing_multiple_edges=False, 77 | weighted=False, 78 | any_hashable=True, 79 | ) 80 | for i in range(0, 4): 81 | g.add_vertex(str(i)) 82 | g.add_edge("0", "1") 83 | g.add_edge("1", "2") 84 | g.add_edge("2", "3") 85 | g.add_edge("3", "0") 86 | 87 | def vertex_comparator(v1, v2): 88 | if int(v1) > int(v2): 89 | return -1 90 | if int(v1) < int(v2): 91 | return 1 92 | return 0 93 | 94 | area = (0, 0, 2, 2) 95 | model = drawing.circular_layout_2d( 96 | g, area, 1.0, vertex_comparator_cb=vertex_comparator 97 | ) 98 | 99 | assert model.area == area 100 | 101 | locations = [model.get_vertex_location(v) for v in g.vertices] 102 | 103 | assert locations == [ 104 | (0.9999999999999998, 0.0), 105 | (0.0, 1.0000000000000002), 106 | (1.0, 2.0), 107 | (2.0, 1.0), 108 | ] 109 | -------------------------------------------------------------------------------- /jgrapht/algorithms/isomorphism.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | 3 | from .._internals._mapping import ( 4 | _JGraphTIntegerGraphMapping, 5 | _JGraphTIntegerGraphMappingIterator, 6 | ) 7 | 8 | from .._internals._anyhashableg import _is_anyhashable_graph 9 | from .._internals._anyhashableg_mapping import ( 10 | _AnyHashableGraphGraphMapping, 11 | _AnyHashableGraphMappingIterator, 12 | ) 13 | 14 | 15 | def vf2(graph1, graph2): 16 | r"""The VF2 algorithm for detection of isomorphism between two graphs. 17 | 18 | * Cordella et al. A (sub)graph isomorphism algorithm for matching large graphs 19 | (2004), DOI:10.1109/TPAMI.2004.75 20 | 21 | This implementation of the VF2 algorithm does not support graphs with multiple-edges. 22 | 23 | .. note :: Graph mappings are represented using :py:class:`.GraphMapping` instances 24 | 25 | :param graph1: the first graph 26 | :param graph2: the second graph 27 | :returns: an iterator over graph mappings if the graphs are isomorphic, otherwise None 28 | """ 29 | exists, map_it_handle = _backend.jgrapht_xx_isomorphism_exec_vf2( 30 | graph1.handle, graph2.handle 31 | ) 32 | if not exists: 33 | return None 34 | 35 | if _is_anyhashable_graph(graph1) or _is_anyhashable_graph(graph2): 36 | return _AnyHashableGraphMappingIterator( 37 | handle=map_it_handle, graph1=graph1, graph2=graph2 38 | ) 39 | else: 40 | return _JGraphTIntegerGraphMappingIterator( 41 | handle=map_it_handle, graph1=graph1, graph2=graph2 42 | ) 43 | 44 | 45 | def vf2_subgraph(graph1, graph2): 46 | r"""The VF2 algorithm for detection of subgraph isomorphism between two graphs. 47 | 48 | * Cordella et al. A (sub)graph isomorphism algorithm for matching large graphs 49 | (2004), DOI:10.1109/TPAMI.2004.75 50 | 51 | This implementation of the VF2 algorithm does not support graphs with multiple-edges. 52 | 53 | .. note :: Graph mappings are represented using :py:class:`.GraphMapping` instances 54 | 55 | .. warning :: This algorithm only finds isomorphisms between a smaller graph and all 56 | `induced subgraphs `_ of a 57 | larger graph. It does **not** find isomorphisms between the smaller graph and arbitrary 58 | subgraphs of the larger graph. 59 | 60 | :param graph1: the first graph 61 | :param graph2: the second graph 62 | :returns: an iterator over graph mappings if the graphs are isomorphic, otherwise None 63 | """ 64 | exists, map_it_handle = _backend.jgrapht_xx_isomorphism_exec_vf2_subgraph( 65 | graph1.handle, graph2.handle 66 | ) 67 | if not exists: 68 | return None 69 | 70 | if _is_anyhashable_graph(graph1) or _is_anyhashable_graph(graph2): 71 | return _AnyHashableGraphMappingIterator( 72 | handle=map_it_handle, graph1=graph1, graph2=graph2 73 | ) 74 | else: 75 | return _JGraphTIntegerGraphMappingIterator( 76 | handle=map_it_handle, graph1=graph1, graph2=graph2 77 | ) 78 | -------------------------------------------------------------------------------- /jgrapht/_internals/_drawing.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from ..types import LayoutModel2D 3 | from .. import backend 4 | from ._wrappers import _HandleWrapper 5 | 6 | _box2d_class = namedtuple("Box2D", ["min_x", "min_y", "width", "height"]) 7 | _point2d_class = namedtuple("Point2D", ["x", "y"]) 8 | 9 | 10 | class _JGraphTIntegerLayoutModel2D(_HandleWrapper, LayoutModel2D): 11 | """A 2D layout model.""" 12 | 13 | def __init__(self, handle, **kwargs): 14 | super().__init__(handle=handle, **kwargs) 15 | 16 | @property 17 | def area(self): 18 | res = backend.jgrapht_xx_drawing_layout_model_2d_get_drawable_area(self.handle) 19 | return _box2d_class(*res) 20 | 21 | def get_vertex_location(self, vertex): 22 | res = backend.jgrapht_ix_drawing_layout_model_2d_get_vertex(self.handle, vertex) 23 | return _point2d_class(*res) 24 | 25 | def set_vertex_location(self, vertex, point_2d): 26 | backend.jgrapht_ix_drawing_layout_model_2d_put_vertex( 27 | self.handle, vertex, *point_2d 28 | ) 29 | 30 | def is_fixed(self, vertex): 31 | return backend.jgrapht_ix_drawing_layout_model_2d_get_fixed(self.handle, vertex) 32 | 33 | def set_fixed(self, vertex, fixed): 34 | backend.jgrapht_ix_drawing_layout_model_2d_set_fixed(self.handle, vertex, fixed) 35 | 36 | def __repr__(self): 37 | return "_JGraphTIntegerLayoutModel2D(%r)" % self._handle 38 | 39 | 40 | class _JGraphTLongLayoutModel2D(_HandleWrapper, LayoutModel2D): 41 | """A 2D layout model.""" 42 | 43 | def __init__(self, handle, **kwargs): 44 | super().__init__(handle=handle, **kwargs) 45 | 46 | @property 47 | def area(self): 48 | res = backend.jgrapht_xx_drawing_layout_model_2d_get_drawable_area(self.handle) 49 | return _box2d_class(*res) 50 | 51 | def get_vertex_location(self, vertex): 52 | res = backend.jgrapht_lx_drawing_layout_model_2d_get_vertex(self.handle, vertex) 53 | return _point2d_class(*res) 54 | 55 | def set_vertex_location(self, vertex, point_2d): 56 | backend.jgrapht_lx_drawing_layout_model_2d_put_vertex( 57 | self.handle, vertex, *point_2d 58 | ) 59 | 60 | def is_fixed(self, vertex): 61 | return backend.jgrapht_lx_drawing_layout_model_2d_get_fixed(self.handle, vertex) 62 | 63 | def set_fixed(self, vertex, fixed): 64 | backend.jgrapht_lx_drawing_layout_model_2d_set_fixed(self.handle, vertex, fixed) 65 | 66 | def __repr__(self): 67 | return "_JGraphTLongLayoutModel2D(%r)" % self._handle 68 | 69 | 70 | def _create_int_layout_model_2d(min_x, min_y, width, height): 71 | """Factory for a 2d layout model.""" 72 | handle = backend.jgrapht_xx_drawing_layout_model_2d_create(min_x, min_y, width, height) 73 | return _JGraphTIntegerLayoutModel2D(handle) 74 | 75 | 76 | def _create_long_layout_model_2d(min_x, min_y, width, height): 77 | """Factory for a 2d layout model.""" 78 | handle = backend.jgrapht_xx_drawing_layout_model_2d_create(min_x, min_y, width, height) 79 | return _JGraphTLongLayoutModel2D(handle) 80 | -------------------------------------------------------------------------------- /jgrapht/_internals/_ioutils.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | from ._callbacks import _create_wrapped_callback 4 | 5 | 6 | def _create_wrapped_attribute_callback(callback, id_type=ctypes.c_int): 7 | if callback is not None: 8 | callback_ctype = ctypes.CFUNCTYPE( 9 | None, id_type, ctypes.c_char_p, ctypes.c_char_p 10 | ) 11 | 12 | # We wrap in order to decode string representation. 13 | # There is no SWIG layer here, as the capi calls us directly 14 | # using a function pointer. This means that the arguments 15 | # are bytearrays. 16 | def decoder_callback(id, key, value): 17 | decoded_key = key.decode(encoding="utf-8") 18 | decoded_value = value.decode(encoding="utf-8") 19 | callback(id, decoded_key, decoded_value) 20 | 21 | return _create_wrapped_callback(decoder_callback, callback_ctype) 22 | else: 23 | return (0, None) 24 | 25 | 26 | def _create_wrapped_strid_attribute_callback(callback, id_type=ctypes.c_int): 27 | if callback is not None: 28 | callback_ctype = ctypes.CFUNCTYPE( 29 | None, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p 30 | ) 31 | 32 | # We wrap in order to decode string representation. 33 | # There is no SWIG layer here, as the capi calls us directly 34 | # using a function pointer. This means that the arguments 35 | # are bytearrays. 36 | def decoder_callback(id, key, value): 37 | decoded_id = id.decode(encoding="utf-8") 38 | decoded_key = key.decode(encoding="utf-8") 39 | decoded_value = value.decode(encoding="utf-8") 40 | callback(decoded_id, decoded_key, decoded_value) 41 | 42 | return _create_wrapped_callback(decoder_callback, callback_ctype) 43 | else: 44 | return (0, None) 45 | 46 | 47 | def _create_wrapped_import_string_id_callback(callback, id_type=ctypes.c_int): 48 | if callback is not None: 49 | callback_ctype = ctypes.CFUNCTYPE(id_type, ctypes.c_char_p) 50 | 51 | # We wrap in order to decode string representation. 52 | # There is no SWIG layer here, as the capi calls us directly 53 | # using a function pointer. This means that the arguments 54 | # are bytearrays. 55 | def decoder_callback(id): 56 | decoded_id = id.decode(encoding="utf-8") 57 | return callback(decoded_id) 58 | 59 | return _create_wrapped_callback(decoder_callback, callback_ctype) 60 | else: 61 | return (0, None) 62 | 63 | 64 | def _create_wrapped_import_integer_id_callback(callback, id_type=ctypes.c_int): 65 | if callback is not None: 66 | callback_ctype = ctypes.CFUNCTYPE(id_type, id_type) 67 | return _create_wrapped_callback(callback, callback_ctype) 68 | else: 69 | return (0, None) 70 | 71 | 72 | def _create_wrapped_notify_id_callback(callback, id_type=ctypes.c_int): 73 | if callback is not None: 74 | callback_ctype = ctypes.CFUNCTYPE(None, id_type) 75 | return _create_wrapped_callback(callback, callback_ctype) 76 | else: 77 | return (0, None) 78 | -------------------------------------------------------------------------------- /tests/test_io_lemon.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | from jgrapht.io.exporters import write_lemon, generate_lemon 5 | 6 | 7 | def build_graph(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=False, 11 | allowing_multiple_edges=False, 12 | weighted=True, 13 | ) 14 | 15 | for i in range(0, 10): 16 | g.add_vertex(i) 17 | 18 | g.add_edge(0, 1) 19 | g.add_edge(0, 2) 20 | g.add_edge(0, 3) 21 | g.add_edge(0, 4) 22 | g.add_edge(0, 5) 23 | g.add_edge(0, 6) 24 | g.add_edge(0, 7) 25 | g.add_edge(0, 8) 26 | g.add_edge(0, 9) 27 | 28 | g.add_edge(1, 2) 29 | g.add_edge(2, 3) 30 | g.add_edge(3, 4) 31 | g.add_edge(4, 5) 32 | g.add_edge(5, 6) 33 | g.add_edge(6, 7) 34 | g.add_edge(7, 8) 35 | g.add_edge(8, 9) 36 | g.add_edge(9, 1) 37 | 38 | return g 39 | 40 | 41 | lemon_expected = """#Creator: JGraphT Lemon (LGF) Exporter 42 | #Version: 1 43 | 44 | @nodes 45 | label 46 | 0 47 | 1 48 | 2 49 | 3 50 | 4 51 | 5 52 | 6 53 | 7 54 | 8 55 | 9 56 | 57 | @arcs 58 | - 59 | 0 1 60 | 0 2 61 | 0 3 62 | 0 4 63 | 0 5 64 | 0 6 65 | 0 7 66 | 0 8 67 | 0 9 68 | 1 2 69 | 2 3 70 | 3 4 71 | 4 5 72 | 5 6 73 | 6 7 74 | 7 8 75 | 8 9 76 | 9 1 77 | 78 | """ 79 | 80 | expected2 = r"""#Creator: JGraphT Lemon (LGF) Exporter 81 | #Version: 1 82 | 83 | @nodes 84 | label 85 | 0 86 | 1 87 | 2 88 | 3 89 | 90 | @arcs 91 | - 92 | 0 1 93 | 0 2 94 | 0 3 95 | 2 3 96 | 97 | """ 98 | 99 | 100 | expected3 = r"""#Creator: JGraphT Lemon (LGF) Exporter 101 | #Version: 1 102 | 103 | @nodes 104 | label 105 | v0 106 | v1 107 | v2 108 | v3 109 | 110 | @arcs 111 | - 112 | v0 v1 113 | v0 v2 114 | v0 v3 115 | v2 v3 116 | 117 | """ 118 | 119 | def test_lemon(tmpdir): 120 | g = build_graph() 121 | tmpfile = tmpdir.join("lemon.out") 122 | tmpfilename = str(tmpfile) 123 | write_lemon(g, tmpfilename) 124 | 125 | with open(tmpfilename, "r") as f: 126 | contents = f.read() 127 | print(contents) 128 | 129 | assert contents == lemon_expected 130 | 131 | 132 | def test_output_to_string(): 133 | g = create_graph( 134 | directed=True, 135 | allowing_self_loops=False, 136 | allowing_multiple_edges=True, 137 | weighted=False, 138 | ) 139 | 140 | g.add_vertices_from(range(0, 4)) 141 | 142 | g.add_edge(0, 1) 143 | g.add_edge(0, 2) 144 | g.add_edge(0, 3) 145 | g.add_edge(2, 3) 146 | 147 | out = generate_lemon(g) 148 | assert out.splitlines() == expected2.splitlines() 149 | 150 | 151 | def test_output_to_string_with_custom_ids(): 152 | g = create_graph( 153 | directed=True, 154 | allowing_self_loops=False, 155 | allowing_multiple_edges=True, 156 | weighted=False, 157 | ) 158 | 159 | g.add_vertices_from(range(0, 4)) 160 | 161 | g.add_edge(0, 1) 162 | g.add_edge(0, 2) 163 | g.add_edge(0, 3) 164 | g.add_edge(2, 3) 165 | 166 | def export_vertex(id): 167 | return 'v{}'.format(id) 168 | 169 | out = generate_lemon(g, export_vertex_id_cb=export_vertex) 170 | assert out.splitlines() == expected3.splitlines() 171 | -------------------------------------------------------------------------------- /jgrapht/_internals/_anyhashableg_mapping.py: -------------------------------------------------------------------------------- 1 | from .. import backend 2 | 3 | from ..types import GraphMapping 4 | 5 | from ._wrappers import ( 6 | _HandleWrapper, 7 | _JGraphTObjectIterator, 8 | ) 9 | 10 | from ._anyhashableg import ( 11 | _vertex_anyhashableg_to_g, 12 | _vertex_g_to_anyhashableg, 13 | ) 14 | from ._anyhashableg import ( 15 | _edge_anyhashableg_to_g, 16 | _edge_g_to_anyhashableg, 17 | ) 18 | 19 | 20 | class _AnyHashableGraphGraphMapping(_HandleWrapper, GraphMapping): 21 | """A mapping between two graphs g1 and g2.""" 22 | 23 | def __init__(self, handle, graph1, graph2, **kwargs): 24 | super().__init__(handle=handle, **kwargs) 25 | self._graph1 = graph1 26 | self._graph2 = graph2 27 | 28 | def vertex_correspondence(self, vertex, forward=True): 29 | if forward: 30 | vertex = _vertex_anyhashableg_to_g(self._graph1, vertex) 31 | else: 32 | vertex = _vertex_anyhashableg_to_g(self._graph2, vertex) 33 | ( 34 | exists, 35 | other, 36 | ) = backend.jgrapht_ix_isomorphism_graph_mapping_vertex_correspondence( 37 | self._handle, vertex, forward 38 | ) 39 | if not exists: 40 | return None 41 | 42 | if forward: 43 | return _vertex_g_to_anyhashableg(self._graph2, other) 44 | else: 45 | return _vertex_g_to_anyhashableg(self._graph1, other) 46 | 47 | def edge_correspondence(self, edge, forward=True): 48 | if forward: 49 | edge = _edge_anyhashableg_to_g(self._graph1, edge) 50 | else: 51 | edge = _edge_anyhashableg_to_g(self._graph2, edge) 52 | ( 53 | exists, 54 | other, 55 | ) = backend.jgrapht_xi_isomorphism_graph_mapping_edge_correspondence( 56 | self._handle, edge, forward 57 | ) 58 | if not exists: 59 | return None 60 | 61 | if forward: 62 | return _edge_g_to_anyhashableg(self._graph2, other) 63 | else: 64 | return _edge_g_to_anyhashableg(self._graph1, other) 65 | 66 | def vertices_correspondence(self, forward=True): 67 | vertices = self._graph1.vertices if forward else self._graph2.vertices 68 | result = dict() 69 | for v in vertices: 70 | result[v] = self.vertex_correspondence(v, forward=forward) 71 | return result 72 | 73 | def edges_correspondence(self, forward=True): 74 | edges = self._graph1.edges if forward else self._graph2.edges 75 | result = dict() 76 | for e in edges: 77 | result[e] = self.edge_correspondence(e, forward=forward) 78 | return result 79 | 80 | def __repr__(self): 81 | return "_AnyHashableGraphGraphMapping(%r)" % self._handle 82 | 83 | 84 | class _AnyHashableGraphMappingIterator(_JGraphTObjectIterator): 85 | """A graph mapping iterator""" 86 | 87 | def __init__(self, handle, graph1, graph2, **kwargs): 88 | super().__init__(handle=handle, **kwargs) 89 | self._graph1 = graph1 90 | self._graph2 = graph2 91 | 92 | def __next__(self): 93 | item = super().__next__() 94 | return _AnyHashableGraphGraphMapping(item, self._graph1, self._graph2) 95 | 96 | def __repr__(self): 97 | return "_AnyHashableGraphMappingIterator(%r)" % self._handle 98 | -------------------------------------------------------------------------------- /tests/test_edgelist_sparse.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_sparse_graph 4 | from jgrapht.io.edgelist import parse_edgelist_json 5 | 6 | input1 = r'{"creator":"JGraphT JSON Exporter","version":"1","nodes":[{"id":"0","label":"\u03BA\u03CC\u03BC\u03B2\u03BF\u03C2 0"},{"id":"1","label":"label 1"},{"id":"2","label":"label 2"},{"id":"3","label":"label 3"},{"id":"4"},{"id":"5"},{"id":"6"},{"id":"7"},{"id":"8"},{"id":"9"}],"edges":[{"source":"0","target":"1"},{"source":"0","target":"2"},{"source":"0","target":"3"},{"source":"0","target":"4"},{"source":"0","target":"5"},{"source":"0","target":"6"},{"source":"0","target":"7"},{"source":"0","target":"8"},{"source":"0","target":"9"},{"source":"1","target":"2","label":"edge 1-2"},{"source":"2","target":"3"},{"source":"3","target":"4"},{"source":"4","target":"5"},{"source":"5","target":"6"},{"source":"6","target":"7"},{"source":"7","target":"8"},{"source":"8","target":"9"},{"source":"9","target":"1"}]}' 7 | 8 | 9 | 10 | def test_graph_sparse_weighted(): 11 | edgelist = parse_edgelist_json(input1) 12 | 13 | assert list(edgelist) == [ 14 | ('0', '1', 1.0), 15 | ('0', '2', 1.0), 16 | ('0', '3', 1.0), 17 | ('0', '4', 1.0), 18 | ('0', '5', 1.0), 19 | ('0', '6', 1.0), 20 | ('0', '7', 1.0), 21 | ('0', '8', 1.0), 22 | ('0', '9', 1.0), 23 | ('1', '2', 1.0), 24 | ('2', '3', 1.0), 25 | ('3', '4', 1.0), 26 | ('4', '5', 1.0), 27 | ('5', '6', 1.0), 28 | ('6', '7', 1.0), 29 | ('7', '8', 1.0), 30 | ('8', '9', 1.0), 31 | ('9', '1', 1.0), 32 | ] 33 | 34 | edgelist = [(int(s), int(t), w) for s, t, w in edgelist] 35 | 36 | g = create_sparse_graph(edgelist, 10, directed=True) 37 | 38 | print(g) 39 | 40 | assert g.type.directed 41 | assert g.type.weighted 42 | 43 | assert g.vertices == set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 44 | 45 | edgelist2 = [] 46 | for e in g.edges: 47 | edgelist2.append(g.edge_tuple(e)) 48 | assert edgelist2 == list(edgelist) 49 | 50 | # sparse graphs cannot be modified 51 | with pytest.raises(ValueError): 52 | g.add_edge(0, 5) 53 | 54 | 55 | def test_graph_sparse_weighted_no_vertex_count(): 56 | edgelist = parse_edgelist_json(input1) 57 | 58 | assert list(edgelist) == [ 59 | ('0', '1', 1.0), 60 | ('0', '2', 1.0), 61 | ('0', '3', 1.0), 62 | ('0', '4', 1.0), 63 | ('0', '5', 1.0), 64 | ('0', '6', 1.0), 65 | ('0', '7', 1.0), 66 | ('0', '8', 1.0), 67 | ('0', '9', 1.0), 68 | ('1', '2', 1.0), 69 | ('2', '3', 1.0), 70 | ('3', '4', 1.0), 71 | ('4', '5', 1.0), 72 | ('5', '6', 1.0), 73 | ('6', '7', 1.0), 74 | ('7', '8', 1.0), 75 | ('8', '9', 1.0), 76 | ('9', '1', 1.0), 77 | ] 78 | 79 | edgelist = [(int(s), int(t), w) for s, t, w in edgelist] 80 | 81 | g = create_sparse_graph(edgelist, directed=True) 82 | 83 | print(g) 84 | 85 | assert g.type.directed 86 | assert g.type.weighted 87 | 88 | assert g.vertices == set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 89 | 90 | edgelist2 = [] 91 | for e in g.edges: 92 | edgelist2.append(g.edge_tuple(e)) 93 | assert edgelist2 == list(edgelist) 94 | 95 | # sparse graphs cannot be modified 96 | with pytest.raises(ValueError): 97 | g.add_edge(0, 5) -------------------------------------------------------------------------------- /tests/test_link_prediction.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | 5 | import jgrapht.algorithms.linkprediction as linkprediction 6 | import jgrapht.generators as generators 7 | 8 | 9 | def build_graph(): 10 | g = create_graph( 11 | directed=False, 12 | allowing_self_loops=False, 13 | allowing_multiple_edges=False, 14 | weighted=True, 15 | ) 16 | 17 | for i in range(0, 10): 18 | g.add_vertex(i) 19 | 20 | g.add_edge(0, 1) 21 | g.add_edge(0, 2) 22 | g.add_edge(0, 3) 23 | g.add_edge(0, 4) 24 | g.add_edge(0, 5) 25 | g.add_edge(0, 6) 26 | g.add_edge(0, 7) 27 | g.add_edge(0, 8) 28 | g.add_edge(0, 9) 29 | 30 | g.add_edge(1, 2) 31 | g.add_edge(2, 3) 32 | g.add_edge(3, 4) 33 | g.add_edge(4, 5) 34 | g.add_edge(5, 6) 35 | g.add_edge(6, 7) 36 | g.add_edge(7, 8) 37 | g.add_edge(8, 9) 38 | g.add_edge(9, 1) 39 | 40 | g.add_edge(1, 6) 41 | 42 | return g 43 | 44 | 45 | def test_adamic_adar(): 46 | g = build_graph() 47 | 48 | scores = [linkprediction.adamic_adar_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 49 | assert scores == pytest.approx([1.1764671337579005, 0.45511961331341866]) 50 | 51 | 52 | def test_common_neighbors(): 53 | g = build_graph() 54 | 55 | scores = [linkprediction.common_neighbors(g, u, v) for u, v in [(1, 7), (1, 6)]] 56 | assert scores == pytest.approx([2.0, 1.0]) 57 | 58 | 59 | def test_hub_depressed_index(): 60 | g = build_graph() 61 | 62 | scores = [linkprediction.hub_depressed_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 63 | print(scores) 64 | assert scores == pytest.approx([0.5, 0.25]) 65 | 66 | 67 | def test_hub_promoted_index(): 68 | g = build_graph() 69 | 70 | scores = [linkprediction.hub_promoted_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 71 | assert scores == pytest.approx([0.6666666666666666, 0.25]) 72 | 73 | 74 | def test_jaccard_coefficient(): 75 | g = build_graph() 76 | 77 | scores = [linkprediction.jaccard_coefficient(g, u, v) for u, v in [(1, 7), (1, 6)]] 78 | assert scores == pytest.approx([0.4, 0.14285714285714285]) 79 | 80 | 81 | def test_leicht_holme_newman_index(): 82 | g = build_graph() 83 | 84 | scores = [linkprediction.leicht_holme_newman_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 85 | assert scores == pytest.approx([1.5, 1.0]) 86 | 87 | 88 | def test_preferential_attachment(): 89 | g = build_graph() 90 | 91 | scores = [linkprediction.preferential_attachment(g, u, v) for u, v in [(1, 7), (1, 6)]] 92 | assert scores == pytest.approx([12.0, 16.0]) 93 | 94 | 95 | def test_resource_allocation_index(): 96 | g = build_graph() 97 | 98 | scores = [linkprediction.resource_allocation_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 99 | assert scores == pytest.approx([0.3611111111111111, 0.1111111111111111]) 100 | 101 | 102 | def test_salton_index(): 103 | g = build_graph() 104 | 105 | scores = [linkprediction.salton_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 106 | assert scores == pytest.approx([0.5773502691896258, 0.25]) 107 | 108 | 109 | def test_sorensen_index(): 110 | g = build_graph() 111 | 112 | scores = [linkprediction.sorensen_index(g, u, v) for u, v in [(1, 7), (1, 6)]] 113 | assert scores == pytest.approx([0.5714285714285714, 0.25]) 114 | -------------------------------------------------------------------------------- /tests/test_mst.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | 5 | import jgrapht.algorithms.spanning as spanning 6 | import jgrapht.generators as generators 7 | 8 | 9 | def build_graph(): 10 | g = create_graph( 11 | directed=False, 12 | allowing_self_loops=False, 13 | allowing_multiple_edges=False, 14 | weighted=True, 15 | ) 16 | 17 | for i in range(0, 10): 18 | g.add_vertex(i) 19 | 20 | g.add_edge(0, 1) 21 | g.add_edge(0, 2) 22 | g.add_edge(0, 3) 23 | g.add_edge(0, 4) 24 | g.add_edge(0, 5) 25 | g.add_edge(0, 6) 26 | g.add_edge(0, 7) 27 | g.add_edge(0, 8) 28 | g.add_edge(0, 9) 29 | 30 | g.add_edge(1, 2) 31 | g.add_edge(2, 3) 32 | g.add_edge(3, 4) 33 | g.add_edge(4, 5) 34 | g.add_edge(5, 6) 35 | g.add_edge(6, 7) 36 | g.add_edge(7, 8) 37 | g.add_edge(8, 9) 38 | g.add_edge(9, 1) 39 | 40 | return g 41 | 42 | 43 | def test_kruskal(): 44 | g = build_graph() 45 | mst_w, mst_edges = spanning.kruskal(g) 46 | assert mst_w == 9.0 47 | expected = set([0, 1, 2, 3, 4, 5, 6, 7, 8]) 48 | solution = set(mst_edges) 49 | assert expected == solution 50 | 51 | 52 | def test_prim(): 53 | g = build_graph() 54 | mst_w, mst_edges = spanning.prim(g) 55 | assert mst_w == 9.0 56 | expected = set([0, 1, 2, 3, 4, 5, 6, 7, 8]) 57 | solution = set(mst_edges) 58 | assert expected == solution 59 | 60 | 61 | def test_boruvka(): 62 | g = build_graph() 63 | mst_w, mst_edges = spanning.boruvka(g) 64 | assert mst_w == 9.0 65 | expected = set([0, 1, 2, 3, 4, 5, 6, 7, 8]) 66 | solution = set(mst_edges) 67 | assert expected == solution 68 | 69 | 70 | def test_small_graph_prim(): 71 | g = create_graph(directed=False) 72 | 73 | generators.gnp_random_graph(g, n=500, p=0.1, seed=17) 74 | 75 | mst_w, mst_edges = spanning.prim(g) 76 | assert mst_w == 499.0 77 | 78 | 79 | def test_anyhashableg_prim(): 80 | g = create_graph( 81 | directed=False, 82 | allowing_self_loops=False, 83 | allowing_multiple_edges=False, 84 | weighted=True, 85 | any_hashable=True, 86 | ) 87 | 88 | g.add_vertex("0") 89 | g.add_vertex("1") 90 | g.add_vertex("2") 91 | 92 | e1 = g.add_edge("0", "1") 93 | g.set_edge_weight(e1, 1.0) 94 | e2 = g.add_edge("1", "2") 95 | g.set_edge_weight(e2, 2.0) 96 | e3 = g.add_edge("2", "0") 97 | g.set_edge_weight(e3, 3.0) 98 | 99 | mst_w, mst_edges = spanning.prim(g) 100 | assert mst_w == 3.0 101 | assert set(mst_edges) == {e1, e2} 102 | 103 | def test_result_with_difference(): 104 | g = build_graph() 105 | mst_weight, mst_tree = spanning.prim(g) 106 | 107 | non_mst_edges = g.edges - set(mst_tree) 108 | 109 | # test that our intermediate set results, property implement 110 | # method _from_iterable 111 | 112 | assert non_mst_edges == { 9, 10, 11, 12, 13, 14, 15, 16, 17 } 113 | 114 | def test_result_with_difference_symmetric(): 115 | g = build_graph() 116 | mst_weight, mst_tree = spanning.prim(g) 117 | 118 | non_mst_edges = g.edges - set(mst_tree) 119 | 120 | # test that our intermediate set results, property implement 121 | # method _from_iterable 122 | 123 | assert non_mst_edges == { 9, 10, 11, 12, 13, 14, 15, 16, 17 } 124 | -------------------------------------------------------------------------------- /tests/test_clustering.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.clustering as clustering 5 | 6 | 7 | def test_k_spanning_tree(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=False, 11 | allowing_multiple_edges=False, 12 | weighted=True, 13 | ) 14 | 15 | for i in range(0, 6): 16 | g.add_vertex(i) 17 | 18 | g.add_edge(0, 1) 19 | g.add_edge(1, 2) 20 | g.add_edge(2, 0) 21 | 22 | g.add_edge(3, 4) 23 | g.add_edge(4, 5) 24 | g.add_edge(5, 3) 25 | 26 | g.add_edge(2, 3, weight=100.0) 27 | 28 | assert len(g.edges) == 7 29 | 30 | c = clustering.k_spanning_tree(g, k=2) 31 | 32 | repr(c) 33 | 34 | assert c.number_of_clusters() == 2 35 | assert set(c.ith_cluster(0)) == set([0, 1, 2]) 36 | assert set(c.ith_cluster(1)) == set([3, 4, 5]) 37 | 38 | 39 | def test_label_propagation(): 40 | g = create_graph( 41 | directed=False, 42 | allowing_self_loops=False, 43 | allowing_multiple_edges=False, 44 | weighted=True, 45 | ) 46 | 47 | for i in range(0, 6): 48 | g.add_vertex(i) 49 | 50 | g.add_edge(0, 1) 51 | g.add_edge(1, 2) 52 | g.add_edge(2, 0) 53 | 54 | g.add_edge(3, 4) 55 | g.add_edge(4, 5) 56 | g.add_edge(5, 3) 57 | 58 | g.add_edge(2, 3, weight=100.0) 59 | 60 | assert len(g.edges) == 7 61 | 62 | c = clustering.label_propagation(g, seed=17) 63 | 64 | assert c.number_of_clusters() == 2 65 | assert set(c.ith_cluster(0)) == set([0, 1, 2]) 66 | assert set(c.ith_cluster(1)) == set([3, 4, 5]) 67 | 68 | # test with auto seed 69 | c = clustering.label_propagation(g) 70 | assert c.number_of_clusters() > 0 71 | 72 | 73 | def test_anyhashableg_k_spanning_tree(): 74 | g = create_graph( 75 | directed=False, 76 | allowing_self_loops=False, 77 | allowing_multiple_edges=False, 78 | weighted=True, 79 | any_hashable=True, 80 | ) 81 | 82 | for i in range(0, 6): 83 | g.add_vertex(str(i)) 84 | 85 | g.add_edge(str(0), str(1)) 86 | g.add_edge(str(1), str(2)) 87 | g.add_edge(str(2), str(0)) 88 | g.add_edge(str(3), str(4)) 89 | g.add_edge(str(4), str(5)) 90 | g.add_edge(str(5), str(3)) 91 | g.add_edge(str(2), str(3), weight=100.0) 92 | 93 | assert len(g.edges) == 7 94 | 95 | c = clustering.k_spanning_tree(g, k=2) 96 | 97 | assert c.number_of_clusters() == 2 98 | assert set(c.ith_cluster(0)) == set(["0", "1", "2"]) 99 | assert set(c.ith_cluster(1)) == set(["3", "4", "5"]) 100 | 101 | 102 | def test_anyhashableg_label_propagation(): 103 | g = create_graph( 104 | directed=False, 105 | allowing_self_loops=False, 106 | allowing_multiple_edges=False, 107 | weighted=True, 108 | any_hashable=True, 109 | ) 110 | 111 | for i in range(0, 6): 112 | g.add_vertex(i) 113 | 114 | g.add_edge(0, 1) 115 | g.add_edge(1, 2) 116 | g.add_edge(2, 0) 117 | 118 | g.add_edge(3, 4) 119 | g.add_edge(4, 5) 120 | g.add_edge(5, 3) 121 | 122 | g.add_edge(2, 3, weight=100.0) 123 | 124 | assert len(g.edges) == 7 125 | 126 | c = clustering.label_propagation(g, seed=17) 127 | 128 | assert c.number_of_clusters() == 2 129 | assert set(c.ith_cluster(0)) == set([0, 1, 2]) 130 | assert set(c.ith_cluster(1)) == set([3, 4, 5]) 131 | 132 | -------------------------------------------------------------------------------- /jgrapht/algorithms/linkprediction.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import ( 3 | _unwrap_vertex, 4 | ) 5 | 6 | 7 | def _linkprediction_alg(name, graph, u, v): 8 | alg_method = getattr(_backend, "jgrapht_ix_link_prediction_exec_{}".format(name)) 9 | score = alg_method( 10 | graph.handle, _unwrap_vertex(graph, u), _unwrap_vertex(graph, v) 11 | ) 12 | return score 13 | 14 | 15 | def adamic_adar_index(graph, u, v): 16 | """Compute the Adamic-Adar index. 17 | 18 | :param graph: the graph 19 | :param u: a vertex 20 | :param v: a vertex 21 | :returns: the score 22 | """ 23 | return _linkprediction_alg("adamic_adar_index", graph, u, v) 24 | 25 | 26 | def common_neighbors(graph, u, v): 27 | """Compute the number of common neighbors. 28 | 29 | :param graph: the graph 30 | :param u: a vertex 31 | :param v: a vertex 32 | :returns: the score 33 | """ 34 | return _linkprediction_alg("common_neighbors", graph, u, v) 35 | 36 | 37 | def hub_depressed_index(graph, u, v): 38 | """Compute the Hub Depressed index. 39 | 40 | :param graph: the graph 41 | :param u: a vertex 42 | :param v: a vertex 43 | :returns: the score 44 | """ 45 | return _linkprediction_alg("hub_depressed_index", graph, u, v) 46 | 47 | 48 | def hub_promoted_index(graph, u, v): 49 | """Compute the Hub Promoted index. 50 | 51 | :param graph: the graph 52 | :param u: a vertex 53 | :param v: a vertex 54 | :returns: the score 55 | """ 56 | return _linkprediction_alg("hub_promoted_index", graph, u, v) 57 | 58 | 59 | def jaccard_coefficient(graph, u, v): 60 | """Compute the Jaccard coefficient. 61 | 62 | :param graph: the graph 63 | :param u: a vertex 64 | :param v: a vertex 65 | :returns: the score 66 | """ 67 | return _linkprediction_alg("jaccard_coefficient", graph, u, v) 68 | 69 | 70 | def leicht_holme_newman_index(graph, u, v): 71 | """Compute the Leicht-Holme-Newman index. 72 | 73 | :param graph: the graph 74 | :param u: a vertex 75 | :param v: a vertex 76 | :returns: the score 77 | """ 78 | return _linkprediction_alg("leicht_holme_newman_index", graph, u, v) 79 | 80 | 81 | def preferential_attachment(graph, u, v): 82 | """Compute the preferential attachment index. 83 | 84 | :param graph: the graph 85 | :param u: a vertex 86 | :param v: a vertex 87 | :returns: the score 88 | """ 89 | return _linkprediction_alg("preferential_attachment", graph, u, v) 90 | 91 | 92 | def resource_allocation_index(graph, u, v): 93 | """Compute the Resource Allocation index. 94 | 95 | :param graph: the graph 96 | :param u: a vertex 97 | :param v: a vertex 98 | :returns: the score 99 | """ 100 | return _linkprediction_alg("resource_allocation_index", graph, u, v) 101 | 102 | 103 | def salton_index(graph, u, v): 104 | """Salton Index, also called the cosine similarity. 105 | 106 | :param graph: the graph 107 | :param u: a vertex 108 | :param v: a vertex 109 | :returns: the score 110 | """ 111 | return _linkprediction_alg("salton_index", graph, u, v) 112 | 113 | 114 | def sorensen_index(graph, u, v): 115 | """Compute the Sørensen index. 116 | 117 | :param graph: the graph 118 | :param u: a vertex 119 | :param v: a vertex 120 | :returns: the score 121 | """ 122 | return _linkprediction_alg("sorensen_index", graph, u, v) 123 | -------------------------------------------------------------------------------- /tests/test_vc.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.vertexcover as vc 5 | 6 | 7 | def build_graph(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=True, 11 | allowing_multiple_edges=False, 12 | weighted=True, 13 | ) 14 | 15 | for i in range(0, 10): 16 | g.add_vertex(i) 17 | for i in range(1, 10): 18 | g.add_edge(0, i) 19 | 20 | vertex_weights = dict() 21 | vertex_weights[0] = 1000.0 22 | for i in range(1, 10): 23 | vertex_weights[i] = 1.0 24 | 25 | return g, vertex_weights 26 | 27 | 28 | def test_greedy(): 29 | g, _ = build_graph() 30 | vc_weight, vc_vertices = vc.greedy(g) 31 | assert vc_weight == 1.0 32 | assert set(vc_vertices) == set([0]) 33 | 34 | 35 | def test_greedy_with_weights(): 36 | g, vertex_weights = build_graph() 37 | vc_weight, vc_vertices = vc.greedy(g, vertex_weights=vertex_weights) 38 | assert vc_weight == 9.0 39 | assert set(vc_vertices) == set([1, 2, 3, 4, 5, 6, 7, 8, 9]) 40 | 41 | 42 | def test_clarkson(): 43 | g, _ = build_graph() 44 | vc_weight, vc_vertices = vc.clarkson(g) 45 | assert vc_weight == 1.0 46 | assert set(vc_vertices) == set([0]) 47 | 48 | 49 | def test_clarkson_with_weights(): 50 | g, vertex_weights = build_graph() 51 | vc_weight, vc_vertices = vc.clarkson(g, vertex_weights=vertex_weights) 52 | assert vc_weight == 9.0 53 | assert set(vc_vertices) == set([1, 2, 3, 4, 5, 6, 7, 8, 9]) 54 | 55 | 56 | def test_edgebased(): 57 | g, _ = build_graph() 58 | vc_weight, vc_vertices = vc.edgebased(g) 59 | assert vc_weight == 2.0 60 | assert set(vc_vertices) == set([0, 1]) 61 | 62 | 63 | def build_property_graph(): 64 | g = create_graph( 65 | directed=False, 66 | allowing_self_loops=True, 67 | allowing_multiple_edges=False, 68 | weighted=True, 69 | any_hashable=True, 70 | ) 71 | 72 | for i in range(0, 10): 73 | g.add_vertex(str(i)) 74 | for i in range(1, 10): 75 | g.add_edge(str(0), str(i)) 76 | 77 | vertex_weights = dict() 78 | vertex_weights["0"] = 1000.0 79 | for i in range(1, 10): 80 | vertex_weights[str(i)] = 1.0 81 | 82 | return g, vertex_weights 83 | 84 | 85 | def test_anyhashableg_greedy_with_weights(): 86 | g, vertex_weights = build_property_graph() 87 | vc_weight, vc_vertices = vc.greedy(g, vertex_weights=vertex_weights) 88 | assert vc_weight == 9.0 89 | assert set(vc_vertices) == set(["1", "2", "3", "4", "5", "6", "7", "8", "9"]) 90 | 91 | 92 | def test_baryehuda_even(): 93 | g, _ = build_graph() 94 | vc_weight, vc_vertices = vc.baryehuda_even(g) 95 | assert vc_weight == 1.0 96 | assert set(vc_vertices) == set([0]) 97 | 98 | 99 | def test_baryehuda_even_with_weights(): 100 | g, vertex_weights = build_graph() 101 | vc_weight, vc_vertices = vc.baryehuda_even(g, vertex_weights=vertex_weights) 102 | assert vc_weight == 9.0 103 | assert set(vc_vertices) == set([1, 2, 3, 4, 5, 6, 7, 8, 9]) 104 | 105 | 106 | def test_exact(): 107 | g, _ = build_graph() 108 | vc_weight, vc_vertices = vc.exact(g) 109 | assert vc_weight == 1.0 110 | assert set(vc_vertices) == set([0]) 111 | 112 | 113 | def test_exact_with_weights(): 114 | g, vertex_weights = build_graph() 115 | vc_weight, vc_vertices = vc.exact(g, vertex_weights=vertex_weights) 116 | assert vc_weight == 9.0 117 | assert set(vc_vertices) == set([1, 2, 3, 4, 5, 6, 7, 8, 9]) 118 | -------------------------------------------------------------------------------- /tests/test_metrics.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.metrics as metrics 5 | 6 | 7 | def create_test_graph(): 8 | g = create_graph( 9 | directed=False, 10 | allowing_self_loops=False, 11 | allowing_multiple_edges=False, 12 | weighted=True, 13 | ) 14 | 15 | for i in range(0, 10): 16 | g.add_vertex(i) 17 | 18 | g.add_edge(0, 1) 19 | g.add_edge(0, 2) 20 | g.add_edge(0, 3) 21 | g.add_edge(0, 4) 22 | g.add_edge(0, 5) 23 | g.add_edge(0, 6) 24 | g.add_edge(0, 7) 25 | g.add_edge(0, 8) 26 | g.add_edge(0, 9) 27 | 28 | g.add_edge(1, 2) 29 | g.add_edge(2, 3) 30 | g.add_edge(3, 4) 31 | g.add_edge(4, 5) 32 | g.add_edge(5, 6) 33 | g.add_edge(6, 7) 34 | g.add_edge(7, 8) 35 | g.add_edge(8, 9) 36 | g.add_edge(9, 1) 37 | 38 | return g 39 | 40 | 41 | def create_test_property_graph(): 42 | g = create_graph( 43 | directed=False, 44 | allowing_self_loops=False, 45 | allowing_multiple_edges=False, 46 | weighted=True, 47 | any_hashable=True, 48 | ) 49 | 50 | g.add_vertex("0") 51 | for i in range(1, 5): 52 | g.add_vertex(i) 53 | g.add_vertex("5") 54 | for i in range(6, 10): 55 | g.add_vertex(i) 56 | 57 | g.add_edge("0", 1, edge="e01") 58 | g.add_edge("0", 2, edge="e02") 59 | g.add_edge("0", 3, edge="e03") 60 | g.add_edge("0", 4, edge="e04") 61 | g.add_edge("0", "5", edge="e05") 62 | g.add_edge("0", 6, edge="e06") 63 | g.add_edge("0", 7, edge="e07") 64 | g.add_edge("0", 8, edge="e08") 65 | g.add_edge("0", 9, edge="e09") 66 | 67 | g.add_edge(1, 2, edge="e12") 68 | g.add_edge(2, 3, edge="e23") 69 | g.add_edge(3, 4, edge="e34") 70 | g.add_edge(4, "5", edge="e45") 71 | g.add_edge("5", 6, edge="e56") 72 | g.add_edge(6, 7, edge="e67") 73 | g.add_edge(7, 8, edge="e67") 74 | g.add_edge(8, 9, edge="e89") 75 | g.add_edge(9, 1, edge="e91") 76 | 77 | return g 78 | 79 | 80 | def test_diameter(): 81 | g = create_test_graph() 82 | assert metrics.diameter(g) == 2.0 83 | 84 | 85 | def test_radius(): 86 | g = create_test_graph() 87 | assert metrics.radius(g) == 1.0 88 | 89 | 90 | def test_girth(): 91 | g = create_test_graph() 92 | assert metrics.girth(g) == 3.0 93 | 94 | 95 | def test_count_triangles(): 96 | g = create_test_graph() 97 | assert metrics.count_triangles(g) == 9 98 | 99 | 100 | def test_measure(): 101 | g = create_test_graph() 102 | 103 | d, r, center, periphery, pseudo_periphery, eccentricity_map = metrics.measure(g) 104 | 105 | assert d == 2.0 106 | assert r == 1.0 107 | assert center == set([0]) 108 | assert periphery == set([1, 2, 3, 4, 5, 6, 7, 8, 9]) 109 | assert pseudo_periphery == set([1, 2, 3, 4, 5, 6, 7, 8, 9]) 110 | 111 | assert eccentricity_map[0] == 1.0 112 | for i in range(1, 10): 113 | assert eccentricity_map[i] == 2.0 114 | 115 | 116 | def test_property_graph_measure(): 117 | g = create_test_property_graph() 118 | 119 | d, r, center, periphery, pseudo_periphery, eccentricity_map = metrics.measure(g) 120 | 121 | assert d == 2.0 122 | assert r == 1.0 123 | assert center == set(["0"]) 124 | assert periphery == set([1, 2, 3, 4, "5", 6, 7, 8, 9]) 125 | assert pseudo_periphery == set([1, 2, 3, 4, "5", 6, 7, 8, 9]) 126 | 127 | assert eccentricity_map["0"] == 1.0 128 | for i in range(1, 5): 129 | assert eccentricity_map[i] == 2.0 130 | assert eccentricity_map["5"] == 2.0 131 | for i in range(6, 10): 132 | assert eccentricity_map[i] == 2.0 133 | -------------------------------------------------------------------------------- /jgrapht/algorithms/cuts.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import _wrap_cut, _wrap_gomory_hu_tree, _build_vertex_set 3 | from .flow import push_relabel 4 | 5 | 6 | def mincut_stoer_wagner(graph): 7 | r"""Compute a min-cut using the Stoer-Wagner algorithm. 8 | 9 | This implementation requires :math:`\mathcal{O}(m n \log m)` time where :math:`n` is the 10 | number of vertices and :math:`m` the number of edges of the graph. 11 | 12 | :param graph: the input graph. Must be undirected with non-negative edge weights 13 | :returns: a min cut as an instance of :py:class:`.Cut`. 14 | """ 15 | ( 16 | cut_weight, 17 | cut_source_partition_handle, 18 | ) = _backend.jgrapht_xx_cut_mincut_exec_stoer_wagner(graph.handle) 19 | return _wrap_cut(graph, cut_source_partition_handle, cut_weight) 20 | 21 | 22 | def min_st_cut(graph, source, sink): 23 | r"""Compute a minimum s-t cut using the Push-relabel algorithm. 24 | 25 | This is a :math:`\mathcal{O}(n^3)` algorithm where :math:`n` is the number of vertices 26 | of the graph. For more details on the algorithm see: 27 | 28 | * Andrew V. Goldberg and Robert Tarjan. A new approach to the maximum flow problem. 29 | Proceedings of STOC '86. 30 | 31 | The algorithm uses the graph edge weights as the network edge capacities. 32 | 33 | :param graph: The input graph. This can be either directed or undirected. Edge capacities 34 | are taken from the edge weights. 35 | :param source: The source vertex 36 | :param sink: The sink vertex. 37 | :returns: A min s-t cut. 38 | """ 39 | _, cut = push_relabel(graph, source, sink) 40 | return cut 41 | 42 | 43 | def gomory_hu_gusfield(graph): 44 | r"""Computes a Gomory-Hu Tree using Gusfield's algorithm. 45 | 46 | Gomory-Hu Trees can be used to calculate the maximum s-t flow value and the minimum 47 | s-t cut between all pairs of vertices. It does so by performing :math:`n-1` max flow 48 | computations. 49 | 50 | For more details see: 51 | 52 | * Gusfield, D, Very simple methods for all pairs network flow analysis. SIAM Journal 53 | on Computing, 19(1), p142-155, 1990 54 | 55 | This implementation uses the push-relabel algorithm for the minimum s-t cut which 56 | is :math:`\mathcal{O}(n^3)`. The total complexity is, therefore, :math:`\mathcal{O}(n^4)`. 57 | 58 | :param graph: an undirected network 59 | :returns: a Gomory-Hu tree as an instance of :py:class:`jgrapht.types.GomoryHuTree` 60 | """ 61 | handle = _backend.jgrapht_xx_cut_gomoryhu_exec_gusfield(graph.handle) 62 | return _wrap_gomory_hu_tree(graph, handle) 63 | 64 | 65 | def oddmincutset_padberg_rao(graph, odd_vertices, use_tree_compression=False): 66 | r"""Compute Odd Minimum Cut-Sets using the Pardberg and Rao algorithm. 67 | 68 | See the following papers: 69 | 70 | * Padberg, M. Rao, M. Odd Minimum Cut-Sets and b-Matchings. Mathematics of Operations 71 | Research, 7(1), p67-80, 1982. 72 | * Letchford, A. Reinelt, G. Theis, D. Odd minimum cut-sets and b-matchings revisited. 73 | SIAM Journal of Discrete Mathematics, 22(4), p1480-1487, 2008. 74 | 75 | Running time :math:`\mathcal{O}(n^4)`. 76 | 77 | :param graph: an undirected network. Must be simple and all edge weights must be positive. 78 | :param odd_vertices: set of vertices labelled "odd". It must have even cardinality. 79 | :param use_tree_compression: whether to use the tree compression technique 80 | :returns: a cut as an instance of :py:class:`.Cut`. 81 | """ 82 | odd_set = _build_vertex_set(graph, odd_vertices) 83 | ( 84 | cut_weight, 85 | cut_source_partition_handle, 86 | ) = _backend.jgrapht_xx_cut_oddmincutset_exec_padberg_rao( 87 | graph.handle, odd_set.handle, use_tree_compression 88 | ) 89 | return _wrap_cut(graph, cut_source_partition_handle, cut_weight) 90 | -------------------------------------------------------------------------------- /jgrapht/metrics.py: -------------------------------------------------------------------------------- 1 | from . import backend as _backend 2 | 3 | from ._internals._results import _wrap_vertex_set, _wrap_vertex_double_map 4 | 5 | 6 | def diameter(graph): 7 | r"""Compute the `diameter `_ of a graph. The 8 | diameter of a graph is defined as :math:`\max_{v\in V}\epsilon(v)`, where :math:`\epsilon(v)` 9 | is the eccentricity of vertex :math:`v`. In other words, this method computes the 'longest 10 | shortest path'. Two special cases exist: (a) if the graph has no vertices, the diameter is 0, 11 | and (b) if the graph is disconnected, the diameter is positive infinity. 12 | 13 | :param graph: the input graph 14 | :returns: the graph diameter 15 | """ 16 | return _backend.jgrapht_xx_graph_metrics_diameter(graph.handle) 17 | 18 | 19 | def radius(graph): 20 | r"""Compute the `radius `_ of a graph. 21 | 22 | The radius of a graph is the minimum vertex eccentricity. 23 | 24 | .. note:: 25 | 26 | If the graph has no vertices, the radius is zero. In case the graph is disconnected, the 27 | radius is positive infinity. 28 | 29 | :param graph: the input graph 30 | :returns: the graph diameter 31 | """ 32 | return _backend.jgrapht_xx_graph_metrics_radius(graph.handle) 33 | 34 | 35 | def girth(graph): 36 | r"""Compute the `girth `_ of a graph. 37 | 38 | The girth is the length of the shortest graph cycle (if any) in a graph. Acyclic graphs 39 | are considered to have infinite girth. For directed graphs, the length of the shortest 40 | directed cycle is returned. 41 | 42 | This method invokes a breadth-first search from every vertex in the graph. Thus, its 43 | runtime complexity is :math:`\mathcal{O}(n(m+n)) = \mathcal{O}(m n)`. 44 | 45 | :param graph: the input graph 46 | :returns: the graph girth 47 | """ 48 | return _backend.jgrapht_xx_graph_metrics_girth(graph.handle) 49 | 50 | 51 | def count_triangles(graph): 52 | r"""Count the number of triangles in a graph. 53 | 54 | This is an :math:`\mathcal{O}(m^{3/2})` algorithm for counting the number of 55 | triangles in an undirected graph. 56 | 57 | :param graph: the input graph. Must be undirected 58 | :returns: the number of triangles in the graph 59 | :raises ValueError: if the graph is not undirected 60 | """ 61 | return _backend.jgrapht_xx_graph_metrics_triangles(graph.handle) 62 | 63 | 64 | def measure(graph): 65 | """Measure the graph. This method executes an all-pairs shortest paths 66 | using Floyd-Warshal. 67 | 68 | This method computes: 69 | 70 | * the graph diameter 71 | * the graph radius 72 | * the set of vertices which form the center of the graph 73 | * the set of vertices which form the periphery of the graph 74 | * the set of vertices which form the pseudo-periphery of the graph 75 | * the vertex eccentricity map 76 | 77 | :param graph: the input graph 78 | :returns: a 6-tuple containing the results. 79 | """ 80 | ( 81 | diameter, 82 | radius, 83 | center_handle, 84 | periphery_handle, 85 | pseudo_periphery_handle, 86 | vertex_eccentricity_map_handle, 87 | ) = _backend.jgrapht_xx_graph_metrics_measure_graph(graph.handle) 88 | 89 | centers = _wrap_vertex_set(graph, center_handle) 90 | periphery = _wrap_vertex_set(graph, periphery_handle) 91 | pseudo_periphery = _wrap_vertex_set(graph, pseudo_periphery_handle) 92 | vertex_eccentricity_map = _wrap_vertex_double_map(graph, vertex_eccentricity_map_handle) 93 | 94 | return ( 95 | diameter, 96 | radius, 97 | centers, 98 | periphery, 99 | pseudo_periphery, 100 | vertex_eccentricity_map, 101 | ) 102 | -------------------------------------------------------------------------------- /jgrapht/algorithms/vertexcover.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import _wrap_vertex_set, _build_vertex_weights 3 | 4 | 5 | def _vertexcover_alg(name, graph, vertex_weights=None): 6 | alg_method_name = "jgrapht_xx_vertexcover_exec_" + name 7 | if vertex_weights is not None: 8 | alg_method_name += "_weighted" 9 | alg_method = getattr(_backend, alg_method_name) 10 | 11 | if vertex_weights is not None: 12 | jgrapht_vertex_weights = _build_vertex_weights(graph, vertex_weights) 13 | weight, vc_handle = alg_method(graph.handle, jgrapht_vertex_weights.handle) 14 | else: 15 | weight, vc_handle = alg_method(graph.handle) 16 | 17 | return weight, _wrap_vertex_set(graph, vc_handle) 18 | 19 | 20 | def greedy(graph, vertex_weights=None): 21 | r"""A greedy algorithm for the vertex cover problem. 22 | 23 | At each iteration the algorithm picks the vertex :math:`v` with 24 | the minimum ration of weight over degree. Afterwards it removes all 25 | its incident edges and recurses. 26 | 27 | Its running time is :math:`\mathcal{O}(m \log n)`. The implementation 28 | supports both the uniform and the weighted case where the graph vertices 29 | have weights. 30 | 31 | :param graph: the input graph. It must be undirected. Self-loops and multiple edges 32 | are allowed 33 | :param vertex_weights: an optional dictionary of vertex weights 34 | :returns: a tuple (weight, vertex set) 35 | """ 36 | return _vertexcover_alg("greedy", graph, vertex_weights) 37 | 38 | 39 | def clarkson(graph, vertex_weights=None): 40 | r"""Compute a vertex cover using the 2-opt algorithm of Clarkson. 41 | 42 | The algorithm runs in time :math:`\mathcal{O}(m \log n)` and is a 2-approximation 43 | which means that the solution is guaranteed to be at most twice the optimum. 44 | 45 | For more information see the following paper: 46 | 47 | Clarkson, Kenneth L. A modification of the greedy algorithm for vertex cover. 48 | Information Processing Letters 16.1 (1983): 23-25. 49 | 50 | :param graph: the input graph. It must be undirected. Self-loops and multiple edges 51 | are allowed 52 | :param vertex_weights: an optional dictionary of vertex weights 53 | :returns: a tuple (weight, vertex set) 54 | """ 55 | return _vertexcover_alg("clarkson", graph, vertex_weights) 56 | 57 | 58 | def edgebased(graph): 59 | r"""A 2-opt algorithm for the minimum vertex cover. 60 | 61 | This is a 2-approximation algorithm with running time :math:`\mathcal{O}(m)`. 62 | 63 | :param graph: the input graph. It must be undirected. Self-loops and multiple edges 64 | are allowed 65 | :returns: a tuple (weight, vertex set) 66 | """ 67 | return _vertexcover_alg("edgebased", graph) 68 | 69 | 70 | def baryehuda_even(graph, vertex_weights=None): 71 | r"""The 2-opt algorithm for a minimum weighted vertex cover by R. Bar-Yehuda and S. Even. 72 | 73 | This is a 2-approximation algorithm with running time :math:`\mathcal{O}(m)`. 74 | 75 | :param graph: the input graph. It must be undirected. Self-loops and multiple edges 76 | are allowed 77 | :param vertex_weights: an optional dictionary of vertex weights 78 | :returns: a tuple (weight, vertex set) 79 | """ 80 | return _vertexcover_alg("baryehudaeven", graph, vertex_weights) 81 | 82 | 83 | def exact(graph, vertex_weights=None): 84 | r"""Compute a vertex cover exactly using a recursive algorithm. 85 | 86 | At each recursive step the algorithm picks a vertex and either includes in the 87 | cover or it includes all of its neighbors. To speed up the algorithm, memoization 88 | and a bounding procedure is also used. 89 | 90 | Can solve instances with around 150-200 vertices to optimality. 91 | 92 | :param graph: the input graph. It must be undirected 93 | :param vertex_weights: an optional dictionary of vertex weights 94 | :returns: a tuple (weight, vertex set) 95 | """ 96 | return _vertexcover_alg("exact", graph, vertex_weights) 97 | -------------------------------------------------------------------------------- /examples/io/plot_import_graphml.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | GraphML Import 4 | ============== 5 | 6 | This is an example on how to import a graph from a string in GraphML format. 7 | """ 8 | 9 | # %% 10 | # Start by importing the package. 11 | 12 | import jgrapht 13 | from jgrapht.io.importers import parse_graphml 14 | 15 | # %% 16 | # Let us create a directed graph 17 | 18 | g = jgrapht.create_graph(directed=True) 19 | 20 | # %% 21 | # Let us assume that we are reading the following graph 22 | 23 | graphml_input = r""" 27 | 28 | 1.0 29 | 30 | 31 | 32 | 33 | 34 | node 0 35 | 36 | 37 | node 1 38 | 39 | 40 | 41 | 42 | 43 | 100.0 44 | 20.3 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 33.3 53 | 48.5 54 | 55 | 56 | """ 57 | 58 | # %% 59 | # We would also like to capture the vertex and edge attributes. The 60 | # importer will inform us everytime that it reads a vertex or edge 61 | # attribute. The edge attribute "weight" is considered special and 62 | # is being read automatically if the provided graph object is weighted. 63 | # Thus, we define the following callback functions. 64 | 65 | v_attrs = {} 66 | e_attrs = {} 67 | 68 | def vertex_attribute_cb(vertex, attribute_name, attribute_value): 69 | if vertex not in v_attrs: 70 | v_attrs[vertex] = {} 71 | v_attrs[vertex][attribute_name] = attribute_value 72 | 73 | 74 | def edge_attribute_cb(edge, attribute_name, attribute_value): 75 | if edge not in e_attrs: 76 | e_attrs[edge] = {} 77 | e_attrs[edge][attribute_name] = attribute_value 78 | 79 | 80 | # %% 81 | # The importer needs a way to translate the vertex identifiers from 82 | # the input to integer values. By default, if we do not provide a function, 83 | # it will auto assign new integer values. If you want to control the 84 | # actual vertices created, you can also provide a `import_id_cb` function 85 | # like the following. Here we simply convert from a string to integer, since 86 | # the actual input contains integer vertices. 87 | 88 | def import_id_cb(vertex): 89 | return int(vertex) 90 | 91 | 92 | # %% 93 | # Now we call the importer. 94 | 95 | parse_graphml( 96 | g, 97 | graphml_input, 98 | import_id_cb=import_id_cb, 99 | vertex_attribute_cb=vertex_attribute_cb, 100 | edge_attribute_cb=edge_attribute_cb, 101 | ) 102 | 103 | # %% 104 | # We print the graph for debugging purposes. 105 | 106 | print(g) 107 | 108 | # %% 109 | # The vertex attributes dictionary should contain all vertex attributes. 110 | # The attribute with key `ID` contains the original identifier from the file. 111 | # In our case it will be the same with the `id`. 112 | print(v_attrs) 113 | 114 | # %% 115 | # The edge attributes dictionary should contain all edge attributes. 116 | print(e_attrs) 117 | 118 | -------------------------------------------------------------------------------- /jgrapht/algorithms/spanning.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import _wrap_edge_set 3 | 4 | 5 | 6 | def kruskal(graph): 7 | r"""Compute the minimum spanning tree using `Kruskal's algorithm `_. 8 | 9 | 10 | If the given graph is connected it computes the minimum spanning tree, otherwise it computes 11 | the minimum spanning forest. The algorithm runs in time :math:`\mathcal{O}(m \log m)` or 12 | :math:`\mathcal{O}(m \log n)` in case multiple edges are not allowed and thus :math:`m \le n^2`. 13 | Here :math:`n` is the number of vertices and :math:`m` the number of edges of the graph. 14 | 15 | :param graph: The input graph 16 | :returns: A tuple (weight, mst) 17 | """ 18 | weight, mst_handle = _backend.jgrapht_xx_mst_exec_kruskal(graph.handle) 19 | return weight, _wrap_edge_set(graph, mst_handle) 20 | 21 | 22 | def prim(graph): 23 | r"""Compute the minimum spanning tree using `Prim's algorithm `_. 24 | 25 | The algorithm was developed by Czech mathematician V. Jarník and later independently by computer scientist 26 | Robert C. Prim and rediscovered by E. Dijkstra. This implementation uses a Fibonacci Heap in order to 27 | achieve a running time of :math:`\mathcal{O}(m+n\log n)` where :math:`n` is the number of vertices and 28 | :math:`m` the number of edges of the graph. 29 | 30 | :param graph: The input graph 31 | :returns: A tuple (weight, mst) 32 | """ 33 | weight, mst_handle = _backend.jgrapht_xx_mst_exec_prim(graph.handle) 34 | return weight, _wrap_edge_set(graph, mst_handle) 35 | 36 | 37 | def boruvka(graph): 38 | r"""Compute the minimum spanning tree using `Borůvka's algorithm `_. 39 | 40 | This implementation uses a union-find data structure (with union by rank and path compression 41 | heuristic) in order to track components. In graphs where edges have identical weights, edges with 42 | equal weights are ordered lexicographically. The running time is :math:`\mathcal{O}((m+n) \log n)` under the 43 | assumption that the union-find uses path-compression. 44 | Here :math:`n` is the number of vertices and :math:`m` the number of edges of the graph. 45 | 46 | :param graph: The input graph 47 | :returns: A tuple (weight, mst) 48 | """ 49 | weight, mst_handle = _backend.jgrapht_xx_mst_exec_boruvka(graph.handle) 50 | return weight, _wrap_edge_set(graph, mst_handle) 51 | 52 | 53 | def multiplicative_greedy(graph, k): 54 | r"""Greedy algorithm for :math:`(2k-1)`-multiplicative spanner construction (for any integer :math:`k \ge 1`. 55 | 56 | The spanner is guaranteed to contain :math:`\mathcal{O}(n^{1+1/k})` edges and the shortest path 57 | distance between any two vertices in the spanner is at most :math:`2k-1` times the corresponding 58 | shortest path distance in the original graph. Here :math:`n` denotes the number of vertices of 59 | the graph. 60 | 61 | The algorithm is described in: Althoefer, Das, Dobkin, Joseph, Soares. 62 | `On Sparse Spanners of Weighted Graphs `_. Discrete 63 | Computational Geometry 9(1):81-100, 1993. 64 | 65 | If the graph is unweighted the algorithm runs in :math:`\mathcal{O}(m n^{1+1/k})` time. Setting 66 | :math:`k` to infinity will result in a slow version of Kruskal's algorithm where cycle detection 67 | is performed by a BFS computation. In such a case use the implementation of Kruskal with 68 | union-find. Here :math:`n` and :math:`m` are the number of vertices and edges of the graph 69 | respectively. 70 | 71 | If the graph is weighted the algorithm runs in :math:`\mathcal{O}(m (n^{1+1/k} + n \log n))` time 72 | by using Dijkstra's algorithm. Edge weights must be non-negative. 73 | 74 | :param graph: The input graph 75 | :param k: integer 76 | :returns: tuple of the form (weight, spanner_edges) 77 | """ 78 | weight, spanner = _backend.jgrapht_xx_spanner_exec_greedy_multiplicative( 79 | graph.handle, k 80 | ) 81 | return weight, _wrap_edge_set(graph, spanner) 82 | -------------------------------------------------------------------------------- /jgrapht/algorithms/cliques.py: -------------------------------------------------------------------------------- 1 | from .. import backend as _backend 2 | from .._internals._results import ( 3 | _wrap_vertex_set, 4 | _wrap_vertex_set_iterator, 5 | ) 6 | 7 | 8 | def bron_kerbosch(graph, timeout=0): 9 | """Bron-Kerbosch maximal clique enumeration algorithm. 10 | 11 | Implementation of the Bron-Kerbosch clique enumeration algorithm as described in: 12 | 13 | * R. Samudrala and J. Moult. A graph-theoretic algorithm for comparative modeling of protein 14 | structure. Journal of Molecular Biology, 279(1):287--302, 1998. 15 | 16 | The algorithm first computes all maximal cliques and then returns the result to the user. A 17 | timeout (in seconds) can be set using the parameters. 18 | 19 | :param graph: The input graph which should be simple 20 | :param timeout: Timeout in seconds. No timeout if zero 21 | :returns: An iterator over maximal cliques 22 | """ 23 | custom = [timeout] 24 | res = _backend.jgrapht_xx_clique_exec_bron_kerbosch(graph.handle, *custom) 25 | return _wrap_vertex_set_iterator(graph, res) 26 | 27 | 28 | def bron_kerbosch_with_pivot(graph, timeout=0): 29 | r"""Bron-Kerbosch maximal clique enumeration algorithm with pivot. 30 | 31 | The pivoting follows the rule from the paper: 32 | 33 | * E. Tomita, A. Tanaka, and H. Takahashi. The worst-case time complexity for generating all 34 | maximal cliques and computational experiments. Theor. Comput. Sci. 363(1):28–42, 2006. 35 | 36 | where the authors show that using that rule guarantees that the Bron-Kerbosch algorithm has 37 | worst-case running time :math:`\mathcal{O}(3^{n/3})` where :math:`n` is the number of vertices 38 | of the graph, excluding time to write the output, which is worst-case optimal. 39 | 40 | The algorithm first computes all maximal cliques and then returns the result to the user. A 41 | timeout (in seconds) can be set using the parameters. 42 | 43 | :param graph: The input graph which should be simple 44 | :param timeout: Timeout in seconds. No timeout if zero 45 | :returns: An iterator over maximal cliques 46 | """ 47 | custom = [timeout] 48 | res = _backend.jgrapht_xx_clique_exec_bron_kerbosch_pivot(graph.handle, *custom) 49 | return _wrap_vertex_set_iterator(graph, res) 50 | 51 | 52 | def bron_kerbosch_with_degeneracy_ordering(graph, timeout=0): 53 | r"""Bron-Kerbosch maximal clique enumeration algorithm with pivot and degeneracy ordering. 54 | 55 | The algorithm is a variant of the Bron-Kerbosch algorithm which apart from the pivoting 56 | uses a degeneracy ordering of the vertices. The algorithm is described in 57 | 58 | * David Eppstein, Maarten Löffler and Darren Strash. Listing All Maximal Cliques in Sparse 59 | Graphs in Near-Optimal Time. Algorithms and Computation: 21st International Symposium 60 | (ISSAC), 403--414, 2010. 61 | 62 | and has running time :math:`\mathcal{O}(d n 3^{d/3})` where :math:`n` is the number of 63 | vertices of the graph and :math:`d` is the degeneracy of the graph. The algorithm looks for 64 | a maximal clique parameterized by degeneracy, a frequently-used measure of the sparseness 65 | of a graph that is closely related to other common sparsity measures such as arboricity and 66 | thickness, and that has previously been used for other fixed-parameter problems. 67 | 68 | The algorithm first computes all maximal cliques and then returns the result to the user. A 69 | timeout (in seconds) can be set using the parameters. 70 | 71 | :param graph: The input graph which should be simple 72 | :param timeout: Timeout in seconds. No timeout if zero 73 | :returns: An iterator over maximal cliques 74 | """ 75 | custom = [timeout] 76 | res = _backend.jgrapht_xx_clique_exec_bron_kerbosch_pivot_degeneracy_ordering( 77 | graph.handle, *custom 78 | ) 79 | return _wrap_vertex_set_iterator(graph, res) 80 | 81 | 82 | def chordal_max_clique(graph): 83 | r"""Find a maximum clique in a chordal graph. 84 | 85 | The algorithms first computes a perfect elimination ordering and then a 86 | maximum clique. Running time :math:`\mathcal{O}(n+m)`. 87 | 88 | :param graph: the chordal graph. If the graph is not chordal an error is raised 89 | :returns: a clique as a vertex set 90 | """ 91 | res = _backend.jgrapht_xx_clique_exec_chordal_max_clique(graph.handle) 92 | return _wrap_vertex_set(graph, res) 93 | -------------------------------------------------------------------------------- /tests/test_maxflow.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.flow as flow 5 | 6 | 7 | 8 | def _do_run_both(algo): 9 | g = create_graph( 10 | directed=True, 11 | allowing_self_loops=False, 12 | allowing_multiple_edges=False, 13 | weighted=True, 14 | ) 15 | 16 | g.add_vertex(0) 17 | g.add_vertex(1) 18 | g.add_vertex(2) 19 | g.add_vertex(3) 20 | 21 | e01 = g.add_edge(0, 1, weight=20) 22 | e02 = g.add_edge(0, 2, weight=10) 23 | e12 = g.add_edge(1, 2, weight=30) 24 | e13 = g.add_edge(1, 3, weight=10) 25 | e23 = g.add_edge(2, 3, weight=20) 26 | 27 | f, cut = algo(g, 0, 3) 28 | 29 | assert f.source == 0 30 | assert f.sink == 3 31 | assert f.value == 30.0 32 | assert f[e01] == 20.0 33 | assert f[e02] == 10.0 34 | assert f[e12] == 10.0 35 | assert f[e13] == 10.0 36 | assert f[e23] == 20.0 37 | 38 | assert cut.capacity == 30.0 39 | assert cut.edges == set([e01, e02]) 40 | assert cut.source_partition == set([0]) 41 | assert cut.target_partition == set([1, 2, 3]) 42 | 43 | 44 | def _do_run_anyhashableg_both(algo): 45 | g = create_graph( 46 | directed=True, 47 | allowing_self_loops=False, 48 | allowing_multiple_edges=False, 49 | weighted=True, 50 | any_hashable=True, 51 | ) 52 | 53 | g.add_vertex(0) 54 | g.add_vertex(1) 55 | g.add_vertex(2) 56 | g.add_vertex(3) 57 | 58 | e01 = g.add_edge(0, 1, weight=20) 59 | e02 = g.add_edge(0, 2, weight=10) 60 | e12 = g.add_edge(1, 2, weight=30) 61 | e13 = g.add_edge(1, 3, weight=10) 62 | e23 = g.add_edge(2, 3, weight=20) 63 | 64 | f, cut = algo(g, 0, 3) 65 | 66 | assert f.source == 0 67 | assert f.sink == 3 68 | assert f.value == 30.0 69 | assert f[e01] == 20.0 70 | assert f[e02] == 10.0 71 | assert f[e12] == 10.0 72 | assert f[e13] == 10.0 73 | assert f[e23] == 20.0 74 | 75 | assert cut.capacity == 30.0 76 | assert cut.edges == set([e01, e02]) 77 | assert cut.source_partition == set([0]) 78 | assert cut.target_partition == set([1, 2, 3]) 79 | 80 | 81 | def _do_run_flow(algo): 82 | g = create_graph( 83 | directed=True, 84 | allowing_self_loops=False, 85 | allowing_multiple_edges=False, 86 | weighted=True, 87 | ) 88 | 89 | g.add_vertex(0) 90 | g.add_vertex(1) 91 | g.add_vertex(2) 92 | g.add_vertex(3) 93 | 94 | e01 = g.add_edge(0, 1, weight=20) 95 | e02 = g.add_edge(0, 2, weight=10) 96 | e12 = g.add_edge(1, 2, weight=30) 97 | e13 = g.add_edge(1, 3, weight=10) 98 | e23 = g.add_edge(2, 3, weight=20) 99 | 100 | f = algo(g, 0, 3) 101 | 102 | assert f.source == 0 103 | assert f.sink == 3 104 | assert f.value == 30.0 105 | assert f[e01] == 20.0 106 | assert f[e02] == 10.0 107 | assert f[e12] == 10.0 108 | assert f[e13] == 10.0 109 | assert f[e23] == 20.0 110 | 111 | 112 | def _do_run_anyhashableg_flow(algo): 113 | g = create_graph( 114 | directed=True, 115 | allowing_self_loops=False, 116 | allowing_multiple_edges=False, 117 | weighted=True, 118 | any_hashable=True, 119 | ) 120 | 121 | g.add_vertex(0) 122 | g.add_vertex(1) 123 | g.add_vertex(2) 124 | g.add_vertex(3) 125 | 126 | e01 = g.add_edge(0, 1, weight=20) 127 | e02 = g.add_edge(0, 2, weight=10) 128 | e12 = g.add_edge(1, 2, weight=30) 129 | e13 = g.add_edge(1, 3, weight=10) 130 | e23 = g.add_edge(2, 3, weight=20) 131 | 132 | f = algo(g, 0, 3) 133 | 134 | assert f.source == 0 135 | assert f.sink == 3 136 | assert f.value == 30.0 137 | assert f[e01] == 20.0 138 | assert f[e02] == 10.0 139 | assert f[e12] == 10.0 140 | assert f[e13] == 10.0 141 | assert f[e23] == 20.0 142 | 143 | 144 | def test_dinic(): 145 | _do_run_both(flow.dinic) 146 | _do_run_anyhashableg_both(flow.dinic) 147 | 148 | 149 | def test_push_relabel(): 150 | _do_run_both(flow.push_relabel) 151 | _do_run_anyhashableg_both(flow.push_relabel) 152 | 153 | 154 | def test_edmonds_karp(): 155 | _do_run_both(flow.edmonds_karp) 156 | _do_run_anyhashableg_both(flow.edmonds_karp) 157 | 158 | def test_max_st_flow(): 159 | _do_run_flow(flow.max_st_flow) 160 | _do_run_anyhashableg_flow(flow.max_st_flow) 161 | -------------------------------------------------------------------------------- /tests/test_contraction_hierarchies.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | import jgrapht.algorithms.shortestpaths as sp 5 | import math 6 | 7 | 8 | def get_graph(): 9 | g = create_graph( 10 | directed=True, 11 | allowing_self_loops=False, 12 | allowing_multiple_edges=False, 13 | weighted=True, 14 | ) 15 | 16 | for i in range(0, 8): 17 | g.add_vertex(i) 18 | 19 | g.add_edge(0, 1, weight=3.0) 20 | g.add_edge(1, 3, weight=100.0) 21 | g.add_edge(0, 2, weight=40.0) 22 | g.add_edge(2, 4, weight=20.0) 23 | g.add_edge(3, 5, weight=2.0) 24 | g.add_edge(4, 5, weight=2.0) 25 | g.add_edge(5, 0, weight=13.0) 26 | g.add_edge(0, 5, weight=1000.0) 27 | g.add_edge(5, 6, weight=12.0) 28 | g.add_edge(5, 7, weight=20.0) 29 | g.add_edge(6, 7, weight=3.0) 30 | g.add_edge(4, 7, weight=200.0) 31 | 32 | return g 33 | 34 | 35 | def get_anyhashableg_graph(): 36 | g = create_graph( 37 | directed=True, 38 | allowing_self_loops=False, 39 | allowing_multiple_edges=False, 40 | weighted=True, 41 | any_hashable=True, 42 | ) 43 | 44 | for i in range(0, 8): 45 | g.add_vertex(i) 46 | 47 | g.add_edge(0, 1, weight=3.0, edge=0) 48 | g.add_edge(1, 3, weight=100.0, edge=1) 49 | g.add_edge(0, 2, weight=40.0, edge="2") 50 | g.add_edge(2, 4, weight=20.0, edge=3) 51 | g.add_edge(3, 5, weight=2.0, edge=4) 52 | g.add_edge(4, 5, weight=2.0, edge=5) 53 | g.add_edge(5, 0, weight=13.0, edge=6) 54 | g.add_edge(0, 5, weight=1000.0, edge=7) 55 | g.add_edge(5, 6, weight=12.0, edge=8) 56 | g.add_edge(5, 7, weight=20.0, edge=9) 57 | g.add_edge(6, 7, weight=3.0, edge=10) 58 | g.add_edge(4, 7, weight=200.0, edge=11) 59 | 60 | return g 61 | 62 | 63 | def test_ch_dijkstra(): 64 | g = get_graph() 65 | 66 | ch = sp.precompute_contraction_hierarchies(g, parallelism=1, seed=31) 67 | 68 | p1 = sp.contraction_hierarchies_dijkstra(g, 0, 7, ch=ch) 69 | 70 | assert 77 == p1.weight 71 | assert [2, 3, 5, 8, 10] == p1.edges 72 | assert 0 == p1.start_vertex 73 | assert 7 == p1.end_vertex 74 | 75 | p2 = sp.contraction_hierarchies_dijkstra(g, 1, 6, ch=ch) 76 | 77 | assert 114 == p2.weight 78 | assert [1, 4, 8] == p2.edges 79 | assert 1 == p2.start_vertex 80 | assert 6 == p2.end_vertex 81 | 82 | 83 | def test_ch_dijkstra_anyhashable(): 84 | g = get_anyhashableg_graph() 85 | 86 | ch = sp.precompute_contraction_hierarchies(g, parallelism=1, seed=31) 87 | 88 | p1 = sp.contraction_hierarchies_dijkstra(g, 0, 7, ch=ch) 89 | 90 | assert 77 == p1.weight 91 | assert ["2", 3, 5, 8, 10] == p1.edges 92 | assert 0 == p1.start_vertex 93 | assert 7 == p1.end_vertex 94 | 95 | p2 = sp.contraction_hierarchies_dijkstra(g, 1, 6, ch=ch) 96 | 97 | assert 114 == p2.weight 98 | assert [1, 4, 8] == p2.edges 99 | assert 1 == p2.start_vertex 100 | assert 6 == p2.end_vertex 101 | 102 | 103 | def test_ch_many_to_many(): 104 | g = get_graph() 105 | 106 | ch = sp.precompute_contraction_hierarchies(g, parallelism=1, seed=31) 107 | 108 | mm = sp.contraction_hierarchies_many_to_many(g, {0,1}, {6,7}, ch=ch) 109 | 110 | p1 = mm.get_path(0, 7) 111 | 112 | assert 77 == p1.weight 113 | assert [2, 3, 5, 8, 10] == p1.edges 114 | assert 0 == p1.start_vertex 115 | assert 7 == p1.end_vertex 116 | 117 | p2 = mm.get_path(1, 6) 118 | 119 | assert 114 == p2.weight 120 | assert [1, 4, 8] == p2.edges 121 | assert 1 == p2.start_vertex 122 | assert 6 == p2.end_vertex 123 | 124 | with pytest.raises(ValueError): 125 | mm.get_path(2, 6) 126 | 127 | 128 | def test_anyhashable_ch_many_to_many(): 129 | g = get_anyhashableg_graph() 130 | 131 | ch = sp.precompute_contraction_hierarchies(g, parallelism=1, seed=31) 132 | 133 | mm = sp.contraction_hierarchies_many_to_many(g, {0,1}, {6,7}, ch=ch) 134 | 135 | p1 = mm.get_path(0, 7) 136 | 137 | assert 77 == p1.weight 138 | assert ['2', 3, 5, 8, 10] == p1.edges 139 | assert 0 == p1.start_vertex 140 | assert 7 == p1.end_vertex 141 | 142 | p2 = mm.get_path(1, 6) 143 | 144 | assert 114 == p2.weight 145 | assert [1, 4, 8] == p2.edges 146 | assert 1 == p2.start_vertex 147 | assert 6 == p2.end_vertex 148 | 149 | with pytest.raises(ValueError): 150 | mm.get_path(2, 6) 151 | -------------------------------------------------------------------------------- /tests/test_edgelist_gexf.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from jgrapht import create_graph 4 | from jgrapht.io.edgelist import read_edgelist_gexf, parse_edgelist_gexf 5 | 6 | 7 | input1 = r""" 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | """ 26 | 27 | expected = r""" 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | """ 44 | 45 | 46 | def test_input_gexf(tmpdir): 47 | tmpfile = tmpdir.join("gexf.out") 48 | tmpfilename = str(tmpfile) 49 | 50 | # write file json with escaped characters 51 | with open(tmpfilename, "w", encoding='utf-8') as f: 52 | f.write(input1) 53 | 54 | v_attrs = dict() 55 | e_attrs = dict() 56 | 57 | # test that you read back unescaped 58 | def va_cb(vertex, attribute_name, attribute_value): 59 | if vertex not in v_attrs: 60 | v_attrs[vertex] = {} 61 | v_attrs[vertex][attribute_name] = attribute_value 62 | 63 | def ea_cb(edge, attribute_name, attribute_value): 64 | if edge not in e_attrs: 65 | e_attrs[edge] = {} 66 | e_attrs[edge][attribute_name] = attribute_value 67 | 68 | edgelist = read_edgelist_gexf( 69 | tmpfilename, 70 | vertex_attribute_cb=va_cb, 71 | edge_attribute_cb=ea_cb, 72 | ) 73 | 74 | assert v_attrs['1']["label"] == "κόμβος 1" 75 | assert v_attrs['2']["label"] == "mylabel 2" 76 | 77 | assert e_attrs == { 78 | 0: {"id": "1", "source": "2", "target": "3"}, 79 | 1: {"id": "0", "source": "1", "target": "2"}, 80 | 2: {"id": "2", "source": "3", "target": "1"}, 81 | } 82 | 83 | assert list(edgelist) == [('2', '3', 1), ('1', '2', 1), ('3', '1', 1)] 84 | 85 | 86 | def test_input_gexf_no_attrs(tmpdir): 87 | tmpfile = tmpdir.join("gexf.out") 88 | tmpfilename = str(tmpfile) 89 | 90 | # write file json with escaped characters 91 | with open(tmpfilename, "w", encoding='utf-8') as f: 92 | f.write(input1) 93 | 94 | edgelist = read_edgelist_gexf(tmpfilename) 95 | 96 | assert list(edgelist) == [('2', '3', 1), ('1', '2', 1), ('3', '1', 1)] 97 | 98 | 99 | def test_input_gexf_from_string_no_attrs(tmpdir): 100 | edgelist = parse_edgelist_gexf(input1) 101 | 102 | assert list(edgelist) == [('2', '3', 1), ('1', '2', 1), ('3', '1', 1)] 103 | 104 | 105 | def test_input_gexf_from_string(tmpdir): 106 | v_attrs = dict() 107 | e_attrs = dict() 108 | 109 | # test that you read back unescaped 110 | def va_cb(vertex, attribute_name, attribute_value): 111 | if vertex not in v_attrs: 112 | v_attrs[vertex] = {} 113 | v_attrs[vertex][attribute_name] = attribute_value 114 | 115 | def ea_cb(edge, attribute_name, attribute_value): 116 | if edge not in e_attrs: 117 | e_attrs[edge] = {} 118 | e_attrs[edge][attribute_name] = attribute_value 119 | 120 | edgelist = parse_edgelist_gexf( 121 | input1, 122 | vertex_attribute_cb=va_cb, 123 | edge_attribute_cb=ea_cb, 124 | ) 125 | 126 | assert v_attrs['1']["label"] == "κόμβος 1" 127 | assert v_attrs['2']["label"] == "mylabel 2" 128 | 129 | assert e_attrs == { 130 | 0: {"id": "1", "source": "2", "target": "3"}, 131 | 1: {"id": "0", "source": "1", "target": "2"}, 132 | 2: {"id": "2", "source": "3", "target": "1"}, 133 | } 134 | 135 | assert list(edgelist) == [('2', '3', 1), ('1', '2', 1), ('3', '1', 1)] 136 | -------------------------------------------------------------------------------- /docs/api/introduction.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _introduction: 3 | 4 | Introduction 5 | ************ 6 | 7 | .. currentmodule:: jgrapht 8 | 9 | The |Project| library is a highly efficient graph library containing state-of-the-art graph 10 | data-structures as well as a multitude of sophisticated graph algorithms. It has been in development 11 | for many years already and is currently used in production both in research and industry. 12 | 13 | The |Bindings| is a pure python/native package having no dependency on the JVM. During the build 14 | process the backend |Project| library is compiled as a shared library and bundled inside the python 15 | package. It provides a *graph* data-structure capable of representing various different 16 | kind of graphs/networks such as: 17 | 18 | * **directed**: in directed graphs an edge :math:`(u,v)` is an ordered pair of vertices, 19 | * **undirected**: in undirected graphs an edge :math:`\{u,v\}` is an unordered pair of vertices, 20 | * with **self-loops**: self-loops are edges of the form :math:`(v,v)` where both endpoints are the same, 21 | * graphs with **multiple-edges**: multiple-edges are edges :math:`e = (u,v)` and :math:`e' = (u,v)` 22 | which have the exact same endpoints, 23 | * **weighted** graphs: graphs where each edge is associated with a double value called its weight, 24 | * **unweighted** graphs: graphs where the weight of each edge is 1.0 (uniform weight function) 25 | 26 | A graph :math:`G(V,E)` contains vertices and edges. Each edge :math:`e = (u,v)` is associated with 27 | 28 | * its two endpoints :math:`u` and :math:`v`, and 29 | * a double value called its weight. 30 | 31 | Additionally, both vertices and edges are usually associated with information called attributes 32 | or properties. All access to information about the graph happens using the graph object. 33 | Thus, given an edge, finding its source, target and weight can be performed using graph methods. Similarly, 34 | information about the vertices, such as its incident edges, can also be performed using corresponding 35 | graph methods. 36 | 37 | The |Bindings| contains two main graph implementations which we describe next. Creating graphs of 38 | either category can be done using the :py:meth:`jgrapht.create_graph` factory method. 39 | 40 | integer graphs 41 | """""""""""""" 42 | 43 | The *integer graph* is oriented torwards simplicity and performance. Its main characteristic is that 44 | vertices and edges are always integers. When manipulating graphs, beside its structure (topology), 45 | users usually associate additional information with the vertices and edges. Such vertex and edge 46 | attributes/properties, are fully supported when exporting and importing graphs, by using callback 47 | functions. Storing such attributes/properties, however, is not performed inside the graph. The 48 | user is responsible to maintain external dictionaries with the vertex and edge identifier as the key. 49 | While this might seem like a restriction at first, it allows for several optimizations at the graph 50 | level. Combined with the simplicity and power of dictionaries in Python, it should pose no real 51 | restriction to the user, except possibly some aesthetic effect. 52 | 53 | A special version of the integer graph which is also supported is the *sparse* graph. The sparse graph 54 | is a static variant which can only be bulk-loaded from a list of edges. Its benefits is that it is 55 | condiderably faster and less memory hungry, at the expense of not being modifiable. It is best suited 56 | for less dynamic workloads where the user creates a graph once and executes complex algorithms on it. 57 | 58 | Another special version of the integer graph is called the *succinct* graph. The succinct graph 59 | is a static variant which is very compact, close to the theoretical lower-bound. This means that it is 60 | going to be even smaller in footprint than the sparse graph, at the expense of beeing a bit slower. 61 | It also is not modifiable. It is best suited for very large graphs and static workloads. 62 | 63 | any-hashable graphs 64 | """"""""""""""""""" 65 | 66 | The *any-hashable graph* is a graph implementation which allows the use of any Python hashable as vertices 67 | or edges. Additionally, it provides support for maintaining the attributes/properties dictionaries 68 | inside the graph. During creation the user can provide a vertex and edge factory function (called vertex 69 | supplier and edge supplier) which the graph can use whenever it needs to create new vertices or edges. 70 | If not explicitly provided, the implementation uses object instances for all automatically created vertices 71 | and edges. Importers and exporters automatically support graphs, by importing/exporting their associated 72 | attributes. 73 | 74 | .. note:: 75 | Any-hashable graphs are implemented by wrapping the integer graph which means that they incur a performance 76 | penalty compared to the integer graph. 77 | 78 | --------------------------------------------------------------------------------