Skip to content

Commit

Permalink
moving CPP SG gif link to gist (#21)
Browse files Browse the repository at this point in the history
Test examples and viz; more love on README
  • Loading branch information
brooksandrew committed Sep 16, 2017
1 parent ecb14d3 commit 9441e05
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 129 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ ENV/
# PNGs
postman_problems/examples/seven_bridges/output/png/
postman_problems/examples/sleeping_giant/output/png/
postman_problems/examples/seven_bridges/output/img/
postman_problems/examples/sleeping_giant/output/img/

# Coveralls config (do not share repo token)
.coveralls.yml

# Large project specific files
postman_problems/examples/sleeping_giant/output/cpp_graph.gif

4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ install:
- bash miniconda.sh -b -p $HOME/miniconda
- export PATH="$HOME/miniconda/bin:$PATH"
- hash -r
- pip install pytest pytest-cov
- sudo apt-get install graphviz
- pip install .
- pip install .[viz]
- pip install .[tests]
- pip install .[test]
script:
- python -m pytest
- py.test --cov=./
Expand Down
79 changes: 68 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ Install the `postman_problems` package:
cd postman_problems
```

2. Install with pip. Builds are tested on Python Python 2.7, 3.3, 3.4, 3.5, 3.6.
2. Install with pip. Builds are tested on Python 2.7, 3.3, 3.4, 3.5, 3.6.
```
pip install .
```

### Viz

`postman_problems` leverages [Graphviz] for visualization which unlocks more robust visualizations than just networkx and
matplotlib. However, it also comes with several dependencies. These are managed separately from the
matplotlib. However, this also comes with several dependencies. These are managed separately from the
base package, so users can optimize graphs to their heart's content unencumbered from the weight and hassle of
installing viz dependencies, if they so choose.

Expand All @@ -55,8 +55,14 @@ installing viz dependencies, if they so choose.
```
brew install graphviz
```

For Windows, Linux and other users, I defer to the Graphviz download docs.

For Linux,
```
sudo apt-get install graphviz
```

These are the installs I'm currently using on my builds for the [tests on TravisCI]. YMMV. For Windows users and
for those where these methods fail, I defer to the Graphviz download docs.


## Usage
Expand All @@ -65,15 +71,30 @@ installing viz dependencies, if they so choose.

The easiest way to start is with the command line installed with this package, `chinese_postman`.

There are several optional command line arguments, but the only one required is `--edgelist`. More on those later.
There are several optional command line arguments, but the only one required is `--edgelist`. For the complete list of
optional arguments run:

```
chinese_postman --help
```

The big ones are `--viz_static` and `--viz_animation` which when present will create the static (single) and animation
of the CPP solution. Most of the other arguments control the visualizations with default values.

Below we solve the CPP on the [Seven Bridges of Konigsberg] network. The edgelist is provided in this repo, but you
can swap this out for any comma delimited text file where the first two columns represent the node pairs in your network.
The columns should have headers. Columns after the first two are treated as edge attributes.

```bash
chinese_postman --edgelist postman_problems/examples/seven_bridges/edgelist_seven_bridges.csv
```

If the `chinese_postman` entry point is not working for whatever reason, you can run the script directly with:

```
python postman_problems/chinese_postman.py --edgelist postman_problems/examples/seven_bridges/edgelist_seven_bridges.csv
```

You should see output that describes the CPP solution (Eulerian circuit) through each edge. Something like this:

```
Expand Down Expand Up @@ -148,20 +169,40 @@ The Seven Bridges of Konigsberg is rather simple network with just 4 nodes and 7
each edge exactly once if all nodes have even degree. Although this wasn't proven until the 1870s by Carl Hierholzer,
Euler was right and this property is a key part of solving the Postman Problems.


This contrived example has been bundled and parameterized into a script that can be run with:
```
chinese_postman_seven_bridges
```

The example can also be run using the verbose method below which allows you to more easily parameterize more pieces.
Many of the options provided below are defaults and can be excluded in practice. They are included here simply to convey
what the possibilities are.
```
chinese_postman --edgelist postman_problems/examples/seven_bridges/edgelist_seven_bridges.csv \
--viz_static \
--viz_static_filename 'postman_problems/examples/seven_bridges/output/cpp_graph' \
--viz_static_engine 'dot' \
--viz_static_format 'svg' \
--viz_animation \
--viz_animation_filename 'postman_problems/examples/seven_bridges/output/cpp_graph.gif' \
--viz_images_dir 'postman_problems/examples/seven_bridges/output/img' \
--viz_animation_engine 'dot' \
--viz_animation_format 'png' \
--fps 2
```

`cpp_graph.svg`: Edges are annotated with the order in which they are walked, starting at 0. Edges walked more than
once are annotated by a sequence and edges **bolded**.
once are annotated by a sequence of numbers (walk order) and **bolded**.

![seven_bridges_cpp_graph](./postman_problems/examples/seven_bridges/output/cpp_graph.svg)

`cpp_graph.gif`: The nodes and edges in red indicate the current direction.

![seven_bridges_cpp_gif](./postman_problems/examples/seven_bridges/output/cpp_graph.gif)

`cpp_graph`: dot representation of the graph is also provided. This is mostly for reference, but in rare cases you may
want to tweak graphviz parameters directly here.


### 2. Sleeping Giant

Expand All @@ -172,7 +213,7 @@ This example is near and dear to my heart and the motivation for this project in
on the [Giantmaster roster] and the glory of a red highlight on their name.

That's all I'll say here. I wrote more about the personal motivation and Sleeping Giant specific data/problem in a
DataCamp tutorial (link forthcoming upon publication) which also helped motivate this project.
[DataCamp tutorial] which also helped motivate this project.


```
Expand All @@ -183,10 +224,9 @@ chinese_postman_sleeping_giant

![sleeping_giant_cpp_graph](./postman_problems/examples/sleeping_giant/output/cpp_graph.svg)

`postman_problems/examples/sleeping_giant/cpp_graph.gif`:
`postman_problems/examples/sleeping_giant/cpp_graph.gif` (omitted from package due to size):

![sleeping_giant_cpp_gif](./postman_problems/examples/sleeping_giant/output/cpp_graph.gif)

![sleeping_giant_animation_example](https://gist.github.com/brooksandrew/1f3a2ce56a3ac0ea0ac2213bccb57e99/raw/08d063791540ef66d1de03603dec77fb2d32ab21/sleeping_giant_cpp_route_animation_using_graphviz.gif)


## Developers
Expand Down Expand Up @@ -216,16 +256,33 @@ you need:
python -m pytest
pytest --cov
```

Some tests that take quite a while to run, namely the examples that write visualizations to the filesystem for
large networks. As I have limited patience while developing, but am too cautious to drop them completely, I've
kept them and marked with the `@slow` and `@long` decorators. I've configured `conftest.py` to exclude them by
default when running `pytest` or `python -m pytest`. You can always run each individual test on a case-by-case basis
by name. Or, for a full complete run of the test suite, you can run `pytest --runslow`.

## License

Released under the MIT License (see LICENSE).

Copyright (C) 2017 Andrew Brooks.





[Postman Problems]: https://en.wikipedia.org/wiki/Route_inspection_problem
[Seven Bridges of Konigsberg]:https://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg
[Graphviz python package]: https://pypi.python.org/pypi/graphviz
[Graphviz Download page]:http://www.graphviz.org/Download..php
[Graphviz]:http://www.graphviz.org/
[Tests on TravisCI]: https://github.com/brooksandrew/postman_problems/blob/master/.travis.yml
[Sleeping Giant]:http://www.sgpa.org/
[Giant Master Program]:http://www.sgpa.org/hikes/masters.html
[trail map]:http://www.ct.gov/deep/lib/deep/stateparks/maps/sleepgiant.pdf
[Giantmaster roster]:http://www.sgpa.org/hikes/master-list.htm
[Datacamp tutorial]:https://www.datacamp.com/community/tutorials/networkx-python-graph-tutorial
[DOT]:https://en.wikipedia.org/wiki/DOT_(graph_description_language)

16 changes: 16 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest


def pytest_addoption(parser):
"""Grabbed from https://docs.pytest.org/en/latest/example/simple.html#control-skipping-of-tests-according-to-command-line-option"""
parser.addoption('--runslow', action='store_true', default=False, help='run slow tests')


def pytest_collection_modifyitems(config, items):
if config.getoption('--runslow'):
# --runslow given in cli: do not skip slow tests
return
skip_slow = pytest.mark.skip(reason='need --runslow option to run')
for item in items:
if 'slow' in item.keywords:
item.add_marker(skip_slow)
120 changes: 106 additions & 14 deletions postman_problems/chinese_postman.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import argparse
import logging
from postman_problems.graph import cpp
from postman_problems.viz import make_circuit_graphviz, make_circuit_video, make_circuit_images


def get_args():
Expand All @@ -9,6 +11,10 @@ def get_args():
"""
parser = argparse.ArgumentParser(description='Arguments to the Chinese Postman Problem solver')

# ---------------------------------------------------------------
# CPP optimization
# ---------------------------------------------------------------

parser.add_argument('--edgelist_filename',
required=True,
type=str,
Expand All @@ -17,6 +23,7 @@ def get_args():
'The first two columns should be the "from" and "to" node names.'
'Additional columns can be provided for edge attributes.'
'The first row should be the edge attribute names.')

parser.add_argument('--nodelist_filename',
required=False,
type=str,
Expand All @@ -26,44 +33,129 @@ def get_args():
'The first column should be the node name.'
'Additional columns should be used to provide node attributes.'
'The first row should be the node attribute names.')

parser.add_argument('--start_node',
required=False,
type=str,
default=None,
help='Node to start the CPP solution from (optional).'
'If not provided, a starting node will be selected at random.')

parser.add_argument('--edge_weight',
required=False,
type=str,
default='distance',
help='Edge attribute used to specify the distance between nodes (optional).'
'Default is "distance".')

# ---------------------------------------------------------------
# CPP viz
# ---------------------------------------------------------------

return parser.parse_args()
parser.add_argument('--viz_static',
action='store_true',
help='Write out the static image of the CPP solution using graphviz?')

parser.add_argument('--viz_animation',
action='store_true',
help='Write out an animation (GIF) of the CPP solution using a series of static graphviz images')

def main():
# parse arguments
args = get_args()
parser.add_argument('--viz_static_engine',
required=False,
type=str,
default='dot',
help='graphviz engine to use for viz layout: "dot", "neato", "fdp", etc) of solution static viz')

parser.add_argument('--viz_animation_engine',
required=False,
type=str,
default='dot',
help='graphviz engine to use for viz layout: "dot", "neato", "fdp", etc) of solution animation '
'viz')

parser.add_argument('--viz_static_format',
required=False,
type=str,
default='png',
help='File type to use when writing out static (single) CPP solution viz using graphviz.')

parser.add_argument('--viz_animation_format',
required=False,
type=str,
default='png',
help='File type to use when writing out animation of CPP solution viz using graphviz.')

# solve CPP
cpp_solution = cpp(edgelist_filename=args.edgelist_filename,
start_node=args.start_node,
edge_weight=args.edge_weight)
parser.add_argument('--fps',
required=False,
type=float,
default=3,
help='Frames per second to use for CPP solution animation.')

# print solution
[print(edge) for edge in cpp_solution]
parser.add_argument('--viz_static_filename',
required=False,
type=str,
default='cpp_graph',
help='Filename to write static (single) viz of CPP solution to. Do not include the file type '
'suffix. This is added with the `format` argument.')

# visualize
parser.add_argument('--viz_animation_filename',
required=False,
type=str,
default='cpp_graph.gif',
help='Filename to write animation (GIF) of CPP solution viz to.')

parser.add_argument('--viz_images_dir',
required=False,
type=str,
default='img',
help='Directory where the series of static visualizations will be produced that get stitched '
'into the animation.')

return parser.parse_args()

if __name__ == '__main__':
"""Parse command line arguments and solve the CPP"""
main()

def main():

# get args
args = get_args()

# setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info('Solving the CPP...')
cpp_solution, graph = cpp(edgelist_filename=args.edgelist_filename,
start_node=args.start_node,
edge_weight=args.edge_weight)
for edge in cpp_solution:
logger.info(edge)

if args.viz_static:
logger.info('Creating single image of CPP solution...')
graph_gv = make_circuit_graphviz(circuit=cpp_solution,
graph=graph,
filename=args.viz_static_filename,
format=args.viz_static_format,
engine=args.viz_static_engine)

if args.viz_animation:
logger.info('Creating individual files for animation...')
make_circuit_images(circuit=cpp_solution,
graph=graph,
outfile_dir=args.viz_images_dir,
format=args.viz_animation_format,
engine=args.viz_animation_engine)

logger.info('Creating animation...')
video_message = make_circuit_video(infile_dir_images=args.viz_images_dir,
outfile_movie=args.viz_animation_filename,
fps=args.fps,
format=args.viz_animation_format)
logger.info(video_message)


if __name__ == '__main__':
"""Parse command line arguments and solve the CPP"""
main()


14 changes: 0 additions & 14 deletions postman_problems/examples/seven_bridges/output/cpp_graph

This file was deleted.

Binary file not shown.
Loading

0 comments on commit 9441e05

Please sign in to comment.