{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "There's something very rewarding about watching a pen plotter's dance as it traces out a graphic you've previously only seen on a screen. It can also be a stark reminder of how forgiving screen graphics are by comparison, as images that render in miliseconds on screen can take ages to plot on paper.\n", "\n", "Each line in a plot takes time in two ways: it takes time to trace the line, and it takes time to move the pen into place from the last position. As a result of the latter, the drawing order of the lines can make a big difference to how long a plot takes. It's common to generate an image that can be sped up by an order of magnitude just by choosing a better drawing order.\n", "\n", "Some folks in the plotting community have already been generous enough to share route optimizers: [Anders Hoff](https://twitter.com/inconvergent)'s [svgsort](https://github.com/inconvergent/svgsort), [Trammell Hudson](https://twitter.com/qrs)'s [vecsort](https://github.com/osresearch/polargraph/blob/master/vecsort.c), [Windell Oskay](https://github.com/oskay)'s [AxiDraw patch](https://github.com/evil-mad/axidraw/issues/4), [Romain Testuz](https://github.com/Daekkyn)'s [InkscapeOptimizePath](https://github.com/Daekkyn/inkscapeOptimizePath).\n", "\n", "Most of these use a “greedy” approach, in which the path is built by iteratively tracing the *closest* untraced path to the current position. (InkscapeOptimizePath is the exception: it uses an approach based on [Eulerian cycles](http://mathworld.wolfram.com/EulerianCycle.html).) In this post I investigate whether we can do better than a greedy algorithm using a more sophisticated path solver." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Environment Setup\n", "\n", "As an optimization package, we'll use [Google's Optimization Tools](https://developers.google.com/optimization/). For the greedy approach, we'll use the [rtree](https://pypi.python.org/pypi/Rtree/) package. It's easiest to install through [Conda](https://conda.io/docs/) since the [libspatialindex](https://libspatialindex.github.io/) dependency is installed automatically. Also, the [svgpathtools](https://github.com/mathandy/svgpathtools) package greatly simplifies parsing and writing paths from SVG files." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Install dependencies\n", "!conda install -q -y rtree 2&> /dev/null\n", "!pip -q install ortools svgpathtools" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualizing the Problem\n", "\n", "I have an SVG file that I'd like to plot. Here's what it looks like:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import SVG\n", "SVG('spiralthing.svg')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Unfortunately, the drawing order is very inefficient. To show you what I mean, let's process the image in two ways:\n", "1. Colorizing the lines based on drawing order.\n", "2. Adding a line for each “pen transit” that is necessary to draw the lines in order." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use `svg2paths` to grab all the paths as a list. This function also returns a dictionary of attributes for each path, but we can ignore those." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import svgpathtools\n", "paths, _ = svgpathtools.svg2paths('spiralthing.svg')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To colorize the lines, `generate_color` returns a color string from an [HSV value](https://en.wikipedia.org/wiki/HSL_and_HSV). By using different values of hue between 0 and 1, we get a nice rainbow gradient. `visualize_pen_transits` takes a list of paths like `svgpathtools` produces and renders a visualization of the transits with the notebook." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import colorsys\n", "import tempfile\n", "import os\n", "\n", "def generate_color(hue, saturation=1.0, value=1.0):\n", " rgb = colorsys.hsv_to_rgb(hue, saturation, value)\n", " return 'rgb({},{},{})'.format(*[int(x*255) for x in rgb])\n", "\n", "def visualize_pen_transits(paths):\n", " # We will construct a new image by storing (path, attribute)\n", " # pairs in this list.\n", " parts = list()\n", " \n", " last_end = None\n", " for i, path in enumerate(paths):\n", " start = path.start\n", " end = path.end\n", " \n", " # Generate a color based on how far along in the plot we are.\n", " frac = i / (len(paths) - 1)\n", " color = generate_color(frac, 1.0, 1.0)\n", "\n", " if last_end is not None:\n", " # If this isn't our first path, add a line between the end of\n", " # the last path and the start of this one.\n", " parts.append((\n", " svgpathtools.Line(last_end, start),\n", " {\n", " 'stroke': 'black',\n", " 'fill': 'none',\n", " }\n", " ))\n", " \n", " last_end = end\n", " \n", " # Also draw a faded, colorized version of this path.\n", " parts.append((\n", " path,\n", " {\n", " 'stroke': color,\n", " 'fill': 'none',\n", " 'opacity': '0.5'\n", " }\n", " ))\n", " \n", " # Write the SVG to a temporary file and load it back as an object that\n", " # will appear in the notebook.\n", " with tempfile.TemporaryDirectory() as svg_path:\n", " svg_file = os.path.join(svg_path, 'image.svg')\n", " new_paths, new_attrs = zip(*parts)\n", " svgpathtools.wsvg(new_paths, attributes=new_attrs, filename=svg_file)\n", " svg = SVG(svg_file)\n", " return svg" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see what the input image looks like when we visualize the transits:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "visualize_pen_transits(paths)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ick! All those lines represent time the pen is just flying through the air instead of writing on the page. Surely we can do better." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Quantifying the Problem\n", "\n", "Now we know what a bad solution looks like, but how do we measure it? One way is to simply add up the distance that the pen travels in the “pen up” (not drawing) position. The distance in the “pen down” position is the same no matter what order we draw the lines in, so we can just ignore it.\n", "\n", "`svgpathtools` stores an `(x, y)` coordinate pair as the complex number `x + y*j` (in Python, `j` represents the imaginary component). This simplifies the Euclidean distance calculation:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def dist(p1, p2):\n", " return abs(p1 - p2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using this distance function, we can define the **cost** of a route to be the travel time between the end of each path and the start of the next one. A pen plotter starts and returns to an **origin point**, so to fully account for the cost we also need to add the distance to and from the origin." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def cost_of_route(path, origin=0.+0j):\n", " # Cost from the origin to the start of the first path\n", " cost = dist(origin, path[0].start)\n", " # Cost between the end of each path and the start of the next\n", " cost += sum(\n", " dist(path[i].end, path[i+1].start) for i in range(len(path) - 1)\n", " )\n", " # Cost to return back to the origin\n", " cost += dist(path[-1].end, origin)\n", " return cost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now calculate the cost of the route. This will give us a perspective of how much things improve (or don't improve; I'm not giving spoilers.)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "24116.595176769544" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "initial_cost = cost_of_route(paths)\n", "initial_cost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Building a Graph\n", "\n", "Now we have a cost function that we hope to reduce by re-ordering the paths. But before we jump into that, here's another thing to consider: in addition to re-ordering the paths, we can also reverse them. In order to take advantage of that fact, we need to keep track of both the order of the paths and whether or not each one is reversed.\n", "\n", "Rather than storing the direction explicitly, I find it easier to represent the problem by creating a graph and adding nodes to the graph for each path *and its reverse*. A valid drawing order is then a cycle through the graph, starting and returning to the origin (which is also represented as a node in the graph), which visits **exactly one** node belonging to each (path, reversed path) pair.\n", "\n", "To build this graph, we construct a list of nodes like this: `[origin, path[0], path[0] reversed, path[1], path[1] reversed, ...]`, that is, `self.endpoints[0]` represents the origin, and then each pair of nodes that follows represents the two directions of one path. Excepting the origin (0), paths with an **odd index** correspond to **unmodified** paths from the original drawing, and paths with an **even index** correspond to their **reversed** versions." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class PathGraph:\n", " # The origin is always at index 0.\n", " ORIGIN = 0\n", " \n", " def __init__(self, paths, origin=0.+0j):\n", " \"\"\"Constructs a PathGraph from the output of svgpathtools.svg2paths.\"\"\"\n", " self.paths = paths\n", " # For any node i, endpoints[i] will be a pair containing that node's\n", " # start and end coordinates, respectively. For i==0 this represents\n", " # the origin.\n", " self.endpoints = [(origin, origin)]\n", " \n", " for path in paths:\n", " # For each path in the original list of paths,\n", " # create nodes for the path as well as its reverse.\n", " self.endpoints.append((path.start, path.end))\n", " self.endpoints.append((path.end, path.start))\n", " \n", " def get_path(self, i):\n", " \"\"\"Returns the path corresponding to the node i.\"\"\"\n", " index = (i - 1) // 2\n", " reverse = (i - 1) % 2\n", " path = self.paths[index]\n", " if reverse:\n", " return path.reversed()\n", " else:\n", " return path\n", " \n", " def cost(self, i, j):\n", " \"\"\"Returns the distance between the end of path i\n", " and the start of path j.\"\"\"\n", " return dist(self.endpoints[i][1], self.endpoints[j][0])\n", " \n", " def get_coordinates(self, i, end=False):\n", " \"\"\"Returns the starting coordinates of node i as a pair,\n", " or the end coordinates iff end is True.\"\"\"\n", " if end:\n", " endpoint = self.endpoints[i][1]\n", " else:\n", " endpoint = self.endpoints[i][0]\n", " return (endpoint.real, endpoint.imag)\n", " \n", " def iter_starts_with_index(self):\n", " \"\"\"Returns a generator over (index, start coordinate) pairs,\n", " excluding the origin.\"\"\"\n", " for i in range(1, len(self.endpoints)):\n", " yield i, self.get_coordinates(i)\n", " \n", " def get_disjoint(self, i):\n", " \"\"\"For the node i, returns the index of the node associated with\n", " its path's opposite direction.\"\"\"\n", " return ((i-1) ^ 1) + 1\n", " \n", " def iter_disjunctions(self):\n", " \"\"\"Returns a generator over 2-element lists of indexes which must\n", " be mutually exclusive in a solution (i.e. pairs of nodes which represent\n", " the same path in opposite directions.)\"\"\"\n", " for i in range(1, len(self.endpoints), 2):\n", " yield [i, self.get_disjoint(i)]\n", " \n", " def num_nodes(self):\n", " \"\"\"Returns the number of nodes in the graph (including the origin.)\"\"\"\n", " return len(self.endpoints)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "path_graph = PathGraph(paths)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Greedy Approach\n", "\n", "Using this graph, we can implement a greedy solution. Here's how it works:\n", "1. Start at the origin.\n", "2. Move to the nearest node. Add the path represented by that node to the end of the solution.\n", "3. Remove both the node you are on, and the node corresponding to the opposite direction of the same path.\n", "4. If there are nodes remaining, return to 2.\n", "\n", "The second step requires a bit of nuance: the naive approach of looping over every point to find the nearest can get computationally expensive. Instead, we'll use a spatial index called an [R-tree](https://en.wikipedia.org/wiki/R-tree) which is good at quickly finding the nearest node to a point.\n", "\n", "The `PathIndex` class below takes a `PathGraph` and turns it into a data structure that can be quickly queried to find the nearest point to a given coordinate." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import rtree\n", "\n", "class PathIndex:\n", " def __init__(self, path_graph):\n", " self.idx = rtree.index.Index()\n", " self.path_graph = path_graph\n", " for index, coordinate in path_graph.iter_starts_with_index():\n", " self.idx.add(index, coordinate + coordinate)\n", " \n", " def get_nearest(self, coordinate):\n", " return next(self.idx.nearest(coordinate))\n", " \n", " def delete(self, index):\n", " coordinate = self.path_graph.get_coordinates(index)\n", " self.idx.delete(index, coordinate + coordinate)\n", " \n", " def delete_pair(self, index):\n", " self.delete(index)\n", " self.delete(self.path_graph.get_disjoint(index))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `greedy_walk` function implements the logic mentioned earlier: we start at the origin, then find the nearest point and move there, deleting nodes along the way." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def greedy_walk(path_graph):\n", " path_index = PathIndex(path_graph)\n", " location = path_graph.get_coordinates(path_graph.ORIGIN)\n", " while True:\n", " try:\n", " next_point = path_index.get_nearest(location)\n", " except StopIteration:\n", " break\n", " location = path_graph.get_coordinates(next_point, True)\n", " path_index.delete_pair(next_point)\n", " yield next_point" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "greedy_solution = list(greedy_walk(path_graph))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have a solution, we want to ensure that it is “valid”, i.e. that it visits all of the original paths exactly once (possibly in reverse). We also want to see if it improved on the original solution." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from collections import Counter\n", "\n", "def check_valid_solution(solution, graph):\n", " \"\"\"Check that the solution is valid: every path is visited exactly once.\"\"\"\n", " expected = Counter(\n", " i for (i, _) in graph.iter_starts_with_index()\n", " if i < graph.get_disjoint(i)\n", " )\n", " actual = Counter(\n", " min(i, graph.get_disjoint(i))\n", " for i in solution\n", " )\n", "\n", " difference = Counter(expected)\n", " difference.subtract(actual)\n", " difference = {k:v for k,v in difference.items() if v != 0}\n", " if difference:\n", " print('Solution is not valid!'\n", " 'Difference in node counts (expected - actual): {}'.format(difference))\n", " return False\n", " return True\n", "\n", "def get_route_from_solution(solution, graph):\n", " \"\"\"Converts a solution (a list of node indices) into a list\n", " of paths suitable for rendering.\"\"\"\n", " \n", " # As a guard against comparing invalid \"solutions\",\n", " # ensure that this solution is valid.\n", " assert check_valid_solution(solution, graph)\n", " \n", " return [graph.get_path(i) for i in solution]" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "greedy_route = get_route_from_solution(greedy_solution, path_graph)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's calculate the cost of this solution:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4739.529261671649" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "greedy_cost = cost_of_route(greedy_route)\n", "greedy_cost" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.088394615853376" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "initial_cost / greedy_cost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's more than a 5x improvement, pretty good! Let's visualize it:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "" ], "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "visualize_pen_transits(greedy_route)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Limitations of Greedy Approach\n", "\n", "The problem with a greedy approach ([Gordon Gekko notwithstanding](https://www.youtube.com/watch?v=VVxYOQS6ggk)) is that we are choosing what's locally optimal at each point on the walk without concern for the side-effects of reducing the available choices later on. This problem becomes apparent if we plot the amount of ink on the page (i.e. the travel distance with the pen down) versus the pen-up transit distance." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "plt.style.use('bmh')" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def ink_transit_curve(route, origin=0.+0.j, label=None):\n", " ink_distance = 0.\n", " transit_distance = 0.\n", " curve = list()\n", " last_end = origin\n", " \n", " for path in route:\n", " transit_distance += dist(last_end, path.start)\n", " ink_distance += path.length()\n", " curve.append((transit_distance, ink_distance))\n", " last_end = path.end\n", " transit_distance += dist(last_end, origin)\n", " curve.append((transit_distance, ink_distance))\n", " \n", " plt.plot(*zip(*curve), label=label)\n", " plt.xlabel('Pen-up travel distance')\n", " plt.ylabel('Ink on page')" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZEAAAEGCAYAAACkQqisAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xl8XHW9//HXJ8sk0zRJszXp3tJVFoEfFfCiLAahKFpv\nLypuoFavS724K3hd7xVFcbu4cPWKsqgsFxcWQVAElGsLQgGhUKC0Dd2yp5Ntkkwmn98f5yTMZBLO\nJJnJmeXzfDzmkcn3LPM975nkO+ec7zlfUVWMMcaY6SjwuwLGGGOylzUixhhjps0aEWOMMdNmjYgx\nxphps0bEGGPMtFkjYowxZtqKZuNFRORnwLlAq6oe7ZZdDrwBGAKeB96jqofdaZcAm4EocJGq3uWW\nnwBcDQSBO4CPqqqKSAlwLXAC0AG8VVX3TlSX++67T0tKStK0pcYYk3v6+/vbGxsb6yaaNiuNCM4/\n/h/g/KMf9UfgElUdFpFvAJcAnxWRI4HzgaOAhcCfRGSNqkaBK4H3Aw/iNCIbgDtxGpwuVV0lIucD\n3wDeOlFFSkpKWLduXRo2MbM1NTWxbNkyv6uRUSyTRJZJPMvDsX379qbJps3K4SxV/QvQOa7sblUd\ndn/dBix2n28EblDVQVXdA+wCThSRBUCFqm5T5wrJa4E3xSxzjfv8ZqBRRCR9W5R9iouL/a5CxrFM\nElkm8SwPb7O1J+LlvcCN7vNFOI3KqP1uWcR9Pr58dJl9AO6eTQioAdrHv1BrayubN2+mqKiIaDTK\npk2b2LJlC83NzZSVlVFYWEh3dzd1dXV0dnaiqtTV1dHS0sLcuXMB6O3tpb6+nra2NkSE6upq2tra\nqKioIBqN0tfXR0NDA83NzRQXF1NZWUl7ezuVlZUMDQ0RDofHpgcCAcrLy+no6KCqqopwOMzAwMDY\n9NLSUoLBIF1dXdTU1NDT08PQ0NDY9GAwSCAQIBQKUVtbSygUIhKJjE0f3ab+/n76+/tzaptm+j71\n9/czODiYU9s00/cpNpNc2aaZvE9DQ0M0NTXl1DZN932ajMzWbU9EZDlw++g5kZjyfwfWA5vc8xs/\nALap6i/c6VfhHLLaC1ymqme65a8GPquq54rIk8AGVd3vTnseOElVExqRrVu3qh3OMmCZTMQyiWd5\nOLZv3/5IY2Pj+omm+do7S0TejXPC/R36Ymt2AFgSM9tit+wALx7yii2PW0ZEioBKnBPsxlVZWel3\nFTKOZZLIMolneXjzrRERkQ3AZ4A3qmp/zKRbgfNFpEREVgCrgYdU9RDQLSInu+c7LgBuiVnmQvf5\necCfdbZ2sbLE0NCQ31XIOJZJIssknuXhbba6+F4PnA7Uish+4Es4vbFKgD+658C3qeoHVXWHiNwE\nPAUMA1vcnlkAH+bFLr53ug+Aq4DrRGQXzgn882dju7JJOBz2uwoZxzJJZJnEszy8zdo5kUyRr+dE\nBgcHsetj4lkmiSyTeMnkER1RhqIjY7+P/ksd/5919H+tjpuvrW+I7sEos2Fd3RyCxYVTXu6lzolk\nSu8sk2bNzc12gnAcyyRRvmaiqrT1RYhERxgYdh+REfYdamHuvJqxsoOhQZp7B8em9wxGaenNnkNe\nPz3vZSydN/VG5KVYI5InAoGA31XIOJZJonzLJBId4fan27nr2Q52dw5MMldvUusqKSpg9OK00avU\nxl+sNnr5Wux80RGlPzLCsQteuittKpQWpf40uDUieaK8vNzvKmQcyyRRPmWyo6WXnzx4gKdbnX49\nxQVCbVkxJUUFBIsLKC0qoEiUuaUBSosKKC0uoDpYzLKqUoJFBQSLC8fK68qKydfrm60RyRMdHR2e\nFw3lG8skUb5k8stHm7nmkUNjv5+yrJJPnLqU8pL4f4l2nYg3a0TyRFVVld9VyDiWSaJcz+T6x5q5\nY2fH2HmM166u5l+Ons8RNcEJ58/1PFLBbgWfJ6yrYiLLJFEuZ3LFA/v4+cOHaOkdQnAakE+ftmzS\nBgRyO49UsT2RPDEwMNlJw/xlmSTK1UwePdDD7TuduyC98/gGzjtmPnMC3r2UcjWPVLJGJE80NDT4\nXYWMY5kkypVMItERugeiDEVH6I9E+d4DLwBw/MK5XHDCgqTXkyt5pJM1InkiX/v/vxTLJFG2Z/JM\nWx937Ozg/t1d9EdGEqZ/6JWLJ1hqctmex2ywRiRPlJaW+l2FjGOZJMqmTFSVXz7Wwp7OMOFIlB0t\nfYRjGo6qYBGBwgIChcKK6iBnralmedXk5z8mkk15+MUakTwRDE7tjycfWCaJsiWToeERvvvAC9yz\nqyth2oY1NZx3zHyWVs28AciWPPxkjUie6OrqoqKiwu9qZBTLJFE2ZLL9QDdfvWcvvUNRigqExlVV\nvHrFPEqLCllYEaC2LHVX3WdDHn6zRiRP1NTU+F2FjGOZJMrkTIaGR/jLnsN8835nuO/ykkK+vmEV\na+rmpO01MzmPTGGNSJ7o6enJiyuRp8IySZSpmbzQNcAX7n6eQz3ORYJr6+bwtQ0rE64wT7VMzSOT\nWCOSJ2xwnUSWSaJMzOTRAz18/u7niUSV6mARm46Zz5uPmT8r96rKxDwyjTUiecL6uyeyTBJlWibR\nEeXbf20iElWWVZXy3XNXMzfNex+xMi2PTGS3PckTzc3Nflch41gmiTItk9/vbKe1N8Kc4gK+v3Ht\nrDYgkHl5ZCJrRPKEdVVMZJkkyqRMHjvYw4+27gfgU6ctS8tYGF4yKY9MZY1Insi3wYaSYZkkypRM\ndjT38sW7dzOicOqKeZyyrNKXemRKHpnMGpE8EQqF/K5CxrFMEvmdyYgq37xvLx+//TkGhkc4Y2UV\nl5yx3LcBn/zOIxvYifU8UVtb63cVMo5lksjPTEZUeef1O2jvjwBwxsoqPn3aMgoL/Bsx0D4j3mxP\nJE/YN6pElkkiPzO5Y2fHWAPy/xaVc8kZyynysQEB+4wkw/ZE8kQkEvG7ChnHMknkVyZfuOt5HtzX\nDbx4IWEmsM+IN9sTyRPW3z2RZZLIj0x2d4THGpDVtUEuPXslBT6dAxnPPiPebE8kT9i4CIksk0Sz\nkcnjB3t4qrWPweERegaj/GlXJ+AcwrrsnFVpfe2pss+It1lpRETkZ8C5QKuqHu2WVQM3AsuBvcBb\nVLXLnXYJsBmIAhep6l1u+QnA1UAQuAP4qKqqiJQA1wInAB3AW1V172xsW7YoKyvzuwoZxzJJlO5M\nHj/Yw6fv2JVQ/sqllXzm9Mz7Z22fEW+zdTjramDDuLKLgXtUdTVwj/s7InIkcD5wlLvMj0RkdDDk\nK4H3A6vdx+g6NwNdqroK+C7wjbRtSZYqLPQeTzrfWCaJ0pmJqvK9B/aN/f7O4xt4/4kLufx1q/jK\nWUdQlsSY57PNPiPeZqURUdW/AJ3jijcC17jPrwHeFFN+g6oOquoeYBdwoogsACpUdZuqKs6ex5sm\nWNfNQKP41bE8Q3V3d/tdhYxjmSRKZybXPHKIA92DAFz5z2u54IQFvPnl9Ry7sDxtrzlT9hnx5uc5\nkXpVPeQ+bwbq3eeLgG0x8+13yyLu8/Hlo8vsA1DVYREJATVAe3qqnn3q6ur8rkLGsUwSpToTVaUz\nPMyPt+3nvt2HAfjkqUtZWZO+MUBSyT4j3jLixLp7XkNn47VaW1vZvHkzRUVFRKNRNm3axJYtW2hu\nbqasrIzCwkK6u7upq6ujs7MTVaWuro6WlpaxcQV6e3upr6+nra0NEaG6upq2tjYqKiqIRqP09fXR\n0NBAc3MzxcXFVFZW0t7eTmVlJUNDQ4TD4bHpgUCA8vJyOjo6qKqqIhwOMzAwMDa9tLSUYDBIV1cX\nNTU19PT0MDQ0NDY9GAwSCAQIhULU1tYSCoWIRCJj00e3af/+/axcuTKntmmm79OhQ4dYs2ZNTm3T\nTN+n1tbWsUxmsk3PNh3k6qfD7OoaIjz84p/229aVcdrSMpqamrLis9fU1ERZWVnGvU9+fPYmI86R\nofQTkeXA7TEn1p8BTlfVQ+6hqvtUda17Uh1V/bo7313Al3FOvt+rquvc8re5y39gdB5V3SoiRTh7\nNnU6wcZt3bpV161bl96NzUD79u1jyZIlflcjo1gmiVKVyVUPHeDGf7QCECwuoKy4kDNXV/PeVyyc\n8bpnk31GHNu3b3+ksbFx/UTT/NwTuRW4ELjM/XlLTPmvROQ7wEKcE+gPqWpURLpF5GTgQeAC4Pvj\n1rUVOA/480QNSD6z3fJElkmimWQSHVF2tPRx21Nt3L/HOXR18enLOGNllW/3vpop+4x4m60uvtcD\npwO1IrIf+BJO43GTiGwGmoC3AKjqDhG5CXgKGAa2qGrUXdWHebGL753uA+Aq4DoR2YVzAv/8Wdis\nrNLS0mL93cexTBJNN5PugWHO+8UTcWUnLqngNauqU1U1X9hnxNusNCKq+rZJJjVOMv+lwKUTlD8M\nHD1B+QDw5pnUMdfZONGJLJNE083kkQMv9mKqChZx+soqNmfZoauJ2GfEW0acWDfGZLfHDvYC8L5X\nLOQtx9Z7zG1yid07K0/09vb6XYWMY5kkmmomfUNRfvi3/dz5TAcAL1+QW9/c7TPizfZE8kR9vX07\nHM8ySTSVTNr6hnjH9TvGfj92wVzW1mXH9R/Jss+IN2tE8kRbW5t1VRzHMkn0UpmoKofDw/xuRxt/\n3XuY/aHBsWlf27CS9YsrZquas8Y+I96sEckT2drFMp0sk0QTZXLXsx386blOnmvvpz8yEjetuED4\nyClLcrIBAfuMJMMakTxRXZ3dXS3TwTJJND6TweERvv2XF8Z+LykqoCxQwKaj53P2mhrKSwozZuyP\ndLDPiDdrRPJEW1ub9XcfxzJJND6TZ9r6x55f+9YjaSgv8aNavrHPiDfrnZUnKipy83DDTFgmicZn\n8sBe58rzjUfW5V0DAvYZSYY1InkiGo16z5RnLJNEsZmEBpyT6ABr6oJ+VclX9hnxZo1Inujr6/O7\nChnHMknU19eHqrKnM8yX7t4NgADHLsjcMT/SyT4j3uycSJ5oaGjwuwoZxzJJVDu/ni/evZsH9714\nG5NPnrqU+XMDPtbKP/YZ8WZ7InmiubnZ7ypkHMsknqpy0a3PjjUgK6pK+e65qzlrTY3PNfOPfUa8\n2Z5IniguLva7ChnHMoFwJErPYJS+oSh/eq6TfT3OOYAPnbyIfz56vs+18599RrxZI5InKisr/a5C\nxsnHTJ7v6Oe2p9sJhYfZ0zXAwe7BhHnOXlNtDYgrHz8jU2WNSJ5ob2+nrKzM72pklHzM5KcPHeSR\nAz1xZcWFwsLyEhZWlvDK2hHOPm6pT7XLPPn4GZkqa0TyhH2jSpRvmYQGhscakM2vWMjaujksnVdK\n9ZwXD9kcPnzYbvURI98+I9NhjUieGBoa8rsKGSefMtl3eIDP3/X82O//csx8igoSG4t8yiQZloc3\na0TyRDgc9rsKGScfMlFV7n2+iyu3HSA0MMzCigBfPXvlhA0I5EcmU2F5eLNGJE9Yf/dE+ZDJg/u6\nuey+JgCOqA7yzdetoqJ08j/7fMhkKiwPb3adSJ6w/u6J8iGT/3PvfdVQHuCKjWtesgGB/MhkKiwP\nb7YnkicCgfy84vil5HImfUNRLv3zHh7e75xI/+zpywgUen9nzOVMpsPy8GZ7InmivDw/7330UnI5\nkyu37h9rQBpXVXFUfXJjn+dyJtNheXizRiRPdHR0+F2FjJPLmfzVPYz1qVOX8pnTkh8PI5czmQ7L\nw5s1InmiqqrK7ypknFzN5Pan2wlHRggUCo2rqqd03UeuZjJdloc3a0TyhHVVTJSLmTzR3MsV/7cP\ngLNW11A4SVfeyeRiJjNheXjzvRERkY+LyA4ReVJErheRUhGpFpE/ishz7s+qmPkvEZFdIvKMiJwd\nU36CiDzhTrtC7LLbOAMDA35XIePkUiZ7u8JcfOcuPnn7cwDUlRXzkVMWT3k9uZRJKlge3nxtRERk\nEXARsF5VjwYKgfOBi4F7VHU1cI/7OyJypDv9KGAD8CMRKXRXdyXwfmC1+9gwi5uS8ay/e6JcyeRw\nOMLHbn2W7e4tTcoChXz17JUUTON7VK5kkiqWhzff90RwuhkHRaQImAMcBDYC17jTrwHe5D7fCNyg\nqoOqugfYBZwoIguAClXdpqoKXBuzjMH6u08kVzK585kO+iMjAHzpzBX84vyjWFE9veFscyWTVLE8\nvPnaiKjqAeBbwAvAISCkqncD9ap6yJ2tGah3ny8C9sWsYr9btsh9Pr7cuEpLS/2uQsbJlUxGu/J+\n5J8Wc8ryeZQFCj2WmFyuZJIqloc3Xy82dM91bARWAIeB/xWRd8bOo6oqIpqq12xtbWXz5s0UFRUR\njUbZtGkTW7Zsobm5mbKyMgoLC+nu7qauro7Ozk5Ulbq6OlpaWpg71+lr39vbS319PW1tbYgI1dXV\ntLW1UVFRQTQapa+vj4aGBpqbmykuLqayspL29nYqKysZGhoiHA6PTQ8EApSXl9PR0UFVVRXhcJiB\ngYGx6aWlpQSDQbq6uqipqaGnp4ehoaGx6cFgkEAgQCgUora2llAoRCQSGZs+uk2hUIjy8vKc2qaZ\nvk+hUIh58+Zl9Tb1FZbxRHMvAGvLozQ1Nc3ofYrNJFPeJz8/e319fXGZ5sI2Tfd9mow4R3/8ISJv\nBjao6mb39wuAk4FG4HRVPeQeqrpPVdeKyCUAqvp1d/67gC8De4F7VXWdW/42d/kPjH/NrVu36rp1\n69K+bZmmqamJZcuSv14gH+RCJu+4/kna+iKsX1zO1zasmvH6ciGTVLI8HNu3b3+ksbFx/UTT/D4n\n8gJwsojMcXtTNQJPA7cCF7rzXAjc4j6/FThfREpEZAXOCfSH3ENf3SJysrueC2KWMUBNTf6Okz2Z\nbM/kYPcgbX0RAN768nqPuZOT7ZmkmuXhbUqNiIgsEZGTU/XiqvogcDOwHXjCrc9PgMuA14rIc8CZ\n7u+o6g7gJuAp4A/AFlWNuqv7MPBTnJPtzwN3pqqeuaCnp8d7pjyT7Zn84Rnnauq6smKOXZia23Nk\neyapZnl4S+qciIgsBa4HjgMUmCsi5+EcinrfTCqgql8CvjSueBBnr2Si+S8FLp2g/GHg6JnUJZfZ\n4DqJsjmT6Ihy+9PtAPzrSanrQ5LNmaSD5eEt2T2RHwO/B8qBiFv2R+C16aiUST3r754omzP50db9\n9A5FKS4Q/mlZ6oZwzeZM0sHy8JZsI3IicJmqjuDsiaCqIcAGIM4S1t89UbZmsu2FELe5eyHnH1dP\ncRK3eE9WtmaSLpaHt2S7+LYAq4BnRwvcq8dfSEelTOoFg9O7+CyXZVsmjx3s4brtzWNdeo9dMJc3\np+iE+qhsyyTdLA9vyTYi3wJuF5GvA0VuF9rP4Z7wNpnPBtdJlE2ZPHawh4vv3MWI2yP/iOpSPvaq\npZQWpbaDZTZlMhssD29JNSKq+jMR6QA+gHPF+IXAF1T1d+msnEmd0YvIzIuyKZNfPdbMiMKZq6o4\n/7gGllSWTOkW78nKpkxmg+XhLekr1lX1Fuzai6xVW1vrdxUyTjZk0jcU5S97DvPYQecQ1tuPb2Bx\nZfpuxZENmcwmy8Nbsl183zvJpEGc+1RtU9XBlNXKpFwoFKKsrMzvamSUTM5kZ2sfNz7ewt+aQoze\nU+Ks1dVpbUAgszPxg+XhLdk9kQuAV+KcYN8PLMa5KeLDwHIAEdnoXqthMlAkEvGeKc9kYiYjqnz9\n3r3cv/vwWNmqmiBnrq7m3Jel/1txJmbiJ8vDW7KNyA7gN6p6xWiBiHwEWAe8Cvh34Ps4DY3JQNbf\nPVGmZRKJjnDpn/fyt6YQANVzivjBxrXUls3eyd1My8Rvloe3ZBuRtwPjbyJzJdCuqh8RkcuBT6e0\nZialmpub7UZy46QjkxFV9nSG6QoPExoYpmcwSjgSZSiqDA6PMBQdcX8qh8MRBoZHGIiMMBgdoaN/\nmMFhZ1yQM1dV8ZnTl6e0bsmwz0k8y8PbVK4TeQPxJ9ZfD7S6z0t58Up2k4HsuG6iVGQSHVGG3cfW\nphA3Pt5C0+HpD6m6dF4pJy6p4N3rF8y4btNhn5N4loe3ZBuRi3DG+ngSp4vvEpz7VL3ZnX4SzuEs\nk6EKC6c/UFGumkkm4UiUi259lqauxAajek4Ry+YFqSgtpKKkiGBxAYHCAkqK3EehECgqoKKkiDmB\nAkqLnEd5SREVpb4O8WOfk3EsD2/JXidyt4gcAbwOWAjcAfxeVTtGpwN3p62WZsa6u7upqqryuxoZ\nZSaZ3P50O01dAwhQVCgUFwjz5wbYdPR8GldVpfRWJLPJPifxLA9vU7lOpAO4Lo11MWlUV1fndxUy\nTjKZREeU5p5B9nQNsLdrgKdb+mg6HKa11zl6+x9nHcFJS3PnFnL2OYlneXhL9jqRIpzxOk4DaoGx\nS2VV9dT0VM2kUmdnJ3PmzPG7Ghllokz+caiXHS297A8Ncjg8zJMtvYQjIxMuf1R9GesXV8xGVWeN\nfU7iWR7ekt0T+S7wGpwBoy7F6dL7IeCGNNXLpJifwyBnqvGZPNHcy6d+/1zCfLVlxSyvKmVFVZAV\n1UFeNn8OVcFi5gRy73i5fU7iWR7ekm1ENgGvVNUXROQrqvpf7vjmP8YZ49xkONstTzSayfMd/dz6\nVDuPH3JuLbKiqpQ3HlVHZUkRa+rmMH9u/tyEzz4n8SwPb8k2InNwemUBhEVkjqruFJHj01Qvk2It\nLS153999aHiEZ9r76RuKMqJKS0sbNbW1XP3IIfaHnLv2zA0UcvnrV/veS8ov9jmJZ3l4S/Yv5Wng\nFcBDOLc6+bKIdAMH0lUxk1pz5871uwq++sehHi798166wsPjpvSOPfv4q5Zw/KLyvG1AwD4n41ke\n3pL9a/koEHWffwLnavVy4F/TUSljUmV4RNnZ2sfX3AZkcWUJCytKKBAYjkQoLSmhek4Rb3hZLcuq\nbAAiY6Yq2etE/h7z/DngzLTVyKRFb28vNTXj71yTuyLREW78Rys3/6OFfrd31dq6OXzvDWsoLHA6\nFzY1NdmhinHy7XPixfLwlvR+u4i8BngbzsWGB4EbVPWedFXMpFZ9fWqHUc10tz3dzrWPHAJgUUUJ\n6xeXc/5xDWMNCORfJsmwTOJZHt6SuqxWRD6J0523E/g90AH8yi03WaCtrc3vKswaVeXRAz0AnLO2\nhp+/5Ui2/NMSauYUx82XT5kkyzKJZ3l4S3ZP5BPAa1T1ydECEbkO+CPw7XRUzKRWOoZSzTTREeXn\nDx/ktqfbCUdGCBYXcP5xk3+TzIdMpsoyiWd5eJtKN5Rd437fDdiVOFmiurra7yqk1cDwCF+6ezeP\nHuwZK7volCUsKC+ZdJlcz2Q6LJN4loe3ZO8S92XgKhFZLSJBEVmDc/X6l0SkYPSRtlqaGcvl3fJI\ndIRL79nDowd7qAoW8aGTF3Hj24+mcdVL/wPI5UymyzKJZ3l4S3ZP5Mfuz7fh7H2M7uO9w50mbvmU\n7wMhIvOAn+LcWl6B9wLPADfiDL27F3iLqna5818CbMbpcnyRqt7llp8AXA0Ece4y/FG1exaMqajI\nrXs8gXM79v956CD37+6iZzBKRUkhl79uNUurkhuHPBczmSnLJJ7l4S3ZRmRFGuvwX8AfVPU8EQng\nXB3/OeAeVb1MRC4GLgY+KyJHAucDR+H0EvuTiKxR1SjOtSvvBx7EaUQ2AHemsd5ZJRqNes+UZX78\n4AHu2NkBQEN5gM+/ZkXSDQjkZiYzZZnEszy8JXudSFM6XlxEKoFTgXe7rzMEDInIRuB0d7ZrgPuA\nzwIbcboWDwJ7RGQXcKKI7AUqVHWbu95rgTdhjciYvr4+amtr/a5Gymw/0M0dOzsoLhC+fs4qjmko\nm/JJ0FzLJBUsk3iWhze/7++wAmgDfi4ixwKP4FwdX6+qh9x5moHRLjaLgG0xy+93yyLu8/HlCVpb\nW9m8eTNFRUVEo1E2bdrEli1baG5upqysjMLCQrq7u6mrq6OzsxNVpa6ujpaWlrFbIPT29lJfX09b\nWxsiQnV1NW1tbVRUVBCNRunr66OhoYHm5maKi4uprKykvb2dyspKhoaGCIfDY9MDgQDl5eV0dHRQ\nVVVFOBxmYGBgbHppaSnBYJCuri5qamro6elhaGhobHowGCQQCBAKhaitrSUUChGJRMamj25TNBql\nv78/K7ep6/BhWnsGadEy7n22jQN9IxwecL4hvuXoGqqjh9m/v2vK2xSNRhkcHMyo98nvz15sJrmy\nTTN5n0SEpqamnNqm6b5PkxE/TxuIyHqcRuEUVX1QRP4L6Ab+TVXnxczXpapVIvIDYJuq/sItvwpn\nb2MvcJmqnumWvxr4rKqeO/41t27dquvWrUv3pmWcbL06u2dwmC2/e4bmnqGEaesXl/MfZ62kqGB6\n3TCzNZN0skziWR6O7du3P9LY2Lh+oml+74nsB/ar6oPu7zfjnP9oEZEFqnpIRBYAre70Azjju49a\n7JYdcJ+PLzeu4uJi75kyTPfAMN974IWxBuTEJRUcu2Auxy4sp35ugIqSwhn148/GTNLNMolneXjz\ntRFR1WYR2Scia1X1GaAReMp9XAhc5v68xV3kVpwr5b+Dc2J9NfCQqkZFpFtETsY5sX4B8P1Z3pyM\nVlmZXUO4RkeUz9yxi92dYQKFwjfOWcVRDam9o2q2ZTIbLJN4loe3ZIfHXYEzouFxQNxfsqounWEd\n/g34pdszazfwHpzrV24Skc1AE/AW97V2iMhNOI3MMLDF7ZkFzvC9V+N08b0TO6kep729nbKyMr+r\nkRRV5brth9jdGaaurJhvvm4ViyqT73WVrGzKZLZYJvEsD2/J7on8Cnge+CTQn8oKqOpjwETH2hon\nmf9SnAZtfPnDONeamAlk+jeqrv4If917mOc7wjzV2kdT1wAAHzhpUVoaEMj8TPxgmcSzPLwl24gc\nhXPyeySdlTHpMzSUeGI6U+ztCvOvv94ZV1ZSKHzi1GWcekRV2l43kzPxi2USz/Lwlmwj8hfgeJwu\nuCYLhcNhv6swqcvvf/EypLcfV88xDXNZWzeHuSXpPWWXyZn4xTKJZ3l4S/avdC/wBxH5Lc51G2NU\n9YuprpReq6uZAAAfQUlEQVRJvYaGBr+rMKG/NR3muXbnD/Ub56zi+EXls/bamZqJnyyTeJaHt2Rv\nmlgG3A4U43SxHX0sfqmFTOZobm72nmmWqSr37uoCoH5uYFYbEMjMTPxmmcSzPLwle9uT96S7Iia9\nAoGA31WIcyA0wNWPHOL+PYcR4MuvTeft2SaWaZlkAssknuXhbSrD467GuYvvIpwL+a53x1s3WaC8\nfHa/5U8kEh3hnl1d3LGznZ1tTie/kqICLjljGStr5sx6fTIhk0xjmcSzPLwle53IG4Bf4hzSagLW\nAg+LyLtU9dY01s+kSEdHh+c9cNJpcHiED/12J/tDgwDMKS7gyPoy3rN+IatrZ78BAf8zyUSWSTzL\nw1uyeyJfAzaq6r2jBSJyOvADnKvITYarqkpfV9lkPLD3MPtDg1SWFvGvJy3k1SuqKC3ydxwzvzPJ\nRJZJPMvDW7J/xYuBv44rewA7sZ41/OyqeNtTbXzjPqcb7yuWVPDa1TW+NyBg3TcnYpnEszy8JfuX\n/BjO1eqxPuGWmywwMDAw66/ZMzjMx297lu//zblL/8vmz+Ftx9Z7LDV7/Mgk01km8SwPb8kezvoQ\ncJuIfBTYh9O9tx94Q7oqZlLLj/7ujx3sZUdLHwCnrpjH5xtnvwfWS7FrABJZJvEsD29J7Ymo6k7g\nZTg3Qvy2+/Nlqvp0GutmUsiP/u5d4QgAy+aVcskZy2f99b3YNQCJLJN4loe3pLv4quowznkQk4VK\nS9NzE8OJ3L+7i781hXh4fzcA56yroXCaA0el02xmki0sk3iWhze/B6UysyQYDM7aa136571jz9cv\nLmfjkXWz9tpTMZuZZAvLJJ7l4c3/LjJmVnR1daX9NV7oGmDL7168G++3Xr+a/zxrZUbuhcDsZJJt\nLJN4loc32xPJEzU1NWl/jR8/eGDsZooXn76Mly/I7Iu0ZiOTbGOZxLM8vCW1JyIi47v3Io6fpb5K\nJh16enrSuv4DoQH+7p4D+erZR/CaVdVpfb1USHcm2cgyiWd5eEv2cNYF7lC1gNOAANdhFxtmjXQP\nrvOk25X36IYyTlySHaPB2YBDiSyTeJaHt2QPZ20A7hORHuDXwPU4t4e360SyRLr6u0dHlMvu3cv9\new4DcEYaRyJMNbsGIJFlEs/y8JbsdSKHgLOBbwD34YwrslFVB9NXNZNK6ejvrqpcdOszYw3I0Q1l\nnL0me44h2zUAiSyTeJaHt0n3RETkvRMU/wZ4O/ALnENcqKqdF8kC6eiq+Nc9L45K+JqVVVx8xvKU\nv0Y6WffNRJZJPMvD20sdznrXJOU7gfPd5wpYI5IFUjm4zvCIctPjLVz9yCEAzlhZxWdPX5ay9c8W\nG3AokWUSz/LwNmkjoqpnzGZFTHqFQiHmzZs3o3X0DUW5+YlWbn+6ndDAMABVwSL+7Z8W4/S1yC6p\nyCTXWCbxLA9vU7pORETmA3Gd/1V1d0prZNKitrZ2RsuHI1G+cNfzY72wAC48YQH/csz8jLit+3TM\nNJNcZJnEszy8JTuy4QbgKmDBuEkKFKa6Uib1QqEQZWVl01p2cHiEd1y/g96hKHOKC/jAyYs5cUkF\nNXOKU1zL2TWTTHKVZRLP8vCW7FfIHwL/CZSpakHMIyUNiIgUisijInK7+3u1iPxRRJ5zf1bFzHuJ\niOwSkWdE5OyY8hNE5Al32hWSjcdX0igSiUx72b/uOUzvUBSAr569knPW1mR9AwIzyyRXWSbxLA9v\nyTYiVcCPVTVdw3x9FIi9rfzFwD2quhq4x/0dETkS56T+UTjXrvxIREYbsiuB9wOr3ceGNNU1K82k\nv/vfmkIAvHrFPI5uyOxbmUyFXQOQyDKJZ3l4S7YRuQp4TzoqICKLgdcDP40p3ghc4z6/BnhTTPkN\nqjqoqnuAXcCJIrIAqFDVbaqqwLUxyxhm1t99dFyQbLqQMBl2DUAiyySe5eEt2RPrJwMXicjFQFyq\nqnrqDOvwPeAzQHlMWb17gSPu642OqboI2BYz3363LOI+H1+eoLW1lc2bN1NUVEQ0GmXTpk1s2bKF\n5uZmysrKKCwspLu7m7q6Ojo7O1FV6urqaGlpYe5c51t4b28v9fX1tLW1ISJUV1fT1tZGRUUF0WiU\nvr4+GhoaaG5upri4mMrKStrb26msrGRoaIhwODw2PRAIUF5eTkdHB1VVVYTDYQYGBsaml5aWEgwG\n6erqoqamhp6eHoaGhsamB4NBAoEAoVCI2tpaQqEQkUhkbProNvX399Pf3z/lbYoWz+Gplj6KBI6s\nDdDU1JQx2zTT96m/v5/BwcGMep/8/uzFZpIr2zST92lwcJCmpqac2qbpvk+TEeeL+0sTkQsnm6aq\n10w2LYn1ngu8TlU/LCKnA59S1XNF5LCqzouZr0tVq0TkB8A2Vf2FW34VcCewF7hMVc90y18NfFZV\nzx3/mlu3btV169ZNt8pZq6uri6qqqe9J/PbJVq7cdoBXLqvkK689Ig018890M8lllkk8y8Oxffv2\nRxobG9dPNC2pPZGZNBQeTgHeKCKvA0qBChH5BdAiIgtU9ZB7qKrVnf8Azvjuoxa7ZQeIvxnkaLlx\ndXd3T+mPITQwzG1Pt3Ote0HhKcuy46aKUzHVTPKBZRLP8vD2ko2IiLzGawWq+ufpvriqXgJc4r7W\n6Th7Iu8UkcuBC4HL3J+3uIvcCvxKRL4DLMQ5gf6QqkZFpFtETgYeBC4Avj/deuWiurrkRxds6xvi\n47c9S2uvcy7kiOogr1hcka6q+WYqmeQLyySe5eHNa0/kKo/pCqTjGMdlwE3u7eebgLcAqOoOEbkJ\neAoYBraoatRd5sPA1UAQ5xDXnWmoV9bq7Oxkzpw5Sc3739sO0NobYem8Ura8cjHHLZyblVeke5lK\nJvnCMolneXh7yUZEVVfMVkVU9T6cOwSjqh1A4yTzXQpcOkH5w8DR6athdvM697WnM8wzbf38fmc7\nz7T1A3DJGctYWZO7f0DJnA/MN5ZJPMvDmw2Pmycm2i1XVX6/s4PbnmpjT9dA3LQzV1XldAMCdqhi\nIpZJPMvDmzUieaKlpYUlS5fy+MFeDvYM0tUfYWdbPw/tc4a0LS8pZP3iCubPDXBEdSlnrMz84W1n\nqqWlhWXLsu/uw+lkmcSzPLxZI5InIkVBLrzxKVp644f7LC0q4KJTlnDaEfMoLszOGylOl1f/93xk\nmcSzPLxZI5InfrK9Y6wBOWt1NTVlxVQFi3nF4goWVZb4XDtjTLayRiQPjKiy97AzkvEHT17EpqPn\n+1yjzNDb20tNTfYM5zsbLJN4loe3/Dp+kacePdDD4UGlvKSQjUfaicJR9fX13jPlGcsknuXhzRqR\nHHc4HOGSPzwPwDlraygsyL3rPaarra3N7ypkHMsknuXhzRqRHPfrJ50/gkChMxKheVEuXkA5U5ZJ\nPMvDm50TyVGqyj27urjx8RYAPviKhrzrfeWlujr3uzFPlWUSz/LwZv9VctT/PtHKN+9vAmBhRYBj\nyoc8lsg/dqgikWUSz/LwZnsiOah7YHhsDwTgc69ZQUXhoI81ykwVFbl3U8mZskziWR7erBHJQb98\nrJmewShHVAe5YuMaAoUFtLf3+12tjBONRr1nyjOWSTzLw5sdzsoxuzvC3LLD2QX/6KuWEHDPg/T1\n9flZrYxkmSSyTOJZHt6sEckxf9/fzYjCK5dW8rL5ZWPlDQ0NPtYqM1kmiSyTeJaHN2tEckhb3xC/\nfdIZBPLUI+bFTWtubvajShnNMklkmcSzPLzZOZEc0NEX4ScPHeD+3V2MKKypncPpR8QP6VlcXOxT\n7TKXZZLIMolneXizRiSLqSo/f/gQN8T0xKorK+aSM5YnXJleWZl7Y6TPlGWSyDKJZ3l4s0Yki123\nvXmsAVk2r5S3H1/PqSuqJry1SXt7O2VlZQnl+cwySWSZxLM8vFkjkqVae4f4wzMdAJywqJyvn7Pq\nJee3b1SJLJNElkk8y8ObNSJZZHdHmF8+1szTrX109kcYUXdQqVct8Vx2aMiuWB/PMklkmcSzPLxZ\nI5Ilbn2qjR9t3c+IOr8XCJy2Yh7vXr+ABeXeg0qFw+E01zD7WCaJLJN4loc3a0SyQHvfED/4234A\nXr+uhn8+aj4LKgJTuqGi9XdPZJkkskziWR7e7DqRDNcVjvC+m58GYHVtkI++ailLq0qnfEde6++e\nyDJJZJnEszy8WSOS4S6/v4n+yAjFBTKj8UACgUAKa5UbLJNElkk8y8ObHc7KYHu7wjy8v4c5xQVc\ndd6R1JRN/8Kn8vLyFNYsN1gmiSyTeJaHN1/3RERkiYjcKyJPicgOEfmoW14tIn8Ukefcn1Uxy1wi\nIrtE5BkROTum/AQRecKddoXkwJBkdz/bCcDpK6tm1IAAdHR0pKJKOcUySWSZxLM8vPl9OGsY+KSq\nHgmcDGwRkSOBi4F7VHU1cI/7O+6084GjgA3Aj0Sk0F3XlcD7gdXuY8NsbkgqHQ5H+NNzndz8hHMf\nrLPX1Mx4nVVVVd4z5RnLJJFlEs/y8OZrI6Kqh1R1u/u8B3gaWARsBK5xZ7sGeJP7fCNwg6oOquoe\nYBdwoogsACpUdZuqKnBtzDJZ5+I7d42NSrigPMC6ujkzXqd1VUxkmSSyTOJZHt4y5pyIiCwHjgce\nBOpV9ZA7qRmod58vArbFLLbfLYu4z8eXJ2htbWXz5s0UFRURjUbZtGkTW7Zsobm5mbKyMgoLC+nu\n7qauro7Ozk5Ulbq6OlpaWpg7dy4Avb291NfX09bWhohQXV1NW1sbFRUVRKNR+vr6aGhooLm5meLi\nYiorK2lvb6eyspKhoSHC4fDY9EAgQHl5OR0dHQwH5vI/D7eyu3MAgDetCnLG8gp6enro6uqipqaG\nnp4ehoaGxpYPBoMEAgFCoRC1tbWEQiEikcjY9NFtamlpoby8fNa3qaqqinA4zMDAwNj00tJSgsHg\njLdppu9TS0sL8+bNy6ltmun7FJtJrmzTTN6ntra2uOVzYZum+z5N+r/b+eLuLxGZC9wPXKqqvxGR\nw6o6L2Z6l6pWicgPgG2q+gu3/CrgTmAvcJmqnumWvxr4rKqeO/61tm7dquvWrUv/Rk3RD/+2j1ue\nah/7/VXLK/nimUekbP2Dg4OUlHhflJhPLJNElkk8y8Oxffv2RxobG9dPNM3vcyKISDHwa+CXqvob\nt7jFPUSF+7PVLT8AxN7jY7FbdsB9Pr48K9z8j5axBqS8pJCvbVjJ5xtXpPQ1rL97IsskkWUSz/Lw\n5nfvLAGuAp5W1e/ETLoVuNB9fiFwS0z5+SJSIiIrcE6gP+Qe+uoWkZPddV4Qs0xGC0ei/OShg4Bz\nMeGv3/Vy1i+uoCDFnctKS0tTur5cYJkkskziWR7e/D4ncgrwLuAJEXnMLfsccBlwk4hsBpqAtwCo\n6g4RuQl4Cqdn1xZVjbrLfRi4GgjiHOK6c7Y2YiYud0+glwUKueKNa9P2OsFgMG3rzlaWSSLLJJ7l\n4c3XRkRVHwAm+8rdOMkylwKXTlD+MHB06mqXfo8f7OGBvSEA3nF8w4TjgKRKV1cXFRUVaVt/NrJM\nElkm8SwPb76fE8lnt+90zoMUCLzxyNq0vlZNzcyvNck1lkkiyySe5eHNGhGfqCrPtTt90K9441oC\nU7yh4lT19PSkdf3ZyDJJZJnEszy8WSPikwf2hjjYPUh5SSErqtN/8s4G10lkmSSyTOJZHt78PrGe\ndzr6Ily7/RB3ukPbvvuEBVO+rft02LgIiSyTRJZJPMvDm+2JzCJV5d/ven6sATmiOsjr1qX3XMgo\n6++eyDJJZJnEszy82Z7ILHqqtY/dnc55kA+dvIjGVdVp7ZEVy7oqJrJMElkm8SwPb9aIzJL2viEu\nvmMXAOsXl/PPR8+f1de3wXUSWSaJLJN4loc3O5w1S659pJnBqLK8qpQPnrzYe4EUC4VCs/6amc4y\nSWSZxLM8vNmeyCzY2hTiD892UCDwxTNXsLhy9m+lUFs7O+desollksgyiWd5eLM9kVlwh3tR4Zra\nOb40IGDfqCZimSSyTOJZHt5sTySNRlS585kOHtzXDcBFpyzxWCJ9IpGIb6+dqSyTRJZJPMvDmzUi\nafLgCyEuv7+J7kHn/pBHzi9jRbV/PT2sv3siyySRZRLP8vBmh7PS5I6dHWMNyGtXV/OdN6yete68\nE7H+7oksk0SWSTzLw5vtiaRY7+AwX7t3Lw/vd+65841zVnH8onKfawVlZWV+VyHjWCaJLJN4loc3\na0RSaGtTiCv+bx8d/c5x1LcdV8+xC196fOLZUlhY6HcVMo5lksgyiWd5eLPDWSnS2R/hK3/aTUd/\nhJJC4ZIzlvOe9QtTPkLhdHV3d/tdhYxjmSSyTOJZHt5sTyRFbn+6nRGFipJCrjv/KILFmfUNpq6u\nzu8qZBzLJJFlEs/y8GZ7Iinyv0+0AvDRVy3NuAYEoLOz0+8qZBzLJJFlEs/y8GaNSAo8eqCHweER\nAF61vNLn2kxMVf2uQsaxTBJZJvEsD2/WiKTAjtY+AKrnFCEZcg5kPNstT2SZJLJM4lke3qwRmYGe\nwWHu2NnOrTvaAHjfKxb5XKPJtbS0+F2FjGOZJLJM4lke3uzE+jQ939HP5/7wPF3hYQBeNn8Op66Y\n53OtJjd3bmZ0Nc4klkkiyySe5eHNGpFpePxgD592xwYB+NeTFrHxyNpZGebWGGMyif3Xm6LoiPLD\nrfsBqAoWcf3bjua8Y+ZnfAPS29vrdxUyjmWSyDKJZ3l4sz2RKQhHonz1nr3s7RoA4CuvPYKasmKf\na5Wc+vp6v6uQcSyTRJZJPMvDW2Z/fZ4iEdkgIs+IyC4RuTiV6x4aHuE9//sUf9/fTWlRAe9Zv4C1\ndXNS+RJp1dbW5ncVMo5lksgyiWd5eMuZPRERKQR+CLwW2A/8XURuVdWnZrrujv4I37hvL539w5QU\nFfC9N6zhiBr/bus+HZna9dhPlkkiyySe5eEtl/ZETgR2qepuVR0CbgA2pmLFz3f089hB59joB05a\nlHUNCEB1dbXfVcg4lkkiyySe5eEtZ/ZEgEXAvpjf9wMnjZ+ptbWVzZs3U1RURDQaZdOmTWzZsoXm\n5mbKysooLCyku7uburo6Ojs7UVWOqa1lw/JSTlsxj+XzhKamJurr62lra0NEqK6upq2tjYqKCqLR\nKH19fTQ0NNDc3ExxcTGVlZW0t7dTWVnJ0NAQ4XB4bHogEKC8vJyOjg6qqqoIh8MMDAyMTS8tLSUY\nDNLV1UVNTQ09PT0MDQ2NTQ8GgwQCAUKhELW1tYRCISKRyNj00W3at28fq1atGtumuro6Wlpaxrow\n9vb2Zt02jX+fprpNBw8eZO3atTm1TTN9n1paWsYyyZVtmsn7tGfPHsrLy3Nqm6b7Pk1GcuWyfhE5\nD9igqu9zf38XcJKqfiR2vq1bt+q6dev8qKKvurq6qKqq8rsaGcUySWSZxLM8HNu3b3+ksbFx/UTT\nculw1gEgdhDzxW6ZAaLRqN9VyDiWSSLLJJ7l4S2XGpG/A6tFZIWIBIDzgVt9rlPG6Ovr87sKGccy\nSWSZxLM8vOXMORFVHRaRjwB3AYXAz1R1h8/VyhgNDQ1+VyHjWCaJLJN4loe3XNoTQVXvUNU1qrpS\nVS/1uz6ZpLm52e8qZBzLJJFlEs/y8JZTjYiZ3O9+9zu/q5BxLJNElkk8y8ObNSJ54je/+Y3fVcg4\nlkkiyySe5eHNGpE8MTw87HcVMo5lksgyiWd5eMuZ60SSdc8997QBTX7XY7Z1dnbWVldXt/tdj0xi\nmSSyTOJZHmOWNTY2TjjMY941IsYYY1LHDmcZY4yZNmtEjDHGTJs1IsYYY6bNGpEsJiI/E5FWEXky\npqxaRP4oIs+5P6tipl3iDtj1jIicHVN+gog84U67QrJ0EAURWSIi94rIUyKyQ0Q+6pbncyalIvKQ\niDzuZvIVtzxvMwFn/CEReVREbnd/z+s8ZkRV7ZGlD+BU4P8BT8aUfRO42H1+MfAN9/mRwONACbAC\neB4odKc9BJwMCHAncI7f2zbNPBYA/899Xg486253PmciwFz3eTHwoLtdeZuJuy2fAH4F3O7+ntd5\nzORheyJZTFX/AnSOK94IXOM+vwZ4U0z5Dao6qKp7gF3AiSKyAKhQ1W3q/GVcG7NMVlHVQ6q63X3e\nAzyNM85MPmeiqtrr/lrsPpQ8zkREFgOvB34aU5y3ecyUNSK5p15VD7nPm4F69/lEg3Ytch/7JyjP\naiKyHDge55t3XmfiHrp5DGgF/qiq+Z7J94DPACMxZfmcx4xYI5LD3G9IeXchkIjMBX4NfExVu2On\n5WMmqhpV1eNwxtg5UUSOHjc9bzIRkXOBVlV9ZLJ58imPVLBGJPe0uLvauD9b3fLJBu064D4fX56V\nRKQYpwH5paqO3vgorzMZpaqHgXuBDeRvJqcAbxSRvcANwGtE5Bfkbx4zZo1I7rkVuNB9fiFwS0z5\n+SJSIiIrgNXAQ+4ufLeInOz2LrkgZpms4tb/KuBpVf1OzKR8zqROROa5z4PAa4Gd5GkmqnqJqi5W\n1eU4A9f9WVXfSZ7mkRJ+n9m3x/QfwPXAISCCc0x2M1AD3AM8B/wJqI6Z/99xepc8Q0xPEmA98KQ7\n7Qe4t8PJtgfwKpzDEP8AHnMfr8vzTF4OPOpm8iTwRbc8bzOJ2Z7TebF3Vt7nMd2H3TvLGGPMtNnh\nLGOMMdNmjYgxxphps0bEGGPMtFkjYowxZtqsETHGGDNt1ogYk2VE5MvuBXLJzq8issp9/t8i8oX0\n1c7kG2tEjK9EZK+IhEWkV0RaRORq97YlGUtETheR/d5zZh5V/aCq/qfXfO77cuZs1MlkN2tETCZ4\ng6rOxbmt/Xrg8z7XZ8ZEpMjvOhgzG6wRMRlDVQ/gjMtwNICIVIrIVSJySEQOiMhXRaTQnfZuEXlA\nRL4lIl0iskdEzpls3eMPAYnIcvcwT5H7+30i8nV3AKduEblFRKonWE+ZW8eF7t5Tr4gsdNd/s4j8\nQkS6gXeLyIkislVEDrvb8AMRCbjruVJEvjVu3beIyCfc5wtF5Nci0uZu20XJ5igin3Zf76CIvHfc\ntKtF5Kvu81oRud2tX6eI/FVECkTkOmApcJu7fZ9x5/9fEWkWkZCI/EVEjhq33h+KyO9FpEdEHhSR\nlTHTjxJnsKdOd4/zc255gYhcLCLPi0iHiNw0Ue4mc1kjYjKGiCzBuU3Jo27R1cAwsArntu5nAe+L\nWeQknFtR1OIMKnSVex+j6boAeC/O4FbDwBXjZ1DVPuAc4KCqznUfB93JG4GbgXnAL4Eo8HG3fq8E\nGoEPu/NeD7x1tL7ijKR3FnCDiBQAt+EMhrTIXe5jEjOq3mREZAPwKZx7ZK0GXuqQ1CdxbpdTh3Pr\n8885m6jvAl7A3UNU1W+689/prnM+sN3dxljnA18BqnDG3bjUrVM5zq1E/gAsxHk/73GX+TeccThO\nc6d1AT/02k6TOawRMZngdyJyGHgAuB/4mojU4zQoH1PVPlVtBb6L849qVJOq/o+qRnEGElrAi+NA\nTMd1qvqk21B8AXjL6J5Pkraq6u9UdURVw6r6iDqDFg2r6l7gxzj/LAH+inOfr1e7v5/nLn8QeAVQ\np6r/oapDqrob+B/it30ybwF+HrMdX36JeSM4mS1T1Yiq/lVf4j5IqvozVe1R1UF3vceKSGXMLL9V\n1YdUdRingTnOLT8XaFbVb6vqgLuOB91pHwT+XVX3x6z3PDscmD3sjTKZ4E2q+qfYAhE5BmcUvkMx\nOxcFxA8Q1Dz6RFX73fnmisircb41g9PQHEVyYtfd5L5+LdAyjeURkTXAd3DO88zB+Xt7xK2visgN\nwNuAvwBvB0YPty3DOVx2OGZ1hTgNj5eFo68Rsx2TuRznn/bdbnY/UdXLJprRbUwvBd6Ms+cyOqBT\nLRBynzfHLNIPjHaQWIJzk8KJLAN+KyKxA0RFcb4M5OWt1bON7YmYTLUPGARqVXWe+6hIpkFwv1GP\nHmoanb8P5x/5qIYJFo0dN2Ipzjf19oleYrKXHvf7lTi3XV+tqhU4h4tiD7ddj/OtexnOoblfu+X7\ngD0x2z1PVctV9XWTvG6sQxNsx8SVdfYIPqmqRwBvBD4hIo2TbMvbcQ7XnQlUAsvd8mQOH+4DjniJ\naeeM29ZS9/yYyQLWiJiMpM54DXcD3xaRCvcE7EoROc1r2Uk8BpwqIkvdQzCXTDDPO0XkSBGZA/wH\ncLN7qGy8FqBm3KGciZQD3UCviKwDPhQ7UVUfxWmkfgrcpc6gUQAPAT0i8lkRCYozvO3RIvKKJLbz\nJpyT+qPb8aXJZhSRc0VklXteJoSzBzC6R9BC/D/+cpxGvQOnMf5aEnUZdTuwQEQ+Js64HOUicpI7\n7b+BS92GdHT8k41TWLfxmTUiJpNdAASAp3BOuN6Mcwx/ylT1j8CNOONqPILzj22863BO5jcDpcCE\nPaJUdSfOXsRut2fTwkle9lM43+B7cM5p3DjBPL/C+Xb/q5j1R3HOIxwH7OHFhsar0UJV78QZQ/zP\nOCe3//wSs6/GOeHdC2wFfqSq97rTvg583t2+TwHX4hwaO4DzfmzzqktMnXpwTvS/ASfb54Az3Mn/\nhTPw090i0uOu96SJ1mMyk40nYgxOF1/gF6r6U7/rYkw2sT0RY4wx02aNiDHGmGmzw1nGGGOmzfZE\njDHGTJs1IsYYY6bNGhFjjDHTZo2IMcaYabNGxBhjzLT9fwr7IL/S2HBvAAAAAElFTkSuQmCC\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ink_transit_curve(greedy_route)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the solution that this produces starts out pretty efficient with transit distance: the slope of the line is initially about 3:1, indicating that the pen is down three times as often as it is up. However, notice that the efficiency tails off towards the end of the plot: adding the last bits of ink to the page requires thrashing around the drawing area to visit all the islands we've neglected. Let's see if we can improve on this." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A More Optimal Approach\n", "\n", "As hinted earlier, the optimal plotting problem is tantalizingly similar to the [Traveling Salesman Problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) (TSP), which looks for the shortest path that makes a complete cycle through a graph visiting every node exactly once. In fact, it can be reduced to a TSP by constructing the graph differently. My initial approach was to construct the problem as a TSP and throw [or-tools](https://github.com/google/or-tools) package's TSP solver at it. This turned out to be a false path, though, because the construction I came up with didn't optimize well.\n", "\n", "Then I started digging through the code and noticed that the solver allows [disjunction](https://github.com/google/or-tools/blob/master/ortools/constraint_solver/routing.h#L510) constraints. This means that you can give it a pair (or more) of nodes and tell the solver to generate a route that visits *exactly one* of them. Another important feature of or-tools is that the distance function can be asymmetric. We need this since the distance function we are optimizing is the *end* of the first path to the *start* of the second, which is different if we swap the order.\n", "\n", "What this all adds up to is that we can use the same graph structure as for the greedy approach, and as long as we make sure to tell the solver that the pairs of paths are mutually exclusive, it *just works!*" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from ortools.constraint_solver import pywrapcp\n", "from ortools.constraint_solver import routing_enums_pb2\n", "from time import time" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def vrp_solver(path_graph, initial_solution=None, runtime_seconds=60):\n", " \"\"\"Solve a path using or-tools' Vehicle Routing Problem solver.\n", " Params:\n", " path_graph the PathGraph representing the problem\n", " initial_solution a solution to start with (list of indices, not\n", " including the origin)\n", " runtime_seconds how long to search before returning\n", " \n", " Returns: a (solution, curve) pair where the solution is a list of\n", " indices and the curve is a list of (clock seconds, best solution)\n", " pairs representing the progress of the solution over time.\n", " \"\"\"\n", " # Create the VRP routing model. The 1 means we are only looking\n", " # for a single path.\n", " routing = pywrapcp.RoutingModel(path_graph.num_nodes(),\n", " 1, path_graph.ORIGIN)\n", " \n", " # For every path node, add a disjunction so that we do not also\n", " # draw its reverse.\n", " for disjunction in path_graph.iter_disjunctions():\n", " routing.AddDisjunction(disjunction)\n", " \n", " # Wrap the distance function so that it converts to an integer,\n", " # as or-tools requires. Values are multiplied by COST_MULTIPLIER\n", " # prior to conversion to reduce the loss of precision.\n", " COST_MULTIPLIER = 1e4\n", " def distance(i, j):\n", " return int(path_graph.cost(i,j) * COST_MULTIPLIER)\n", " routing.SetArcCostEvaluatorOfAllVehicles(distance)\n", " \n", " # In order to produce a graph of the search progress, keep track\n", " # of the start time and (time, value) pairs.\n", " start_time = time()\n", " solution_curve = list()\n", " def record_solution():\n", " t = time() - start_time\n", " solution_curve.append((t, routing.CostVar().Max() / COST_MULTIPLIER))\n", " routing.AddAtSolutionCallback(record_solution)\n", " \n", " # If we weren't supplied with a solution initially, construct one by taking\n", " # all of the paths in their original direction, in their original order.\n", " if not initial_solution:\n", " initial_solution = [i for i, _ in path_graph.iter_disjunctions()]\n", " \n", " # Compute the cost of the initial solution. This is the number we hope to\n", " # improve on.\n", " initial_assignment = routing.ReadAssignmentFromRoutes([initial_solution],\n", " True)\n", " print('Initial distance:',\n", " initial_assignment.ObjectiveValue() / COST_MULTIPLIER)\n", " \n", " # Set the parameters of the search.\n", " search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()\n", " search_parameters.time_limit_ms = runtime_seconds * 1000\n", " search_parameters.local_search_metaheuristic = (\n", " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", " \n", " # Run the optimizer and report the final distance.\n", " assignment = routing.SolveFromAssignmentWithParameters(initial_assignment,\n", " search_parameters)\n", " print('Final distance:', assignment.ObjectiveValue() / COST_MULTIPLIER)\n", " \n", " # Iterate over the result to produce a list to return as the solution.\n", " solution = []\n", " index = routing.Start(0)\n", " while not routing.IsEnd(index):\n", " index = assignment.Value(routing.NextVar(index))\n", " node = routing.IndexToNode(index)\n", " if node != 0:\n", " # For compatibility with the greedy solution, exclude the origin.\n", " solution.append(node)\n", " return solution, solution_curve" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll prime the solver with the greedy solution to speed things up. It might seem counter-intuitive that the solver *wouldn't* start by doing its own greedy search, but remember that we had to use the raw coordinates in order to build the R-tree to run the greedy algorithm efficiently. The solver doesn't have access to the raw coordinates, since it is generalized to problems that don't use Euclidean distance." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Initial distance: 4739.4783\n", "Final distance: 4127.1414\n" ] } ], "source": [ "vrp_solution, curve = vrp_solver(path_graph, greedy_solution, runtime_seconds=1200)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the distances here will be slightly off, due to the loss in precision that comes with converting to integers. To get the actual figure (and also verify that the solution is valid), we can use the same methods as before:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4127.194508693134" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vrp_route = get_route_from_solution(vrp_solution, path_graph)\n", "vrp_cost = cost_of_route(vrp_route)\n", "vrp_cost" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.8708026221231636" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vrp_cost / greedy_cost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, there is a real improvement over the greedy algorithm, even if it's not on the same order as the big improvement we got from the greedy approach.\n", "\n", "Here's the result, visualized:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "\t\n", "" ], "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "visualize_pen_transits(vrp_route)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting the Optimization Curve\n", "\n", "In addition to returning the solution, the `vrp_solver` function returns a list of the costs of solutions it found during the optimization process. Visualizing it gives us an indicator of whether we would benefit from increasing the runtime." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": true }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYsAAAEGCAYAAACUzrmNAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztvXuUW2d58Pt7dBtprp6LPGN7HDtJE0wSSmhCSEqBpG4h\nJTkE3BZCVwgQl9LG5fD1cqBue77Sj6ahVy6Lkn6nQLkESNMWSJoSIBAC9Fs2gThproQ4sY2deGY0\nF2tumpFG85w/3q2JPJnZo9GMtDXS81tLS9K7t7Sf/dO2n3n3exNVxTAMwzD8CAUdgGEYhlH7WLIw\nDMMwVsSShWEYhrEiliwMwzCMFbFkYRiGYayIJQvDMAxjRSJBB1AJ7rvvPm1qago6DMMwjA3F9PT0\n8O7du5NLbavLZNHU1MSuXbtW3O/YsWPs2LGjChHVLubAHBQwD+bg0KFDx5bb1tC3oaLRaNAhBI45\nMAcFzIM58KOhk0VHR0fQIQSOOTAHBcyDOfCjoZPF8PBw0CEEjjkwBwXMgznwo6GThf0VYQ7AHBQw\nD+bAj4ZOFtlsNugQAsccmIMC5sEc+NHQySKTyQQdQuCYA3NQwDyYAz/qsutsqfT19Z32/vipGUan\nc7Q1RehIRGhvChMN13c+XeygETEHDvNgDvxo6GQxMDBwWp/qrz6W4j+eOL2BqyUWpiMeoSNeeD79\nsSkR4fzeVlpi4WqHvy4sdtCImAOHeTAHfjR0sojFYqe97+9o4oK+FiZm86Qzc4zPzjGVzTOVzfPc\n+PLf094U5g9evYOzuxNsbo0tv2MNsthBI2IOHObBHPjR0Mmira3ttPdvumAzb7pg88L7eVUmZ/Ok\nZ+ZIz8xxamaO8aLXQxNZ/s+xNOOzef7snmfYFI/wz28+b0PVMhY7aETMgcM8mAM/GjpZjIyM0Nra\nuuz2kAjt8Qjt8Qjbl9nn24dH+f6RUzw9kmFwMstXHh3iup/bUpmAK8BKDhoBc+AwD+bAj/puvV2B\nzs7ONX/H7p/p4gO/fBbveWU/AD847nO/qgZZDwcbHXPgMA/mwI+GThbr2U2u12ureDI1TTY/v27f\nW2msq6A5KGAezIEfDZ0sZmZm1u27etuenxL90YHJdfveSrOeDjYq5sBhHsyBHw2dLNazT3U8EuKy\nHW6qgPTM3Lp9b6WxfuXmoIB5MAd+NHSyGBgYWNfva29yvaBmchvnNtR6O9iImAOHeTAHfjR0sojH\n4+v6fYmoSxaZuY2TLNbbwUbEHDjMgznwo6rJQkTCIvKgiNzlvf8XEXnIexwVkYeK9t0vIodF5EkR\neV1R+UUi8oi37WMiIuXGk0gk1nZCi4hHnM7hqdy6fm8lWW8HGxFz4DAP5sCPatcs3gs8UXijqm9R\n1QtV9ULg34EvA4jIecC1wPnAlcAnRKQw0u0W4F3AOd7jynKDGRsbK/ejS3JushmAew+Pruv3VpL1\ndrARMQcO82AO/KhashCRfuAq4JNLbBPgzcCXvKJrgNtUdVZVjwCHgUtEZAvQrqoHVVWBzwFvLDem\n7u7ucj+6JJf0twMwPptf1++tJOvtYCNiDhzmwRz4Uc0R3B8B3gcsNZ7+VcCgqj7lvd8GHCzafsIr\ny3mvF5efxtDQEHv37iUSiZDP59mzZw/79u1jYGCAlpYWwuEw4+Nu8NypU6dQVZLJJIODgwujNycn\nJ+nt7SWVSiEidHV1kUqlaG9vJ5/PMzU1RV9fHwMDA0SjUTo6Ojg5lAJAVTl27NjC9lgsRltbGyMj\nI3R2dpLJZJiZmVnYHo/HSSQSjI2N0d3dzcTEBNlsdmF7IpEgFouRTqfp6ekhnU6Ty+UWthefUzKZ\nZHR0tORzOnbsGFu3bl32nIaHh+no6CCbzZLJZDbEOa30Oy0+p1AoxMjISF2dUzm/k6rW3Tmt9nc6\nduwYvb29dXVOq/md/BD3B3plEZGrgder6o0icjnwh6p6ddH2W4DDqvp33vuPAwdV9Vbv/aeAu4Gj\nwIdU9Ze88lcB7y/+LoADBw7orl27Vozr2LFj6zrDZC4/z1X//N8A/N3V59DbGqO7OUo4VHazSsVZ\nbwcbEXPgMA/m4NChQw/s3r374qW2Vatm8UrgDSLyeiAOtIvIrap6nYhEgD3ARUX7PwunTcfU75U9\n671eXF4W692nOhwSeltjDE5m+YO7XCUpJNCViJJsjZJsiZFsiXLGpji7z+kiVgNrZVi/cnNQwDyY\nAz+q8r+Vqu5X1X5V3YlruL5XVa/zNv8S8GNVLb69dCdwrYg0iciZuIbs+1X1JDAuIpd67RzXA3eU\nG9d696kOifDB153FFWd38qJkM13NEVRheDrHE0PTfO/IKf790RQf/q/jfP/IqXU9drlYv3JzUMA8\nmAM/amHW2Wt5vmEbAFV9TERuBx4H5oB9qlpoNb4R+AyQwN2aurvcA1eim9zOzgT7r9i58D6Xn2dk\nOkdqKsfQZJa7fzzCwwOTHB2rjWkFrKugOShgHsyBH1VPFqp6H3Bf0ft3LLPfTcBNS5T/CLhgPWKp\nxkIn0XCIvrYm+ry5o34yPM3DA5OcHJ+t+LFLwRZ7MQcFzIM58CP4m+YBkk6nq37Mbe0uadRKk3cQ\nDmoNc+AwD+bAj4ZOFj09PVU/ZmHZ1VqZEiQIB7WGOXCYB3PgR0MniyD+ikh4U4JkamSyQftLyhwU\nMA/mwI+GTha5XPXncFqYbDBXG6O8g3BQa5gDh3kwB340dLIIok91POqUz9TIbSjrV24OCpgHc+BH\nQyeLIPpUJ6K1dRvK+pWbgwLmwRz40dDJoqWlperHfL7NojZuQwXhoNYwBw7zYA78aOhkEQ6HV95p\nnSm0WczMzVONeblWIggHtYY5cJgHc+BHQyeLwsyz1SQcEuKREPMKqRpYJCkIB7WGOXCYB3PgR0Mn\ni2QyGchxe9vcWIujY5lAjl9MUA5qCXPgMA/mwI+GThajo8GsaHdWl5t/ZnR6LpDjFxOUg1rCHDjM\ngznwo6GTRVBtBts73JQfR2qgZlEL7SZBYw4c5sEc+NHQySKoKmdhUsGx6eDbLKzabQ4KmAdz4EdD\nJ4vBwcFAjtsedz0uJmpgre6gHNQS5sBhHsyBHw2dLEpZd7YStMRcspjKBp8sgnJQS5gDh3kwB340\ndLIIilpKFoZhGKXQ0MlicnIykOMuJIsaGMUdlINawhw4zIM58KOhk0Vvb28gx22JFmoWwc8PFZSD\nWsIcOMyDOfCjoZNFKpUK5LiJaIiQwOzcPHPzwXbVC8pBLWEOHObBHPjR0MlCJJjFTUWkZtotgnJQ\nS5gDh3kwB340dLLo6uoK7NjN0dpIFkE6qBXMgcM8mAM/GjpZBFnlrJWahVW7zUEB82AO/GjoZNHe\n3h7YsWslWQTpoFYwBw7zYA78qGqyEJGwiDwoIncVlb1HRH4sIo+JyF8Xle8XkcMi8qSIvK6o/CIR\necTb9jFZw03GfD64/6hbYk590MkiSAe1gjlwmAdz4Ee1axbvBZ4ovBGRK4BrgJeq6vnA33rl5wHX\nAucDVwKfEJHCqiS3AO8CzvEeV5YbzNTUVLkfXTO1UrMI0kGtYA4c5sEc+FG1ZCEi/cBVwCeLin8H\n+JCqzgKo6pBXfg1wm6rOquoR4DBwiYhsAdpV9aC66SE/B7yx3JiCXJy9VpKFLVBvDgqYB3PgRzVr\nFh8B3gcUj0Q7F3iViPxARL4rIi/3yrcBx4v2O+GVbfNeLy4viyAXZ6+VZGEL1JuDAubBHPgRqcZB\nRORqYEhVHxCRyxcdvwu4FHg5cLuInLXW4w0NDbF3714ikQj5fJ49e/awb98+BgYGaGlpIRwOMz4+\njqpy4sQJVJVkMsng4ODCRGKTk5P09vaSSqUQEbq6ukilUrS3t5PP55mamqKvr4+BgQGi0SgdHR0M\nDw/T0dFBNpslk8ksbI/FYrS1tTEyMkJnZyeZTIbclFu+8eTwKQYHQyQSCcbGxuju7mZiYoJsNrvw\n+UQiQSwWI51O09PTQzqdJpfLLWwvPqdkMsno6GjJ5zQxMcHY2Ni6nNPMzMzC9ng8Htg5rfZ3EhGO\nHTtWV+dUzu80Pz/PyZMn6+qcVvs7TUxMMDw8XFfntJrfyQ+pxmIfInIz8DZgDogD7cCXgR7gr1T1\nO95+T+MSx28CqOrNXvk3gA8AR4HvqOour/ytwOWq+u7i4x04cEB37dq1YlxTU1O0tLSs/QTL4D9/\nPMxH/+s4V57bze+/+oxAYoBgHdQK5sBhHszBoUOHHti9e/fFS22rym0oVd2vqv2quhPXcH2vql4H\nfBW4AkBEzgViwDBwJ3CtiDSJyJm4huz7VfUkMC4il3q9oK4H7ig3ruHh4bWc1ppYmB8q4MkEg3RQ\nK5gDh3kwB35U5TaUD58GPi0ijwJZ4O1ew/VjInI78DiuNrJPVQv/q94IfAZIAHd7j7Lo6OhYQ+hr\nIx51eXp2LtjJBIN0UCuYA4d5MAd+VD1ZqOp9wH3e6yxw3TL73QTctET5j4AL1iOWbDa7Hl9TFrGw\nGx6SywebLIJ0UCuYA4d5MAd+NPQI7kwmE9ixo2GnPpsPdtbZIB3UCubAYR7MgR+rShYiEvLGOtQF\nQfapLtQssgHXLKxfuTkoYB7MgR8lJQsR2SQiXwRmcAPkEJE3iMhfVDK4ShNkn+poqDZqFtav3BwU\nMA/mwI9Saxb/CKSBHbiGaIADwFsqEVS1iMVigR076tUsZnLB1iyCdFArmAOHeTAHfpTawL0b2Kqq\nORFRAFVNicjmyoVWedra2gI7dndzFIDhqWAb1IJ0UCuYA4d5MAd+lFqzSOMG0C0gImcAJ9c9oioy\nMjIS2LET0RCRkJBXyAbYfTZIB7WCOXCYB3PgR6nJ4pPAv3uzxIZE5DLgs7jbUxuWzs7OwI4tImyK\nu4rdqZm5wOII0kGtYA4c5sEc+FFqsvgr4F+AfwCiuMF0dwAfrVBcVSHobnKbEsEni6Ad1ALmwGEe\nzIEfJbVZeKOqP8oGTw6LmZmZCfT4C8kikwsshqAd1ALmwGEezIEfpXad/aOi6cMLZZeIyPsqE1Z1\nCLpP9cJtqExwNYugHdQC5sBhHsyBH6Xehnovbp6mYh4H/sf6hlNdgu5TvSnhekQFmSyCdlALmAOH\neTAHfpSaLGLA4nslWdx04xuWeDzY8Au3ocYCvA0VtINawBw4zIM58KPUZPEAbrbXYn4bOLS+4VSX\nRCIR6PFroTdU0A5qAXPgMA/mwI9SB+X9HnCPiLwNeBo4G+gDfrlSgVWDsbEx2tvbAzv+8w3cwSWL\noB3UAubAYR7MgR+l9oZ6zFuc6P8C+nGr3N2lqpOVDK7SdHd3B3r8hTaLAGsWQTuoBcyBwzyYAz9K\nXs/CSwxfqmAsVWdiYqKktWcrRWG1vEyAq+UF7aAWMAcO82AO/CgpWXhLm94EXAicZlJVg1tAeo0E\nvdBJS8w1GY1l5pibVyIhqXoMQTuoBcyBwzyYAz9KrVl8EddW8QfAdOXCqS5B96nelIjS1RxhdHqO\n0ekcm1urP+Nl0A5qAXPgMA/mwI9Se0OdD1yvqner6neLH5UMrtLUQp/q9iaXrydmg2m3qAUHQWMO\nHObBHPhRarL4HvCySgYSBLXQTa61ybVbTMwG025RCw6Cxhw4zIM58KPU21BHga+LyFeA01Kvqv7P\n9Q6qWtTCQidtXs1iMqBkUQsOgsYcOMyDOfCj1JpFC3AXbsbZ7YseG5Z0Oh10CLTFXM3ioZMTgRy/\nFhwEjTlwmAdz4Eep4yzeWelAgqCnp2flnSpMuzeK+7nx2UCOXwsOgsYcOMyDOfCj1JoFACLSJiJn\nishZhccqPx8WkQdF5C7v/QdE5FkRech7vL5o3/0iclhEnhSR1xWVXyQij3jbPiYiZfc3rYW/Inb/\njFts5ehoMFMj14KDoDEHDvNgDvwodZzFecAXgJcCCoj3DBBexfHeCzwBFI+n/7Cq/u0Sx7sW1wtr\nK/AtETlXVfPALcC7gB8AXwOuBO5eRQwL5HLBTeBXYGdngpDA8HSO6Wye5thqdK6dWnAQNObAYR7M\ngR+l1iw+AXwH6ALGgU7gfwNvL/VAItIPXIVbonUlrgFuU9VZVT0CHAYuEZEtQLuqHvQWZPoc8MZS\nY1hMLfSpDoeEs7pcD4wHn6t+u0UtOAgac+AwD+bAj1KTxUuB96vqKUBUNQ38P8AHV3GsjwDvA+YX\nlb9HRB4WkU+LSGEB3G3A8aJ9Tnhl27zXi8vLolb6VPd6g/GeTFV/vGOtOAgSc+AwD+bAj1K7zs7g\nekLlgGEROQMYA0qadUtErgaGVPUBEbm8aNMtuISj3vPfATeUGNOyDA0NsXfvXiKRCPl8nj179rBv\n3z4GBgZoaWkhHA4zPj5OJBLhxIkTqCrJZJLBwcGFeWEmJyfp7e0llUohInR1dZFKpWhvbyefzzM1\nNUVfXx8DAwNEo1E6OjoYHh6mo6ODbDZLJpNZ2B6LxWhra2NkZITOzk4ymQwzMzML2/ubXf687b8H\n+fltcTpklmw2u7A9kUgQi8VIp9P09PSQTqfJ5XIL24vPKZlMMjo6WvI5TU9PMzY2tu7nFI/HSSQS\njI2N0d3dzcTERNXOabW/UywW49ixY3V1TuX8TuFwmJMnT9bVOa32d5qenmZ4eLiuzmk1v5Mf4u7m\nrLCTyO3A11T1MyLyIdzss7PAT1V1xdtAInIz8DZgDrdgUjvwZVW9rmifnbiZbC8Qkf0Aqnqzt+0b\nwAdw4z2+o6q7vPK3Aper6ruLj3fgwAHdtWvXiuc1NjZGZ2fnivtVmlOZHG/94qPkFf7o8h384s90\nVe3YteIgSMyBwzyYg0OHDj2we/fui5faVtJtKFV9s6p+xnv7x8DNwD8Bv1Hi5/erar+q7sQ1XN+r\nqtd5bRAF3gQ86r2+E7hWRJq8SQzPAe5X1ZPAuIhc6vWCuh64o5QYlmJ8fLzcj64rmxJRXn2Wu0BX\nTt3rS604CBJz4DAP5sCPUntD/WGhx5KqzgO3euW/D/z9Go7/1yJyIe7/yKPAu71jPObVZh7H1Ub2\neT2hwK3Y9xkggesFVVZPKIBkMll24JUik1vcpFNZatFBtTEHDvNgDvwotYF7uSk9/nS1B1TV+1T1\nau/121T1Jar6s6r6Bq/mUNjvJlU9W1VfpKp3F5X/SFUv8Lb9rpZyH20ZRkdHy/3ouhP2Ros8PjRV\n1ePWkoOgMAcO82AO/PCtWYjIL3ovwyJyBW58RYGzgGDmqFgn1pBn1p2zupvh8BjZuerWLGrJQVCY\nA4d5MAd+rHQb6lPecxz4dFG5AoPAeyoRVLWopSrnmZ1xAMarPFV5LTkICnPgMA/mwA/f21Cqeqaq\nngl8ofDae5ylqpep6p1VirMiDA4OBh3CAoU5ok5lqpssaslBUJgDh3kwB36U2hvq+uL3InKFiLy6\nMiFVj1paa3drexMAz47PVrUqXEsOgsIcOMyDOfCjpGQhIt8VkVd6r98P3AZ8SUT+uJLBNRItsTAh\ngVxeydttU8MwaoxSe0NdABz0Xr8LuAK4FPjtSgRVLSYnJ4MO4TQiIdd/YLaKjdy15iAIzIHDPJgD\nP0pNFiFAReRs3Kjvx1X1OG5CwQ1Lb29v0CGcxhmbXCP3T6o4R1StOQgCc+AwD+bAj1KTxX8BHwf+\nFvgKgJc4hisUV1VIpVJBh3AaP7vF3S99eKB6f93UmoMgMAcO82AO/Cg1WbwDOAU8jJujCWAX8NH1\nD6l6rGHdpIpQSBaPVjFZ1JqDIDAHDvNgDvwodVnVEdycUMVl/1mRiKpIV1f1JuwrhcJU5RNVHGtR\naw6CwBw4zIM58GPZZCEif6KqN3mv/9dy+6nqclOB1DypVIodO3YEHcYCIe+vmnwVB3HXmoMgMAcO\n82AO/PCrWfQXvd5e6UCCoL29feWdqkihN9RMFXtD1ZqDIDAHDvNgDvxYNlmo6u8UvX5ndcKpLvl8\nfuWdqkhfW4xISBiczDI7N09TpNQmpfKpNQdBYA4c5sEc+LHs/0YiclYpj2oGu95MTVV3hteViIZD\ntDeFARieqs7C8bXmIAjMgcM8mAM//G5DHcZNGCicvibP4vfhCsRVFWpxcfa+tiZGM3M8fHKCbR1N\nlT9eDTqoNubAYR7MgR/L1ixUNaSqYVUNAb+Jm+JjF24G2l3AF4G9VYmyQtTi4uwv3twMwPhsdarD\nteig2pgDh3kwB36U1HUW+CBwjqpmvPdPici7gZ/gVq3bkESj0aBDeAHxqKuoZavUJaoWHVQbc+Aw\nD+bAj9VM97FzUdkONvAtKICOjo6gQ3gBMW/JvBPp2aocrxYdVBtz4DAP5sCPUpPFh4F7ReQvReR3\nROQvgW975RuW4eHam60k4dUsvvP0WFWmKq9FB9XGHDjMgznwo9T1LP4GeCfQC7wB6ANuUNW/rmBs\nFacW/4p4zZmbFl4PTVa+R1QtOqg25sBhHsyBH6W2WaCqXwe+XsFYqk42mw06hBfQ2Rzl57a1cejZ\nCY6MZehti1X0eLXooNqYA4d5MAd+VH7UVw2TyWRW3ikAWmJeI3cVRnLXqoNqYg4c5sEc+NHQyaJW\n+1QXGrmzVVgyr1YdVBNz4DAP5sCPhk4WtdqnOhZ2P8vgZOWrxLXqoJqYA4d5MAd+rCpZiCMpZU76\nLiJhEXlQRO5aVP4HIqIi0lNUtl9EDovIkyLyuqLyi0TkEW/bx8qNBSAWq2x7QLk0R93P8tkHTvKf\nPx5mOlu5AXq16qCamAOHeTAHfpSULERkk4h8HsgAg0BGRD4vIqud/P29wBOLvns78Frgp0Vl5wHX\nAucDVwKfEJHCmI5bcOuAn+M9rlxlDAu0tbWV+9GK8sbzNy+M5P7ofx3nHw6cqNixatVBNTEHDvNg\nDvwotWbxz0ACeBnQ6j03AZ8u9UAi0g9cBXxy0aYPA+/j9PmmrgFuU9VZVT2Cm6fqEhHZArSr6kF1\ngxA+B7yx1BgWMzIyUu5HK0pvW4y/veocXrHdTZecmqrc7ahadVBNzIHDPJgDP0rtOvuLQF/RdB9P\niMg7gOdWcayP4JLCQuoWkWuAZ1X1vxfdTdoGHCx6f8Iry3mvF5efxtDQEHv37iUSiZDP59mzZw/7\n9u1jYGCAlpYWwuEw4+PjJBIJTpw4gaqSTCYZHByktdUtbTo5OUlvby+pVAoRoauri1QqRXt7O/l8\nnqmpKfr6+hgYGCAajdLR0cHw8DAdHR1ks1kymczC9lgsRltbGyMjI3R2dpLJZJiZmVnYHo/HSSQS\njI2N0d3dzcTEBNlsljdf0M0Pjo/z5NAUY2NjjI+P09PTQzqdJpfLLXy++JySySSjo6Mln9Ps7Cxj\nY2NVO6fC9kQiQSwWI51Or/s5rfZ3am1t5dixY3V1TuX8Tk1NTZw8ebKuzmm1v9Ps7CzDw8N1dU6r\n+Z38kFJGCYvID4B3qOoTRWW7gM+q6itK+PzVwOtV9UYRuRz4Q+DNwHeA16pqWkSOAher6rCIfBw4\nqKq3ep//FHA3cBT4kKr+klf+KuD9qnp18fEOHDigu3btWvG8BgcH6e3tXXG/oJicnWPP5x8B4JO/\n9mLO2BRf92PUuoNqYA4c5sEcHDp06IHdu3dfvNS2UmsW3wa+6bVbHMetnHcd8HkRuaGwk6oud1vq\nlcAbROT1uFlr24HPA2cChVpFP3BIRC4BnuX01fn6vbJnOX0Fv0J5WczMzJT70arQ2hRhZ2eco2Mz\nPJuerUiyqHUH1cAcOMyDOfCj1DaLy3DtBpfhagSXAU8DPw+8zXtct9yHVXW/qvar6k5cw/W9qvqr\nqrpZVXd65SeAn1PVAeBO4FoRaRKRM3EN2fer6klgXEQu9XpBXQ/cseqz9tgIfapf3u/aLZ4anq7I\n928EB5XGHDjMgznwo6SahapeUelAFh3vMRG5HXgcmAP2qWqh/+iNuGnRE7hbU3eXe5yBgYGaX5x9\nS7tbAOnWBweYnZunLR7mZVvbOLenmTX0Gl5gIzioNObAYR7MgR8lJQsR8VskaVVzUqjqfcB9S5Tv\nXPT+JuCmJfb7EXDBao65HPH4+t/WWW9ecUY78n9cV7F/fWTIKz1JIhpiS1sTL0o281uv2LYwRchq\n2QgOKo05cJgHc+BHqW0Wc5zetbWYDbumRSKRCDqEFUm2xPinX30xD52c4JnRDA+cmGAqm2cym+eZ\n0QzPjGaIhoXf/fntK3/ZEmwEB5XGHDjMgznwo9Rkceai91uAPwL+Y33DqS5jY2O0t7cHHcaKnNEZ\n54zO0//iGZ+Z4/tHT/HR/zrO94+cYt9l/WXdltooDiqJOXCYB3PgR6nrWRxb9DgIvB14f2XDqyzd\n3d1Bh1A27fEIv/KibuKREGOZOZ4ZLW+2zI3sYL0wBw7zYA78WMtEgu1Acr0CCYKJiYmgQ1gTIRF+\nYadbrOXAT8fL+o6N7mA9MAcO82AO/Ci1gfvznN5m0Qy8Gri1EkFVi3pY6ORFyRa+dXiMwYny1uyu\nBwdrxRw4zIM58KPUNovDi95PAf+oqt9a53iqSj30qT6vtwWAB5+bQFVX3W5RDw7WijlwmAdz4Eep\n4yz+vNKBBEE99Kk+uztBRzzC0GSO58azbOtoWtXn68HBWjEHDvNgDvxYdZuFiDxSiUCCoB66yYVE\nOKvL9ZR6bnz1t6LqwcFaMQcO82AO/Cingbtu0m69LHTS3RwFYDSTW/Vn68XBWjAHDvNgDvwoJ1ms\nfY6JGiGdTgcdwrrQ7I3ensmtajA9UD8O1oI5cJgHc+BHOcniV9Y9ioDo6elZeacNQDzifsbp3OqX\nX60XB2vBHDjMgznwY7VrcG8GnhORswqPCsVVFerlr4jCZIPlDMyrFwdrwRw4zIM58KPUcRZXAp8C\n+jj9NpSygeeGyuVWf4+/FtmVdOt1Hxld/Vz89eJgLZgDh3kwB36UWrP4B+CDQKuqhooeGzZRQP30\nqW5rcjl/Orv621D14mAtmAOHeTAHfpSaLDqB/120BnddMDAwEHQI60JHPEJIXG+o7NzqGrnrxcFa\nMAcO82AO/Cg1WXwKeGclAwmClpaWoENYF5oiIba1NzGvcN8zY6v6bL04WAvmwGEezIEfpSaLS4Fb\nROQnIvILqdvtAAAa+ElEQVS94kclg6s04fCGvot2Gq89182W+W8LCySVRj05KBdz4DAP5sCPUueG\n+qT3qCvGx8fp7OwMOox1Yc8FSb740ABHx2YYnsrS01La4KJ6clAu5sBhHsyBH6XODfXZSgcSBMnk\nhp5h/TSi4RAvSjbz0HOTPDOaKTlZ1JODcjEHDvNgDvwo6TaUON4lIveKyMNe2atF5M2VDa+yjI6O\nBh3CutJaGMm9ikbuenNQDubAYR7MgR+ltln8L2Av8P8BZ3hlJ9jgK+WpLres+MakMJI7s4ppP+rN\nQTmYA4d5MAd+lJos3gFcraq38fwiSEeADT2Cu96qnL1tbiT3/cdLXzWv3hyUgzlwmAdz4EepySIM\nTHqvC8mitahsQzI4OBh0COvKi7yR3AOrWDWv3hyUgzlwmAdz4EepyeJrwN+LSBO4NgzciO7/WM3B\nRCQsIg+KyF3e+w+KyMMi8pCIfFNEthbtu19EDovIkyLyuqLyi0TkEW/bx2S1S8MV0draWu5Ha5Id\nnW5di/TMXMmfqTcH5WAOHObBHPhRarL4fWALkAY6cDWKHay+zeK9wBNF7/9GVX9WVS8E7gL+J4CI\nnAdcC5wPXAl8QkQKHaBvAd4FnOM9rlxlDHXLprjr3HYqM2f3Xg3DWFdKShaqOq6qb8I1bl8KnK2q\nb1LViVIPJCL9wFUUjddQ1eKb6y08f4vrGuA2VZ1V1SO4NcAvEZEtQLuqHlT3v+HngDeWGsNiJic3\n9F20FxCPhGgKC9m8ltwjqt4clIM5cJgHc+CH7zgLEWkG/hS4ADgE3KyqPyzzWB8B3ge0LTrGTcD1\nuFrLFV7xNuBg0W4nvLKc93pxeVn09vaW+9GaREToSLj1uE9l5khEVx6NWm8OysEcOMyDOfBjpUF5\n/wBcDNwN/BrQDbxntQcRkauBIVV9QEQuL96mqn8C/ImI7Ad+F/iz1X7/YoaGhti7dy+RSIR8Ps+e\nPXvYt28fAwMDtLS0EA6HGR8fJ5/PE4vFUFWSySSDg4ML9ywnJyfp7e0llUohInR1dZFKpWhvbyef\nzzM1NUVfXx8DAwNEo1E6OjoYHh6mo6ODbDZLJpNZ2B6LxWhra2NkZITOzk4ymQwzMzML2+PxOIlE\ngrGxMbq7u5mYmCCbzS5sTyQSxGIx0uk0PT09pNNpcrncwvbic4qHXRPOE0efpfPcrSue09NPP01/\nf39Nn1MymWR0dLRiv9P8/DyhUKiuzqmc3ymXy9HS0lJX57Ta3+mpp55iy5YtdXVOq/md/BC/e9si\nchL4OVU9KSLbge+p6pmr/c9bRG4G3gbMAXGgHfiyql5XtM8ZwNdU9QIvcaCqN3vbvgF8ADgKfEdV\nd3nlbwUuV9V3Fx/vwIEDumvXrhXjOnHiBP39/as9nZrm9/7jJzw2OMU7L97CWy9cebrlenSwWsyB\nwzyYg0OHDj2we/fui5fatlKbRYuqngRQ1eO4xu1Vo6r7VbVfVXfiGq7vVdXrROScot2uAX7svb4T\nuFZEmkTkTFxD9v1eLOMicqnXC+p64I5yYgLo6uoq96M1y+t3uQkF//lHJ7n/+MqrftWjg9ViDhzm\nwRz4sVKyiIjIFSLyiyLyi4vfe2Vr4UMi8qg3hchrcb2lUNXHgNuBx4GvA/tUtbCyz424RvLDwNO4\nW2RlkUql1hB6bfKaszrpanZ3F//0G8+Qzfs3dNejg9ViDhzmwRz4sdJtqKM830NpKVRVa24Ud6m3\nocbGxupyhsnhqSy/8aXHALhsRwc/t7WNlliYLW0xzuttoXhoSr06WA3mwGEezIHfbSjfBm7vtlHd\nks+vfhnSjUBPS4xfe8lm/u2RIQ4cS3Pg2PO3o645L8m+n3/+nmy9OlgN5sBhHsyBH6UOyqtLpqam\ngg6hYvzWK7bx5798Fm99aS9Xv7iHX9i5CYD/eCJFfv75ymI9OygVc+AwD+bAj1IXP6pL6n1x9st2\ndHDZjuf7JNzwr49zIj3L40NTvKTPdZWrdwelYA4c5sEc+NHQNYtGW5z9nJ4XTjTYaA6Wwhw4zIM5\n8KOhk0U0Gg06hKrSlXAVybHp5ycabDQHS2EOHObBHPjR0Mmio6OsYSMbls6E+4cwlsktlDWag6Uw\nBw7zYA78aOhkMTw8HHQIVaW7xSWL4+nnb0M1moOlMAcO82AO/GjoZNFof0W8bKubw/HBZyeY98bX\nNJqDpTAHDvNgDvxo6GSRzWaDDqGqdDVHaWsKk5tX/uybzzAynWs4B0thDhzmwRz40dDJIpPJBB1C\n1blqVw8APzg+znvvfJKhtPUrb8TrYCnMgznwo6GTRSP2qb7h5Vv5yyvPZsemOEOTOX50ynp/NOJ1\nsBTmwRz40dDJolH7VF/c385bXuoWeXn02VMBRxM8jXodLMY8mAM/GjpZxGKxoEMIjAu3tiLAw8M5\nbjl4oqHX7G7k66AY82AO/GjoZNHW1rbyTnVKT0uMX33JZgC+8miK4encCp+oXxr5OijGPJgDPxo6\nWYyMjAQdQqD81iu2sa3VrdN9w+2PM51tzBk3G/06KGAezIEfDZ0sGnne+gK/foFbWW82rxwZbcye\nIHYdOMyDOfCjoZOFdZODi5IRLuhtAeD+4+MBRxMMdh04zIM58KOhk8XMzEzQIQTOzMwMl5/t/pr6\n1uHRgKMJBrsOHObBHPhh61k0OH19ffRHY9xy4ASpqRxv+cIjdCYidDVHuea8JK84o/6nP7DrwGEe\nzIEfDV2zsD7VzkE4JFzxM10AjGXmeGZ0hh+dmOD//eYzfO/IWMARVh67DhzmwRz40dA1i3g8HnQI\ngVNw8L7X7OD3fmE76Zk5nhvP8oF7nmEym+cHPx3n1WfWd6OfXQcO82AO/GjomkUikQg6hMApdhAN\nh+hpifGzW1q58bJ+AO55apRcfj6o8KqCXQcO82AO/GjoZDE2Vv+3WFZiOQcXbWtjU9xVPH94or57\nSdl14DAP5sCPqiYLEQmLyIMicpf3/m9E5Mci8rCIfEVENhXtu19EDovIkyLyuqLyi0TkEW/bx0RE\nyo2nu7t7bSdUByznoLM5yq+8yG37wD1H+N4zY0zMzi2570bHrgOHeTAHflS7zeK9wBNAu/f+HmC/\nqs6JyF8B+4H3i8h5wLXA+cBW4Fsicq6q5oFbgHcBPwC+BlwJ3F1OMBMTE7S2tq7lfDY8fg62b3r+\n/u1f3Ht04bUAIrAr2cL7r9jBlramCkdZWew6cJgHc+BH1WoWItIPXAV8slCmqt9U1cKfqweBfu/1\nNcBtqjqrqkeAw8AlIrIFaFfVg+pmvvsc8MZyY7KFTvwdXHF2J3/+y2dx/UVbeFGymUIVToF5hceH\npnjffx7mufHZZb9jI2DXgcM8mAM/qlmz+AjwPmC5mbpuAP7Fe70NlzwKnPDKct7rxeVlYX2q/R2E\nQ8JlOzq4bEcH172sD1WlMDftHY+luOXgswxOZnnPHU/y+becT3MsXJ2g1xm7DhzmwRz4UZVkISJX\nA0Oq+oCIXL7E9j8B5oAvrMfxhoaG2Lt3L5FIhHw+z549e9i3bx8DAwO0tLQQDocZHx8nl8sRj8dR\nVZLJJIODgwtV0MnJSXp7e0mlUogIXV1dpFIp2tvbyefzTE1N0dfXx8DAANFolI6ODoaHh+no6CCb\nzZLJZBa2x2Ix2traGBkZobOzk0wmw8zMzML2eDxOIpFgbGyM7u5uJiYmyGazC9sTiQSxWIx0Ok1P\nTw/pdJpcLrewvfickskko6OjJZ/T4cOH2b59+6rP6dLuGP27+/mL7z7LxGyeQ08/x/bm+Zo4p9X+\nTvl8nnA4XNO/UzWuvWw2S2tra12d02p/pyeffJKtW7fW1Tmt5nfyQ6qxjoGI3Ay8DZcQ4rg2iy+r\n6nUi8g7g3cBuVZ329t8PoKo3e++/AXwAOAp8R1V3eeVvBS5X1XcXH+/AgQO6a9euFeMaGhpi8+bN\n63CGG5e1Ovjgt4/w/SOn2H/FDq44u2sdI6sedh04zIM5OHTo0AO7d+++eKltVWmzUNX9qtqvqjtx\nDdf3eoniStytqTcUEoXHncC1ItIkImcC5wD3q+pJYFxELvV6QV0P3FFuXLbQydod9La6zz9wYmLD\nLqBk14HDPJgDP4IeZ/FxXBvGPSLykIj8I4CqPgbcDjwOfB3Y5/WEArgR10h+GHiaMntCAaTT6TWE\nXh+s1UFfm/vH9c2nRvmze57h5Pgs8xssadh14DAP5sCPqk/3oar3Afd5r3/GZ7+bgJuWKP8RcMF6\nxNLT07MeX7OhWauDV525ie89c4qHByY5+NNxDv70cRLREDs74+zsTHBmV4IzO+NsSkToaYnRUoON\n4HYdOMyDOfCjoeeGSqfTtLS0BB1GoKzVQWciyt9efQ5ffnSIHx4f58hYhtHpOZ4YmuaJoekX7N/T\nHOWcZDObW2JEQpCIhumIR+iIR9jcGuPFm5tZwzjLsrDrwGEezIEfDZ0scrnGXXe6wHo52HPBZvZc\n4BoG0zNzHBnNcGQ0w9GxGY6OZZiYzTM4mWV4OsfwseWr+j0tUa4894WjaJfLH83RMK/f1U0iWn6N\nxa4Dh3kwB340dLKwPtWVcdARj3Dh1jYu3Hr6kJr8vPLowCTHTs2Qn1fy88p0bp5TM3Pc9cQwAMNT\nOW59cHXTRE9l81x/0Zay47XrwGEezIEfDZ0sBgYG2LFjR9BhBEo1HYRDwku3tvHSrS8cl7n35Vu5\n+8kRprP5JT65NPc+Pcpz41lufXCA6Vye3760f+UPLYFdBw7zYA78aOhkYfcma8dBSyzMr71kdf3b\nf+NlfXziwAnuemKYLz+aIhISupqjNEfDNMdCdDRF6GyO0pWI0BILL9sWUisOgsY8mAM/GjpZhMO1\n1zOn2mxkB5GQ8H+/cjvRsPCVR1Pc/vDQsvvGwsLW9ibO2BSnrSlMLBLiRT3NvOaszg3tYD0xD+bA\nj4ZOFuPj43R21vcqcCtRDw5uuHgrZ2yKMzKVY2I2z3Quz3Q2T3pmjtHMHKcyOaZz815j+8xpn/2b\n7x4jERFe3DtKX1uMzuYorbEw0bAQDQnRcIhoSIhFhHgkRDwSds/REE2REPFIiFhYqt6DqxLUw7Ww\nVszB8jR0skgmk0GHEDj14KApEuKqXf7946ezeU6kZ/npqRmmc3mmsnnueWqUE+lZJnO6pgWeBNce\ns7k1yh++egcX9G3MKa7r4VpYK+ZgeRo6WYyOjtLc3Bx0GIHSKA6aY2HOTTZzbvL5c732pb3k5pXH\nnznORKSDkekco9M5pnN5cnklN6/k8vPk8ko2P8/M3DwzOe95bp5Z7zmXV+bmlefGs7z/a4f5tZds\npn9Tk2s7iYbZ3Bpjc2uUaDjoCRP8aZRrwQ9zsDwNnSw26lxG60kjOxARYmGhOx7iwu2bVv7AMuTn\nXbL41A+f46uPpfjSfw++8FhAd0uUnuYoiWiYRDREczREPBpeeE5ETi9LREN0JqL0dzRV5TZXI18L\nBczB8jR0srAqpzmAtTsIh4RwSLjxsn5etrWNxwYnSU3lmJmbZ8objJiayjI8lWN4avWDvpItUV66\ntY0Wr53k/N5WLtzauqaBiEsex64Fc+BDQyeLwcHBhu9TbQ7W10FhsajFzM0rqcksY5k5Mrk8mdw8\nmTnvOTfPdC7PzGnPbvuJU7OkpnJ866nRom8bIiQQj7jkkWyJsbU9xmvO6uSVO8uvIdm1YA78aOhk\nYWvtmgOojoNISNjS3sSW9tWtVz6vyuHhDD8ZnmZuXknPzPHgsxP8ODXFtJdUxjJz/GR4mvueOcVr\nztzEDZdspbc1RmiVt67sWjAHfjR0sjCMWick8oKG+bdftIW5eWV2bp5Mzt3memxgilsfHOC7R07x\n3SOniIaE7ZvivLy/jYv62+lujtIc89pHIqG66OprVJeGThaTk5N0d79w0rpGwhxsTAeRkBCJhWmJ\nhelpiXF+byuvOnMT//iDZ3l8cIr0zBzPjGZ4ZjTDvywarBgSN9tv3BsnkvASSGQ+y45khmRrlGRL\nlGRLjKZICAFi4RB9bTFikdru0bVWNuK1UC0aOln09vYGHULgmIP6cbClvYk//+WzAMjk8vx4aJr7\nj6d5dHCKqawbqDiVc11+p7JurMliHkqllv1+AXrbYnTEIwiu1pNsibKlvYmeliixcMglsZAQCcvz\nr0NugOPpZW7fcMj1SguJ+77CswChkBDCzTgcEkHExVDJWlG9XAuVoKGTRSqVYvv27UGHESjmoD4d\nJKJhXratjZdte+GkjW623/zCuJHM3DwzuTyHTwwyH29neCpLairH8FSWbF5RhZm5PAMT2YVH0IS8\nxLE4iYSk6Nkrk8L+3r5u21Kvhblcjlgs+vz+i46x8FogxPOvhcKxn0924ZDrmh0LhwiH5AXT7DdH\nw7zpgiTJlo2xlGtDJwu7b2sOoPEchENCW1OEtkVt7V35NP39y0/mmMvPc3Iiu1AjyeWVocksAxOz\nDE/nmPMGJxY/cnldGIfiHm4QY17dtnl1jfiFZy16r4Vnnt9WGAUx773InzYuYp3GSEyVPvPxWvm3\nR4bY2t5ESyxEc9TdVmyJhWkKh1x2KoO+thhv/tn1ryE1dLLo6uoKOoTAMQfmoMBKHqLhEGdsilcp\nmuVZLonooiRD0fZ5lilXULzvUJjOZGiKxxfWkZ/3yudVF47Hou9T7zsWv57zZgCYnXOJspiBiVnu\neHyY2bl5nhufXVc/u5LNlizWm1Qq1fB9qs2BOSiwUTyICOGFv7rXt1Z4bHyQHVurM5HgDS/fSjoz\nx5Q3V5l7uLE2s3PzZX/vpkRl/ltv6GTR3t4edAiBYw7MQQHzUF0HIRE6m6N0Eq3aMddCffeDW4F8\nvnr3JmsVc2AOCpgHc+BHQyeLqampoEMIHHNgDgqYB3PgR0MnC1uc3RyAOShgHsyBH1VNFiISFpEH\nReQu7/2vi8hjIjIvIhcv2ne/iBwWkSdF5HVF5ReJyCPeto/JGvo9DgwMlH8ydYI5MAcFzIM58KPa\nNYv3Ak8UvX8U2AN8r3gnETkPuBY4H7gS+ISIFOZjvgV4F3CO97iy3GC++tWvlvvRusEcmIMC5sEc\n+FG1ZCEi/cBVwCcLZar6hKo+ucTu1wC3qeqsqh4BDgOXiMgWoF1VD6pbpeRzwBvLjenLX/5yuR+t\nG8yBOShgHsyBH9XsOvsR4H3AC+cfeCHbgINF7094ZTnv9eLy0xgaGmLv3r1EIhHy+Tx79uxh3759\nDAwM0NLSQjgcZnx8nE2bNnHixAlUlWQyyeDg4MIUxZOTk/T29pJKpRARurq6SKVStLe3k8/nmZqa\noq+vj4GBAaLRKB0dHQwPD9PR0UE2myWTySxsj8VitLW1MTIyQmdnJ5lMhpmZmYXt8XicRCLB2NgY\n3d3dTExMkM1mF7YnEglisRjpdJqenh7S6TS5XG5he/E5JZNJRkdHSz6nyy+/nLGxsbo6p9X+TolE\ngmPHjtXVOZXzO/X29nLy5Mm6OqfV/k6XX345w8PDdXVOq/md/JBqLCMoIlcDr1fVG0XkcuAPVfXq\nou33eWU/8t5/HDioqrd67z8F3A0cBT6kqr/klb8KeH/xdwF8+9vfTgHHVoprdHS0p6ura3jtZ7hx\nMQfmoIB5MAfAjt27dy+5XGC1ahavBN4gIq8H4kC7iNyqqtcts/+zQPHMbv1e2bPe68Xlp7HcyRqG\nYRjlUZU2C1Xdr6r9qroT13B9r0+iALgTuFZEmkTkTFxD9v2qehIYF5FLvV5Q1wN3VDp+wzCMRifQ\ncRYi8iYROQFcBvyniHwDQFUfA24HHge+DuxT1cLQyhtxjeSHgadxt6cMwzCMSqKqDffAdbd9Epdw\n/ijoeKp87keBR4CHgB95ZV3APcBT3nNn0HGu8zl/GhgCHi0qW/acgf3etfEk8Lqg46+ggw/gbuM+\n5D1eX+cOtgPfwf0R+hjw3ka8Fsp9NNwIbm+8xj8AvwKcB7zVG9fRSFyhqheqamEg5B8B31bVc4Bv\ne+/ric/wwvE4S57zCmN8NjKfYekxSR/2roULVfVrUNcO5oA/UNXzgEuBfd65Ntq1UBYNlyyAS4DD\nqvqMqmaB23DjOhqZa4DPeq8/yxrGrtQiqvo9YHRR8XLnvOQYn6oEWkGWcbAc9ergpKoe8l5P4AYI\nb6PBroVyacRksQ04XvR+ybEadYwC3xKRB0Tkt7yyXnWdBwAGgEZYiHi5c2606+M9IvKwiHxaRAoL\nOdS9AxHZCbwM+AF2LZREIyaLRucXVPVC3G24fSLy6uKN6m7UVn7wTQ3RiOfscQtwFnAhcBL4u2DD\nqQ4i0gr8O/A/VHW8eFsDXwsr0ojJYrkxHA2Bqj7rPQ8BX8FVqwe9qVTwnoeCi7BqLHfODXN9qOqg\nquZVdR74J56/xVK3DkQkiksUX1DVwtweDX8tlEIjJosfAueIyJkiEsM1YN0ZcExVQURaRKSt8Bp4\nLW4yxzuBt3u7vZ3GGLuy3DkvOcYngPgqTuE/SI834a4FqFMH3tisTwFPqOrfF21q+GuhFBpuWVVV\nnROR3wW+AYSBT6sb19EI9AJf8WZ1jwBfVNWvi8gPgdtFZC9umpQ3BxjjuiMiXwIuB3q8cT1/BnyI\nJc5ZVR8TkcIYnzlOH+OzYVnGweUiciHutstR4N1Qvw5wM0m8DXhERB7yyv6YBrsWyqUqc0MZhmEY\nG5tGvA1lGIZhrBJLFoZhGMaKWLIwDMMwVsSShWEYhrEiliwMwzCMFbFkYRgVQET+WEQ+ufKehrEx\nsK6zhlEGIjJZ9LYZmAUKffDfrapfqH5UhlE5LFkYxhoRkaPAb6rqt4KOxTAqhd2GMowKICIfEJFb\nvdc7RURF5J0iclxExkTkt0Xk5d6Mr6dE5OOLPn+DiDzh7fsNEdkRzJkYhsOShWFUj1fg5hd6C/AR\n4E+AX8ItrvNmEXkNgIhcg5uGYg+QBL4PfCmIgA2jgCULw6geH1TVGVX9JjAFfElVh7yZgL+PW18B\n4LeBm1X1CVWdA/4SuNBqF0aQWLIwjOoxWPQ6s8T7Vu/1DuCj3u2pU7gV7oQGXnjHCJ6Gm3XWMDYA\nx4GbrEeVUUtYzcIwao9/BPaLyPkAItIhIr8ecExGg2M1C8OoMVT1K97Sn7d57RRp4B7gX4ONzGhk\nbJyFYRiGsSJ2G8owDMNYEUsWhmEYxopYsjAMwzBWxJKFYRiGsSKWLAzDMIwVsWRhGIZhrIglC8Mw\nDGNFLFkYhmEYK2LJwjAMw1iR/x+PxSu6+3tZzgAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(*zip(*curve))\n", "plt.ylabel('Pen-up distance')\n", "plt.xlabel('Time');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comparing to the Greedy Solution\n", "\n", "We can use the `ink_transit_curve` function from before to see if we have improved over the problem of the greedy solution's efficiency degrading over time. As you can see, the two approaches are roughly as efficient initially, but the vsp line maintains its slope for longer while the greedy line's slope falls off." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "scrolled": true }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ink_transit_curve(greedy_route, label='greedy')\n", "ink_transit_curve(vrp_route, label='vrp')\n", "plt.legend();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The real gain is at the end: notice that the greedy line continues after the vrp line ends. That's where it's making big moves just to draw the remaining short paths." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "\n", "As we have seen, the greedy step alone produced the vast majority of the efficiency improvement for this particular image. My take-away is that a greedy optimizer should be part of every plotter's toolkit, but that more extreme optimization doesn't add much. That said, there's only so much that can be concluded from looking at a single plot, and it is possible that certain plotting techniques are more likely than others to lead to plots that “trap” a greedy optimizer into an inefficient solution." ] } ], "metadata": { "date": "2018-03-25", "title": "Optimizing Plots with a TSP Solver", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 2 }