Skip to content

Commit

Permalink
Merge e05732f into ecb14d3
Browse files Browse the repository at this point in the history
  • Loading branch information
brooksandrew committed Sep 13, 2017
2 parents ecb14d3 + e05732f commit 266930b
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 19 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

60 changes: 52 additions & 8 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 @@ -65,15 +65,29 @@ 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 +162,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 +206,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,9 +217,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)
<script src="https://gist.github.com/brooksandrew/1f3a2ce56a3ac0ea0ac2213bccb57e99.js"></script>



Expand Down Expand Up @@ -216,6 +250,15 @@ you need:
python -m pytest
pytest --cov
```

## License

Released under the MIT License (see LICENSE).

Copyright (C) 2017 Andrew Brooks.





[Postman Problems]: https://en.wikipedia.org/wiki/Route_inspection_problem
Expand All @@ -227,5 +270,6 @@ you need:
[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)

114 changes: 104 additions & 10 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,19 +33,83 @@ 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
# ---------------------------------------------------------------

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')

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.')

parser.add_argument('--fps',
required=False,
type=float,
default=3,
help='Frames per second to use for CPP solution animation.')

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.')

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()

Expand All @@ -47,16 +118,39 @@ def main():
# parse arguments
args = get_args()

# solve CPP
cpp_solution = cpp(edgelist_filename=args.edgelist_filename,
start_node=args.start_node,
edge_weight=args.edge_weight)

# print solution
[print(edge) for edge in cpp_solution]

# visualize

# 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__':
Expand Down
2 changes: 1 addition & 1 deletion postman_problems/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def make_circuit_video(infile_dir_images, outfile_movie, fps=3, format='png'):
No return value. Writes a movie/gif to disk
"""
# sorting filenames in order
filenames = glob.glob(infile_dir_images + 'img*.%s' % format)
filenames = glob.glob(os.path.join(infile_dir_images, 'img*.%s' % format))
filenames_sort_indices = np.argsort([int(os.path.basename(filename).split('.')[0][3:]) for filename in filenames])
filenames = [filenames[i] for i in filenames_sort_indices]

Expand Down

0 comments on commit 266930b

Please sign in to comment.