{"action":"created","comment":{"url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments/33424813","id":33424813,"diff_hunk":"@@ -0,0 +1,730 @@\n+:author: Matthew Rocklin\n+:email: mrocklin@gmail.com\n+:institution: Continuum Analytics\n+\n+----------------------------------------------------------------------\n+Dask: Parallel Computation with Blocked algorithms and Task Scheduling\n+----------------------------------------------------------------------\n+\n+.. class:: abstract\n+\n+    Dask enables parallel and out-of-core computation.  We couple blocked\n+    algorithms with dynamic and memory aware task scheduling to achieve a\n+    parallel and out-of-core numpy clone.  We show how this extends the\n+    effective scale of modern hardware to larger datasets and discuss how these\n+    ideas can be more broadly applied to other parallel collections.\n+\n+.. class:: keywords\n+\n+   parallelism, numpy, scheduling\n+\n+Introduction\n+------------\n+\n+The Scientific Python stack rarely leverages parallel computation.  Code built\n+off of NumPy and Pandas generally runs in a single thread on data that fits\n+comfortably in memory.  Advances in hardware in the last decade in multi-core\n+processors and solid state drives provide significant and yet largely untapped\n+performance advantages.\n+\n+However the Scientific Python stack consists of hundreds of software packages,\n+papers, PhD theses, and developer-years.  This stack is a significant\n+intellectual and financial investment that, for the most part, does not align\n+well with modern hardware.  We seek software solutions to parallelize this\n+software stack without triggering a full rewrite.\n+\n+This paper introduces ``dask``, a specification to encode parallel algorithms,\n+using primitive Python dictionaries, tuples, and callables.  We use ``dask`` to\n+create ``dask.array`` a parallel out-of-core NumPy clone using blocked\n+algorithms.  This serves both as a general library for parallel out-of-core\n+ndarrays but also as a demonstration that we can parallelize complex codebases\n+like NumPy in a straightforward manner.\n+\n+We first define ``dask`` graphs and give a trivial example of their use.  We then\n+share the design of ``dask.array`` a parallel ndarray.  Then we discuss dynamic\n+task scheduling and policies to minimize memory footprint.  We then give two\n+examples using ``dask.array`` on computational problems.  We then briefly\n+discuss ``dask.bag`` and ``dask.dataframe``, two other collections in the\n+``dask``` library.  We finish with thoughts about extension of this approach\n+into the broader Scientific Python ecosystem.\n+\n+Dask Graphs\n+-----------\n+\n+Normally humans write programs and then compilers/interpreters interpret them\n+(e.g.  ``python``, ``javac``, ``clang``).  Sometimes humans disagree with how\n+these compilers/interpreters choose to interpret and execute their programs.\n+In these cases humans often bring the analysis, optimization, and execution of\n+code into the code itself.\n+\n+Commonly a desire for parallel execution causes this shift of responsibility\n+from compiler to human developer.  In these cases we often represent the\n+structure of our program explicitly as data within the program itself.\n+\n+\n+Dask is a specification that encodes task schedules with minimal incidental\n+complexity using terms common to all Python projects, namely dicts, tuples,\n+and callables.  Ideally this minimum solution is easy to adopt and understand\n+by a broad community.\n+\n+We define a dask graph as a Python dictionary mapping keys to tasks or values.\n+A key is any python hashable, a value is any Python object that is not a task,\n+and a task is a Python tuple with a callable first element.\n+\n+Example\n+~~~~~~~\n+\n+.. figure:: dask-simple.png\n+   :scale: 40%\n+\n+   A simple dask dictionary\n+\n+Consider the following simple program\n+\n+.. code-block:: python\n+\n+   def inc(i):\n+       return i + 1\n+\n+   def add(a, b):\n+       return a + b\n+\n+   x = 1\n+   y = inc(x)\n+   z = add(y, 10)\n+\n+We encode this as a dictionary in the following way\n+\n+.. code-block:: python\n+\n+   d = {'x': 1,\n+        'y': (inc, 'x'),\n+        'z': (add, 'y', 10)}\n+\n+While less pleasant than our original code this representation can be analyzed\n+and executed by other Python code, not just the CPython interpreter.  We don't\n+recommend that users write code in this way, but rather that it is an\n+appropriate target for automated systems.  Also, in non-toy examples the\n+execution times are likely much larger than for ``inc`` and ``add``, warranting\n+the extra complexity.\n+\n+Specification\n+~~~~~~~~~~~~~\n+\n+We represent a computation as a directed acyclic graph of tasks with data\n+dependencies.  Dask is a specification to encode such a graph using ordinary\n+Python data structures, namely dicts, tuples, functions, and arbitrary Python\n+values.\n+\n+A **dask graph** is a dictionary mapping data-keys to values or tasks.\n+\n+.. code-block:: python\n+\n+   {'x': 1,\n+    'y': 2,\n+    'z': (add, 'x', 'y'),\n+    'w': (sum, ['x', 'y', 'z'])}\n+\n+A **key** can be any hashable value that is not a task.\n+\n+.. code-block:: python\n+\n+   'x'\n+   ('x', 2, 3)\n+\n+A **task** is a tuple with a callable first element.  Tasks represent atomic\n+units of work meant to be run by a single worker.\n+\n+.. code-block:: python\n+\n+   (add, 'x', 'y')\n+\n+We represent a task as a tuple such that the *first element is a callable\n+function* (like ``add``), and the succeeding elements are *arguments* for that\n+function.\n+\n+An **argument** may be one of the following:\n+\n+1.  Any key present in the dask like ``'x'``\n+2.  Any other value like ``1``, to be interpreted literally\n+3.  Other tasks like ``(inc, 'x')``\n+4.  List of arguments, like ``[1, 'x', (inc, 'x')]``\n+\n+So all of the following are valid tasks\n+\n+.. code-block:: python\n+\n+   (add, 1, 2)\n+   (add, 'x', 2)\n+   (add, (inc, 'x'), 2)\n+   (sum, [1, 2])\n+   (sum, ['x', (inc, 'x')])\n+   (np.dot, np.array([...]), np.array([...]))\n+\n+To encode keyword arguments we recommend the use of ``functools.partial`` or\n+``toolz.curry``.\n+\n+\n+Dask Arrays\n+-----------\n+\n+The ``dask.array`` submodule uses dask graphs to create a numpy clone that uses\n+all of your cores and operates on datasets that do not fit in memory.  It does\n+this by building up a dask graph of blocked array algorithms.\n+\n+Several other large NumPy implementations exist, including Biggus_ an\n+out-of-core ndarray specialized for climate science, Spartan_ a\n+distributed memory ndarray, and Distarray_ a distributed memory ndarray\n+that interacts well with other distributed array libraries like Trillinos.\n+There have also been numerous projects in traditional high performance\n+computing space including Elemental [Pou13]_, High Performance Fortran, etc..\n+\n+Blocked Array Algorithms\n+~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Blocked algorithms compute a large result like \"take the sum of these trillion\n+numbers\" with many small computations like \"break up the trillion numbers into\n+one million chunks of size one million, sum each chunk, then sum all of the\n+intermediate sums.\"  Through tricks like this we can evaluate one large problem\n+by solving very many small problems.\n+\n+Blocked algorithms have proven useful in modern numerical linear algebra\n+libraries like Flame and Plasma and more recently in data parallel systems like\n+Dryad [Isa07]_ and Spark [Zah10]_.  These compute macroscopic operations with a\n+collection of related in-memory operations.\n+\n+Dask.array takes a similar approach to linear algebra libraries but focuses\n+instead on the more pedestrian ndarray operations, like arithmetic, reductions,\n+and slicing common in interactive use.\n+\n+\n+Example: ``arange``\n+~~~~~~~~~~~~~~~~~~~\n+\n+Dask array functions produce ``Array`` objects that hold on to dask graphs.\n+These dask graphs use several ``numpy`` functions to achieve the full result.\n+In the following example one call to ``da.arange`` creates a graph with three\n+calls to ``np.arange``\n+\n+.. code-block:: python\n+\n+   >>> import dask.array as da\n+   >>> x = da.arange(15, chunks=(5,))\n+   >>> x       # Array object metadata\n+   dask.array<x-1, shape=(15,), chunks=((5, 5, 5)), dtype=int64>\n+   >>> x.dask  # Every dask array holds a dask graph\n+   {('x', 0): (np.arange, 0, 5),\n+    ('x', 1): (np.arange, 5, 10),\n+    ('x', 2): (np.arange, 10, 15)}\n+\n+Further operations on ``x`` create more complex graphs\n+\n+.. code-block:: python\n+\n+   >>> z = (x + 100).sum()\n+   >>> z.dask\n+   {('x', 0): (np.arange, 0, 5),\n+    ('x', 1): (np.arange, 5, 10),\n+    ('x', 2): (np.arange, 10, 15),\n+    ('y', 0): (add, ('x', 0), 100),\n+    ('y', 1): (add, ('x', 1), 100),\n+    ('y', 2): (add, ('x', 2), 100),\n+    ('z', 0): (np.sum, ('y', 0)),\n+    ('z', 1): (np.sum, ('y', 1)),\n+    ('z', 2): (np.sum, ('y', 2)),\n+    ('z',): (sum, [('z', 0), ('z', 1), ('z', 2)])}\n+\n+Dask.array also holds convenience functions to execute this graph, completing\n+the illusion of a numpy clone\n+\n+.. code-block:: python\n+\n+   >>> z.compute()\n+   1605\n+\n+\n+Array metadata\n+~~~~~~~~~~~~~~\n+\n+In the example above ``x`` and ``z`` are both ``dask.array.Array`` objects.\n+These objects contain the following data\n+\n+1.  A dask graph, ``.dask``\n+2.  Information about shape and chunk shape, called ``.chunks``\n+3.  A name identifying which keys in the graph correspond to the result,\n+    ``.name``\n+4.  A dtype\n+\n+The second item here, ``chunks``, deserves further explanation.  A normal numpy\n+array knows its ``shape``, a dask array must know its shape and the shape of\n+all of the internal numpy blocks that make up the larger array.  These shapes\n+can be concisely described by a tuple of tuples of integers, where each\n+internal tuple corresponds to the lengths along a single dimension.\n+\n+.. figure:: array.png\n+   :scale: 40%\n+\n+   A dask array\n+\n+In the example above we have a 20 by 24 array cut into uniform blocks of size 5\n+by 8.  The ``chunks`` attribute describing this array is the following:\n+\n+.. code-block:: python\n+\n+   chunks = ((5, 5, 5, 5), (8, 8, 8))\n+\n+Where the four fives correspond to the heights of the blocks along the first\n+dimension and the three eights correspond to the widths of the blocks along the\n+second dimension.  This particular example has uniform sizes along each\n+dimension but this need not be the case.  Consider the chunks of the following example\n+operations\n+\n+.. code-block:: python\n+\n+   >>> x[::2].chunks\n+   ((3, 2, 3, 2), (8, 8, 8))\n+\n+   >>> x[::2].T.chunks\n+   ((8, 8, 8), (3, 2, 3, 2))\n+\n+Every ``dask.array`` operation, like ``add``, slicing, or ``transpose`` must\n+take the graph and all metadata, add new tasks into the graph and determine new\n+values for each piece of metadata.\n+\n+\n+Capabilities and Limitations\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Adding subgraphs and managing metadata for most of numpy is difficult but\n+straightforward.  At present ``dask.array`` is around 5000 lines of code\n+(including about half comments and docstrings).  It encompasses most commonly\n+used operations including the following:\n+\n+*  Arithmetic and scalar mathematics, ``+, *, exp, log, ...``\n+*  Reductions along axes, ``sum(), mean(), std(), sum(axis=0), ...``\n+*  Tensor contractions / dot products / matrix multiply, ``tensordot``\n+*  Axis reordering / transpose, ``transpose``\n+*  Slicing, ``x[:100, 500:100:-2]``\n+*  Fancy indexing along single axes with lists or numpy arrays, ``x[:, [10, 1, 5]]``\n+*  A variety of utility functions, ``bincount, where, ...``\n+\n+However dask.array is unable to handle any operation whose shape can not be\n+determined ahead of time.  Consider for example the following common numpy\n+operation\n+\n+.. code-block:: python\n+\n+   x[x > 0]\n+\n+The shape of this array depends on the number of positive elements in ``x``.\n+This shape is not known given only metadata; it requires knowledge of the\n+values underlying ``x``, which are not available at graph creation time.\n+\n+\n+Dynamic Task Scheduling\n+-----------------------\n+\n+We now execute task graphs.  How we execute these graphs strongly impacts\n+performance.  Fortunately we can tackle this problem with a variety of\n+approaches without touching the graph creation problem discussed above.  Graph\n+creation and graph execution are separable problems.  The dask library contains\n+schedulers for single-threaded, multi-threaded, multi-process, and distributed\n+execution.\n+\n+Current dask schedulers all operate *dynamically*, meaning that execution order\n+is determined during execution rather than ahead of time through static\n+analysis.  This is good when runtimes are not known ahead of time or when the\n+execution environment contains uncertainty.  However dynamic scheduling does\n+preclude certain clever optimizations.\n+\n+Dynamic task scheduling has a rich literature and numerous projects, both\n+within the Python ecosystem with projects like Spotify's Luigi_ for bulk data\n+processing and projects without like DAGuE [Bos12]_ for more high performance\n+task scheduling.  Additionally, data parallel systems like Dryad or Spark\n+contain their own custom dynamic task schedulers.\n+\n+Traditional task scheduling with data dependencies scheduling literature\n+usually focuses on policies to expose parallelism or chip away at the critical\n+path.  We find that for bulk data analytics these are not very relevant as\n+parallelism is abundant and critical paths are comparatively short relative to\n+the depth of the graph.\n+\n+The logic behind dask's schedulers reduces to the following situation:  A worker\n+reports that it has completed a task and that it is ready for another.  We\n+update runtime state to record the finished task, mark which new tasks can be\n+run, which data can be released, etc..  We then choose a task to give to this\n+worker from among the set of ready-to-run tasks.  This small choice governs the\n+macro-scale performance of the scheduler.\n+\n+Instead for out-of-core computation we find value in choosing tasks that allow\n+us to release intermediate results and keep a small memory footprint.\n+This lets us avoid spilling intermediate values to disk which hampers\n+performance significantly.\n+\n+After several other policies we find that the policy of *last in, first out* is","path":"papers/matthew_rocklin/matthew_rocklin.rst","position":364,"original_position":364,"commit_id":"42c8b89ede0f72fc338afdebbb087c177bcf1f73","original_commit_id":"42c8b89ede0f72fc338afdebbb087c177bcf1f73","user":{"login":"eteq","id":346587,"avatar_url":"https://avatars.githubusercontent.com/u/346587?v=3","gravatar_id":"","url":"https://api.github.com/users/eteq","html_url":"https://github.com/eteq","followers_url":"https://api.github.com/users/eteq/followers","following_url":"https://api.github.com/users/eteq/following{/other_user}","gists_url":"https://api.github.com/users/eteq/gists{/gist_id}","starred_url":"https://api.github.com/users/eteq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eteq/subscriptions","organizations_url":"https://api.github.com/users/eteq/orgs","repos_url":"https://api.github.com/users/eteq/repos","events_url":"https://api.github.com/users/eteq/events{/privacy}","received_events_url":"https://api.github.com/users/eteq/received_events","type":"User","site_admin":false},"body":"I've lost the thread here: LIFO of *what*?  All of the tasks to be computed?  Or is this a subset that is \"ready-to-run\"?","created_at":"2015-06-28T17:24:33Z","updated_at":"2015-06-28T17:24:33Z","html_url":"https://github.com/scipy-conference/scipy_proceedings/pull/132#discussion_r33424813","pull_request_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132","_links":{"self":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments/33424813"},"html":{"href":"https://github.com/scipy-conference/scipy_proceedings/pull/132#discussion_r33424813"},"pull_request":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132"}}},"pull_request":{"url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132","id":37712016,"html_url":"https://github.com/scipy-conference/scipy_proceedings/pull/132","diff_url":"https://github.com/scipy-conference/scipy_proceedings/pull/132.diff","patch_url":"https://github.com/scipy-conference/scipy_proceedings/pull/132.patch","issue_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/132","number":132,"state":"open","locked":false,"title":"Paper: Dask: Parallel Computation with Blocked algorithms and Task Scheduling (Rocklin)","user":{"login":"mrocklin","id":306380,"avatar_url":"https://avatars.githubusercontent.com/u/306380?v=3","gravatar_id":"","url":"https://api.github.com/users/mrocklin","html_url":"https://github.com/mrocklin","followers_url":"https://api.github.com/users/mrocklin/followers","following_url":"https://api.github.com/users/mrocklin/following{/other_user}","gists_url":"https://api.github.com/users/mrocklin/gists{/gist_id}","starred_url":"https://api.github.com/users/mrocklin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrocklin/subscriptions","organizations_url":"https://api.github.com/users/mrocklin/orgs","repos_url":"https://api.github.com/users/mrocklin/repos","events_url":"https://api.github.com/users/mrocklin/events{/privacy}","received_events_url":"https://api.github.com/users/mrocklin/received_events","type":"User","site_admin":false},"body":"[Edit by jaberg] http://nipy.bic.berkeley.edu:5000/download/24","created_at":"2015-06-15T16:50:03Z","updated_at":"2015-06-28T17:24:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5fc76b5d6ada10e58d3526fcdb9d879fc5e433b3","assignee":{"login":"katyhuff","id":393899,"avatar_url":"https://avatars.githubusercontent.com/u/393899?v=3","gravatar_id":"","url":"https://api.github.com/users/katyhuff","html_url":"https://github.com/katyhuff","followers_url":"https://api.github.com/users/katyhuff/followers","following_url":"https://api.github.com/users/katyhuff/following{/other_user}","gists_url":"https://api.github.com/users/katyhuff/gists{/gist_id}","starred_url":"https://api.github.com/users/katyhuff/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/katyhuff/subscriptions","organizations_url":"https://api.github.com/users/katyhuff/orgs","repos_url":"https://api.github.com/users/katyhuff/repos","events_url":"https://api.github.com/users/katyhuff/events{/privacy}","received_events_url":"https://api.github.com/users/katyhuff/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132/commits","review_comments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132/comments","review_comment_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments{/number}","comments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/132/comments","statuses_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/statuses/42c8b89ede0f72fc338afdebbb087c177bcf1f73","head":{"label":"mrocklin:2015","ref":"2015","sha":"42c8b89ede0f72fc338afdebbb087c177bcf1f73","user":{"login":"mrocklin","id":306380,"avatar_url":"https://avatars.githubusercontent.com/u/306380?v=3","gravatar_id":"","url":"https://api.github.com/users/mrocklin","html_url":"https://github.com/mrocklin","followers_url":"https://api.github.com/users/mrocklin/followers","following_url":"https://api.github.com/users/mrocklin/following{/other_user}","gists_url":"https://api.github.com/users/mrocklin/gists{/gist_id}","starred_url":"https://api.github.com/users/mrocklin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrocklin/subscriptions","organizations_url":"https://api.github.com/users/mrocklin/orgs","repos_url":"https://api.github.com/users/mrocklin/repos","events_url":"https://api.github.com/users/mrocklin/events{/privacy}","received_events_url":"https://api.github.com/users/mrocklin/received_events","type":"User","site_admin":false},"repo":{"id":5024453,"name":"scipy_proceedings","full_name":"mrocklin/scipy_proceedings","owner":{"login":"mrocklin","id":306380,"avatar_url":"https://avatars.githubusercontent.com/u/306380?v=3","gravatar_id":"","url":"https://api.github.com/users/mrocklin","html_url":"https://github.com/mrocklin","followers_url":"https://api.github.com/users/mrocklin/followers","following_url":"https://api.github.com/users/mrocklin/following{/other_user}","gists_url":"https://api.github.com/users/mrocklin/gists{/gist_id}","starred_url":"https://api.github.com/users/mrocklin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrocklin/subscriptions","organizations_url":"https://api.github.com/users/mrocklin/orgs","repos_url":"https://api.github.com/users/mrocklin/repos","events_url":"https://api.github.com/users/mrocklin/events{/privacy}","received_events_url":"https://api.github.com/users/mrocklin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mrocklin/scipy_proceedings","description":"Tools used to generate the SciPy conference proceedings","fork":true,"url":"https://api.github.com/repos/mrocklin/scipy_proceedings","forks_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/forks","keys_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/teams","hooks_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/hooks","issue_events_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/issues/events{/number}","events_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/events","assignees_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/assignees{/user}","branches_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/branches{/branch}","tags_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/tags","blobs_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/git/refs{/sha}","trees_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/statuses/{sha}","languages_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/languages","stargazers_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/stargazers","contributors_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/contributors","subscribers_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/subscribers","subscription_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/subscription","commits_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/commits{/sha}","git_commits_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/git/commits{/sha}","comments_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/comments{/number}","issue_comment_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/issues/comments{/number}","contents_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/contents/{+path}","compare_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/merges","archive_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/downloads","issues_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/issues{/number}","pulls_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/pulls{/number}","milestones_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/milestones{/number}","notifications_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/labels{/name}","releases_url":"https://api.github.com/repos/mrocklin/scipy_proceedings/releases{/id}","created_at":"2012-07-13T20:11:58Z","updated_at":"2014-06-25T07:57:12Z","pushed_at":"2015-06-27T20:19:57Z","git_url":"git://github.com/mrocklin/scipy_proceedings.git","ssh_url":"git@github.com:mrocklin/scipy_proceedings.git","clone_url":"https://github.com/mrocklin/scipy_proceedings.git","svn_url":"https://github.com/mrocklin/scipy_proceedings","homepage":"","size":41667,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"scipy-conference:2015","ref":"2015","sha":"e3df71a5659ce32396b7c520b89109716a3210fa","user":{"login":"scipy-conference","id":4884274,"avatar_url":"https://avatars.githubusercontent.com/u/4884274?v=3","gravatar_id":"","url":"https://api.github.com/users/scipy-conference","html_url":"https://github.com/scipy-conference","followers_url":"https://api.github.com/users/scipy-conference/followers","following_url":"https://api.github.com/users/scipy-conference/following{/other_user}","gists_url":"https://api.github.com/users/scipy-conference/gists{/gist_id}","starred_url":"https://api.github.com/users/scipy-conference/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scipy-conference/subscriptions","organizations_url":"https://api.github.com/users/scipy-conference/orgs","repos_url":"https://api.github.com/users/scipy-conference/repos","events_url":"https://api.github.com/users/scipy-conference/events{/privacy}","received_events_url":"https://api.github.com/users/scipy-conference/received_events","type":"Organization","site_admin":false},"repo":{"id":1825629,"name":"scipy_proceedings","full_name":"scipy-conference/scipy_proceedings","owner":{"login":"scipy-conference","id":4884274,"avatar_url":"https://avatars.githubusercontent.com/u/4884274?v=3","gravatar_id":"","url":"https://api.github.com/users/scipy-conference","html_url":"https://github.com/scipy-conference","followers_url":"https://api.github.com/users/scipy-conference/followers","following_url":"https://api.github.com/users/scipy-conference/following{/other_user}","gists_url":"https://api.github.com/users/scipy-conference/gists{/gist_id}","starred_url":"https://api.github.com/users/scipy-conference/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scipy-conference/subscriptions","organizations_url":"https://api.github.com/users/scipy-conference/orgs","repos_url":"https://api.github.com/users/scipy-conference/repos","events_url":"https://api.github.com/users/scipy-conference/events{/privacy}","received_events_url":"https://api.github.com/users/scipy-conference/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/scipy-conference/scipy_proceedings","description":"Tools used to generate the SciPy conference proceedings","fork":false,"url":"https://api.github.com/repos/scipy-conference/scipy_proceedings","forks_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/forks","keys_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/keys{/key_id}","collaborators_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/teams","hooks_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/hooks","issue_events_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/events{/number}","events_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/events","assignees_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/assignees{/user}","branches_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/branches{/branch}","tags_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/tags","blobs_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/refs{/sha}","trees_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/trees{/sha}","statuses_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/statuses/{sha}","languages_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/languages","stargazers_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/stargazers","contributors_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/contributors","subscribers_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/subscribers","subscription_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/subscription","commits_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/commits{/sha}","git_commits_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/git/commits{/sha}","comments_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/comments{/number}","issue_comment_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/comments{/number}","contents_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/contents/{+path}","compare_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/compare/{base}...{head}","merges_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/merges","archive_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/downloads","issues_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues{/number}","pulls_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls{/number}","milestones_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/milestones{/number}","notifications_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/labels{/name}","releases_url":"https://api.github.com/repos/scipy-conference/scipy_proceedings/releases{/id}","created_at":"2011-05-31T10:26:48Z","updated_at":"2015-06-22T07:22:47Z","pushed_at":"2015-06-28T15:59:09Z","git_url":"git://github.com/scipy-conference/scipy_proceedings.git","ssh_url":"git@github.com:scipy-conference/scipy_proceedings.git","clone_url":"https://github.com/scipy-conference/scipy_proceedings.git","svn_url":"https://github.com/scipy-conference/scipy_proceedings","homepage":"","size":153983,"stargazers_count":76,"watchers_count":76,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":142,"mirror_url":null,"open_issues_count":38,"forks":142,"open_issues":38,"watchers":76,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132"},"html":{"href":"https://github.com/scipy-conference/scipy_proceedings/pull/132"},"issue":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/132"},"comments":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/issues/132/comments"},"review_comments":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132/comments"},"review_comment":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/pulls/132/commits"},"statuses":{"href":"https://api.github.com/repos/scipy-conference/scipy_proceedings/statuses/42c8b89ede0f72fc338afdebbb087c177bcf1f73"}}}}