From 03a320fbd315d48e65166796a9000c07ac835243 2015-04-09 21:31:00 From: Min RK Date: 2015-04-09 21:31:00 Subject: [PATCH] remove ipython_parallel --- diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 9f41a88..b9b4665 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -145,7 +145,7 @@ have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x() # Test suite definitions #----------------------------------------------------------------------------- -test_group_names = ['parallel', 'config', 'core', +test_group_names = ['core', 'extensions', 'lib', 'terminal', 'testing', 'utils', 'qt', 'html', 'nbconvert' ] @@ -171,8 +171,6 @@ class TestSection(object): return self.enabled and all(have[p] for p in self.dependencies) shims = { - 'parallel': 'ipython_parallel', - 'config': 'traitlets', 'html': 'jupyter_notebook', } @@ -219,13 +217,6 @@ if sys.platform == 'win32': if (not have['pexpect']) or (not have['zmq']): test_sections['terminal'].exclude('console') -# parallel -sec = test_sections['parallel'] -sec.requires('zmq') -if not have['pymongo']: - sec.exclude('controller.mongodb') - sec.exclude('tests.test_mongodb') - # extensions: sec = test_sections['extensions'] # This is deprecated in favour of rpy2 diff --git a/IPython/testing/iptestcontroller.py b/IPython/testing/iptestcontroller.py index 6cbc8be..5a3bb73 100644 --- a/IPython/testing/iptestcontroller.py +++ b/IPython/testing/iptestcontroller.py @@ -451,7 +451,6 @@ def prepare_controllers(options): py_testgroups = py_test_group_names if not options.all: js_testgroups = [] - test_sections['parallel'].enabled = False else: js_testgroups = all_js_groups() @@ -558,8 +557,7 @@ def run_iptestall(options): is passed, one process is used per CPU core. Default 1 (i.e. sequential) inc_slow : bool - Include slow tests, like IPython.parallel. By default, these tests aren't - run. + Include slow tests. By default, these tests aren't run. slimerjs : bool Use slimerjs if it's installed instead of phantomjs for casperjs tests. diff --git a/docs/source/parallel/index.rst b/docs/source/parallel/index.rst index 7da57a0..0f30e3f 100644 --- a/docs/source/parallel/index.rst +++ b/docs/source/parallel/index.rst @@ -4,22 +4,4 @@ Using IPython for parallel computing ==================================== -.. toctree:: - :maxdepth: 2 - - parallel_intro - parallel_process - parallel_multiengine - magics - parallel_task - asyncresult - parallel_mpi - parallel_db - parallel_security - parallel_winhpc - parallel_demos - dag_dependencies - parallel_details - parallel_transition - - +IPython.parallel has moved to `ipython_parallel `_. diff --git a/examples/Parallel Computing/Data Publication API.ipynb b/examples/Parallel Computing/Data Publication API.ipynb deleted file mode 100644 index f6077a5..0000000 --- a/examples/Parallel Computing/Data Publication API.ipynb +++ /dev/null @@ -1,2671 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# IPython's Data Publication API" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has an API that allows IPython Engines to publish data back to the Client. This Notebook shows how this API works." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We begin by enabling matplotlib plotting and creating a `Client` object to work with an IPython cluster." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython.parallel import Client" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "c = Client()\n", - "dv = c[:]\n", - "dv.block = False\n", - "dv" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simple publication" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is a simple Python function we are going to run on the Engines. This function uses `publish_data` to publish a simple Python dictionary when it is run." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def publish_it():\n", - " from IPython.kernel.zmq.datapub import publish_data\n", - " publish_data(dict(a='hi'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We run the function on the Engines using `apply_async` and save the returned `AsyncResult` object:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "ar = dv.apply_async(publish_it)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The published data from each engine is then available under the `.data` attribute of the `AsyncResult` object." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'a': 'hi'},\n", - " {'a': 'hi'},\n", - " {'a': 'hi'},\n", - " {'a': 'hi'},\n", - " {'a': 'hi'},\n", - " {'a': 'hi'},\n", - " {'a': 'hi'},\n", - " {'a': 'hi'}]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ar.data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each time `publish_data` is called, the `.data` attribute is updated with the most recently published data." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simulation loop" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In many cases, the Engines will be running a simulation loop and we will want to publish data at each time step of the simulation. To show how this works, we create a mock simulation function that iterates over a loop and publishes a NumPy array and loop variable at each time step. By inserting a call to `time.sleep(1)`, we ensure that new data will be published every second." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def simulation_loop():\n", - " from IPython.kernel.zmq.datapub import publish_data\n", - " import time\n", - " import numpy as np\n", - " for i in range(10):\n", - " publish_data(dict(a=np.random.rand(20), i=i))\n", - " time.sleep(1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, we run the `simulation_loop` function in parallel using `apply_async` and save the returned `AsyncResult` object." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "ar = dv.apply_async(simulation_loop)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "New data will be published by the Engines every second. Anytime we access `ar.data`, we will get the most recently published data." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "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\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "data = ar.data\n", - "for i, d in enumerate(data):\n", - " plt.plot(d['a'], label='engine: '+str(i))\n", - "plt.title('Data published at time step: ' + str(data[0]['i']))\n", - "plt.legend()" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Index.ipynb b/examples/Parallel Computing/Index.ipynb deleted file mode 100644 index ad56fb9..0000000 --- a/examples/Parallel Computing/Index.ipynb +++ /dev/null @@ -1,352 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to the main [Index](../Index.ipynb)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Parallel Computing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython includes an architecture and library for interactive parallel computing. The enables Python functions, along with their arguments, to be run in parallel a multicore CPU, cluster or cloud using a simple Python API." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Tutorials" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [Data Publication API](Data Publication API.ipynb) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [Monitoring an MPI Simulation - 1](Monitoring an MPI Simulation - 1.ipynb)\n", - "* [Monitoring an MPI Simulation - 2](Monitoring an MPI Simulation - 2.ipynb)\n", - "* [Parallel Decorator and map](Parallel Decorator and map.ipynb)\n", - "* [Parallel Magics](Parallel Magics.ipynb)\n", - "* [Using Dill](Using Dill.ipynb)\n", - "* [Using MPI with IPython Parallel](Using MPI with IPython Parallel.ipynb)\n", - "* [Monte Carlo Options](Monte Carlo Options.ipynb)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Non-notebook examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This directory also contains examples that are regular Python (`.py`) files." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "customresults.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/customresults.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "dagdeps.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/dagdeps.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "dependencies.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/dependencies.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "fetchparse.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/fetchparse.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "iopubwatcher.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/iopubwatcher.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "itermapresult.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/itermapresult.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "nwmerge.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/nwmerge.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "phistogram.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/phistogram.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "task_profiler.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/task_profiler.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "throughput.py
" - ], - "text/plain": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/throughput.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%run ../utils/list_pyfiles.ipy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "More substantial examples can be found in subdirectories:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "daVinci Word Count/
\n", - "  pwordfreq.py
\n", - "  wordfreq.py
" - ], - "text/plain": [ - "daVinci Word Count/\n", - " pwordfreq.py\n", - " wordfreq.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "interengine/
\n", - "  bintree.py
\n", - "  bintree_script.py
\n", - "  communicator.py
\n", - "  interengine.py
" - ], - "text/plain": [ - "interengine/\n", - " bintree.py\n", - " bintree_script.py\n", - " communicator.py\n", - " interengine.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [], - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "pi/
\n", - "  parallelpi.py
\n", - "  pidigits.py
" - ], - "text/plain": [ - "pi/\n", - " parallelpi.py\n", - " pidigits.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "rmt/
\n", - "  rmt.ipy
\n", - "  rmt.ipynb
\n", - "  rmtkernel.py
" - ], - "text/plain": [ - "rmt/\n", - " rmt.ipy\n", - " rmt.ipynb\n", - " rmtkernel.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "wave2D/
\n", - "  communicator.py
\n", - "  parallelwave-mpi.py
\n", - "  parallelwave.py
\n", - "  RectPartitioner.py
\n", - "  wavesolver.py
" - ], - "text/plain": [ - "wave2D/\n", - " communicator.py\n", - " parallelwave-mpi.py\n", - " parallelwave.py\n", - " RectPartitioner.py\n", - " wavesolver.py" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%run ../utils/list_subdirs.ipy" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Monitoring an MPI Simulation - 1.ipynb b/examples/Parallel Computing/Monitoring an MPI Simulation - 1.ipynb deleted file mode 100644 index 2bfb190..0000000 --- a/examples/Parallel Computing/Monitoring an MPI Simulation - 1.ipynb +++ /dev/null @@ -1,884 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "# Interactive monitoring of a parallel MPI simulation with the IPython Notebook" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", - "from IPython.display import display\n", - "from IPython.parallel import Client, error\n", - "\n", - "cluster = Client(profile=\"mpi\")\n", - "view = cluster[:]\n", - "view.block = True" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[0, 1, 2, 3]" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cluster.ids" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "Now, we load the MPI libraries into the engine namespaces, and do a simple printing of their MPI rank information to verify that all nodes are operational and they match our cluster's real capacity. \n", - "\n", - "Here, we are making use of IPython's special `%%px` cell magic, which marks the entire cell for parallel execution. This means that the code below will not run in this notebook's kernel, but instead will be sent to *all* engines for execution there. In this way, IPython makes it very natural to control your entire cluster from within the notebook environment:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[stdout:0] MPI rank: 3/4\n", - "[stdout:1] MPI rank: 2/4\n", - "[stdout:2] MPI rank: 0/4\n", - "[stdout:3] MPI rank: 1/4\n" - ] - } - ], - "source": [ - "%%px\n", - "# MPI initialization, library imports and sanity checks on all engines\n", - "from mpi4py import MPI\n", - "import numpy as np\n", - "import time\n", - "\n", - "mpi = MPI.COMM_WORLD\n", - "bcast = mpi.bcast\n", - "barrier = mpi.barrier\n", - "rank = mpi.rank\n", - "print(\"MPI rank: %i/%i\" % (mpi.rank,mpi.size))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "We write a utility that reorders a list according to the mpi ranks of the engines, since all gather operations will return data in engine id order, not in MPI rank order. We'll need this later on when we want to reassemble in IPython data structures coming from all the engines: IPython will collect the data ordered by engine ID, but our code creates data structures based on MPI rank, so we need to map from one indexing scheme to the other. This simple function does the job:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "ranks = view['rank']\n", - "rank_indices = np.argsort(ranks)\n", - "\n", - "def mpi_order(seq):\n", - " \"\"\"Return elements of a sequence ordered by MPI rank.\n", - "\n", - " The input sequence is assumed to be ordered by engine ID.\"\"\"\n", - " return [seq[x] for x in rank_indices]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "## MPI simulation example" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "This is our 'simulation', a toy example that computes $\\sin(f(x^2+y^2))$ for a slowly increasing frequency $f$ over a gradually refined mesh. In a real-world example, there typically is a 'simulate' method that, afer setting up initial parameters, runs the entire computation. But having this simple example will be sufficient to see something that changes visually as the computation evolves and that is quick enough for us to test.\n", - "\n", - "And while simple, this example has a realistic decomposition of the spatial domain in one array per MPI node that requires care in reordering the data for visualization, as would be needed in a real-world application (unless your code accumulates data in the rank 0 node that you can grab directly)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "%%px\n", - "\n", - "stop = False\n", - "nsteps = 100\n", - "delay = 0.1\n", - "\n", - "xmin, xmax = 0, np.pi\n", - "ymin, ymax = 0, 2*np.pi\n", - "dy = (ymax-ymin)/mpi.size\n", - "\n", - "def simulation():\n", - " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n", - " over an increasingly fine mesh.\n", - "\n", - " The purpose of this code is simply to illustrate the basic features of a typical\n", - " MPI code: spatial domain decomposition, a solution which is evolving in some \n", - " sense, and local per-node computation. In this case the nodes don't really\n", - " communicate at all.\n", - " \"\"\"\n", - " # By making these few variables global, we allow the IPython client to access them\n", - " # remotely for interactive introspection\n", - " global j, Z, nx, nyt\n", - " freqs = np.linspace(0.6, 1, nsteps)\n", - " for j in range(nsteps):\n", - " nx, ny = 2+j/4, 2+j/2/mpi.size\n", - " nyt = mpi.size*ny\n", - " Xax = np.linspace(xmin, xmax, nx)\n", - " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n", - " X, Y = np.meshgrid(Xax, Yax)\n", - " f = freqs[j]\n", - " Z = np.cos(f*(X**2 + Y**2))\n", - " # We add a small delay to simulate that a real-world computation\n", - " # would take much longer, and we ensure all nodes are synchronized\n", - " time.sleep(delay)\n", - " # The stop flag can be set remotely via IPython, allowing the simulation to be\n", - " # cleanly stopped from the outside\n", - " if stop:\n", - " break" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "## IPython tools to interactively monitor and plot the MPI results" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "We now define a local (to this notebook) plotting function that fetches data from the engines' global namespace. Once it has retrieved the current state of the relevant variables, it produces and returns a figure:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "from IPython.display import clear_output\n", - "\n", - "def plot_current_results(in_place=True):\n", - " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n", - " as a contour plot.\n", - " \n", - " Parameters\n", - " ----------\n", - " in_place : bool\n", - " By default it calls clear_output so that new plots replace old ones. Set\n", - " to False to allow keeping of all previous outputs.\n", - " \"\"\"\n", - " \n", - " # We make a blocking call to load the remote data from the simulation into simple named \n", - " # variables we can read from the engine namespaces\n", - " #view.apply_sync(load_simulation_globals)\n", - " # And now we can use the view to read these variables from all the engines. Then we\n", - " # concatenate all of them into single arrays for local plotting\n", - " try:\n", - " Z = np.concatenate(mpi_order(view['Z']))\n", - " except ValueError:\n", - " print(\"dimension mismatch in Z, not plotting\")\n", - " ax = plt.gca()\n", - " return ax.figure\n", - " \n", - " nx, nyt, j, nsteps = view.pull(['nx', 'nyt', 'j', 'nsteps'], targets=0)\n", - " fig, ax = plt.subplots()\n", - " ax.contourf(Z)\n", - " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n", - " plt.axis('off')\n", - " # We clear the notebook output before plotting this if in-place plot updating is requested\n", - " if in_place:\n", - " clear_output(wait=True)\n", - " display(fig)\n", - " return fig" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "It will also be useful to be able to check whether the simulation is still alive or not. Below we will wrap the main simulation function into a thread to allow IPython to pull data from the engines, and we will call this object `simulation_thread`. So to check whether the code is still running, all we have to do is call the `is_alive` method on all of our engines and see whether any of them returns True:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "def simulation_alive():\n", - " \"\"\"Return True if the simulation thread is still running on any engine.\n", - " \"\"\"\n", - " return any(view.apply_sync(lambda : simulation_thread.is_alive()))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "Finally, this is a convenience wrapper around the plotting code so that we can interrupt monitoring at any point, and that will provide basic timing information:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "def monitor_simulation(refresh=5.0, plots_in_place=True):\n", - " \"\"\"Monitor the simulation progress and call plotting routine.\n", - "\n", - " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n", - " figure is always displayed and provide basic timing and simulation status.\n", - "\n", - " Parameters\n", - " ----------\n", - " refresh : float\n", - " Refresh interval between calls to retrieve and plot data. The default\n", - " is 5s, adjust depending on the desired refresh rate, but be aware that \n", - " very short intervals will start having a significant impact.\n", - "\n", - " plots_in_place : bool\n", - " If true, every new figure replaces the last one, producing a (slow)\n", - " animation effect in the notebook. If false, all frames are plotted\n", - " in sequence and appended in the output area.\n", - " \"\"\"\n", - " import datetime as dt, time\n", - " \n", - " if not simulation_alive():\n", - " plot_current_results(in_place=plots_in_place)\n", - " plt.close('all')\n", - " print('Simulation has already finished, no monitoring to do.')\n", - " return\n", - " \n", - " t0 = dt.datetime.now()\n", - " fig = None\n", - " try:\n", - " while simulation_alive():\n", - " fig = plot_current_results(in_place=plots_in_place)\n", - " plt.close('all') # prevent re-plot of old figures\n", - " time.sleep(refresh) # so we don't hammer the server too fast\n", - " except (KeyboardInterrupt, error.TimeoutError):\n", - " msg = 'Monitoring interrupted, simulation is ongoing!'\n", - " else:\n", - " msg = 'Simulation completed!'\n", - " tmon = dt.datetime.now() - t0\n", - " if plots_in_place and fig is not None:\n", - " clear_output(wait=True)\n", - " plt.close('all')\n", - " display(fig)\n", - " print(msg)\n", - " print('Monitored for: %s.' % tmon)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "## Making a simulation object that can be monitored interactively" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "%%px\n", - "from threading import Thread\n", - "stop = False\n", - "nsteps = 100\n", - "delay=0.5\n", - "# Create a thread wrapper for the simulation. The target must be an argument-less\n", - "# function so we wrap the call to 'simulation' in a simple lambda:\n", - "simulation_thread = Thread(target = lambda : simulation())\n", - "# Now we actually start the simulation\n", - "simulation_thread.start()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [ - { - "data": { - "image/png": [ - "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGKCAYAAAAomMSSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", - "AAALEgAACxIB0t1+/AAAIABJREFUeJztvXvwnkV5//9+UkNOQAgQKAxnQjgIhFA5lNgQUvyGgA0e\n", - "IAK1lFP40VoYEAsjh2GnxVq0RenUEUJQUQtKZ9IhQGxEAgQDgi0QkEkrIVBAwikRwYSEQPb3R/g8\n", - "+Tyf53Tvfe/huvZ+v2Yc5Xn23t1g9vq8Ptde927DWmtBCCGEEEJKMSz1BAghhBBCNEOZIoQQQgip\n", - "AGWKEEIIIaQClClCCCGEkApQpgghhBBCKkCZIoQQQgipAGWKEGW88MIL2HvvvVNPgwhk06ZNqadA\n", - "SC2hTBFSAWMMhg0bhm9+85td28yfPx/Dhg3D2WefHXFmxXj22Wdx8sknY9y4cdhll11w1lln4bXX\n", - "XuvY9oknnsCIESPwq1/9yvs8pk2bhmHDhrX85w/+4A86tn3llVewzTbb4Cc/+Yn3ecRm3rx52G+/\n", - "/TBixAhMnDgRt956a8v3GzduxD/+4z9izz33xMiRIzFp0iTcc889Xfu77rrr8NWvfrXlsx/96EcY\n", - "OXIkFi9e3Nb+nXfewbnnnovx48dj3LhxOOWUU/Dqq6+2tFm1ahU++9nPYty4cRg/fjzOO+88rF27\n", - "tsKfmpD8oEwRUpFRo0bhlltu6fr9vHnzMHr0aDQajYiz6s/bb7+N448/HrvtthtWrFiBJ554Au+/\n", - "/z4+9alPdWx/0UUX4eyzz8bBBx/sfS6NRgPz5s3Dpk2bmv/54IMPOra97LLLMGXKFMycOdP7PGJy\n", - "3XXX4brrrsPNN9+MNWvWYO7cubj22mvxr//6r802F110EebPn4/58+dj9erV+Pu//3ucc845uPvu\n", - "u9v6s9billtuwcknn9z87Otf/zr+5m/+BiNHjuw4h1NPPRW///3v8fTTT2PlypXYddddccIJJzT/\n", - "3b/33nuYMWMGdt99dzz//PN46qmn8Pbbb2P27Nme/20QohxLCCmNMcZ++tOftmPHjrWPPPJI2/cv\n", - "vfSS3WqrreyZZ55pzzrrLC9jPv/883avvfaq3M+9995rp06d2vLZ+vXr7YgRI+yyZctaPr/tttvs\n", - "tttua19//fXK43Zi2rRp9pZbbunbbunSpXb48OH2mWee8T6H+++/306bNs17v51477337NZbb22X\n", - "LFnS8vmDDz5ot9tuO/v+++/b3/zmN3bYsGH2xRdfbGlz66232oMOOqitz5/+9Kd2ypQpzX/+/ve/\n", - "b3fZZRf71FNP2b322sved999Le0feughu/POO9v169e3fH7wwQfb22+/3Vpr7Q9+8AN72GGHtXy/\n", - "bt06O378+I5/3wmpK8xMEVKRMWPG4IwzzsC8efPavvvOd76D448/HnvssUeCmfXm+OOPx4MPPtjy\n", - "2YgRIzBq1KiW2pu1a9fisssuwxVXXIHx48cHm4/tc7PVpk2bcNFFF+Hcc8/FQQcdFGweMXjjjTew\n", - "du1aTJ48ueXzww8/HL/73e/w6quv4oUXXsB2222H3Xffva3N//7v/7b1OXfuXJx//vnNf549ezYe\n", - "fvhhHHLIIR3nsGjRIhx//PEYMWJEy+czZ85sbqEuWrQIJ510Usv3o0aNwrRp07LYZiXEF5QpQjww\n", - "Z84c3HHHHS21JNZafO9732v5ATeYX/7yl5gyZQpGjRqF3XbbDddee23L1tY999yDQw89FKNHj8ah\n", - "hx6K++67r+X5JUuW4GMf+xi23nprfOxjH8PDDz/c8v2RRx6JL37xi05/joHtowMPPLD52Ve/+lV8\n", - "5CMfwcUXX1yoj9NPPx3Tpk1rytH69etxwAEH4Oabb+75nDEGo0ePxvjx43H55Zfj/fffb/n+O9/5\n", - "Dp599ln83d/9ncsfqYVHH30Uf/zHf4wxY8Zg4sSJ+NGPfgQA2GuvvTB9+nQ8+OCDGDZsGPbZZ5/m\n", - "M88++yxmzpyJMWPGYKeddsIll1yCdevWNb+fNm0a7r77blx++eXYbbfdMHr0aEydOhWPPvpo13ns\n", - "uOOOGD16NJ588smWz5944gkAwLbbbos99tgDb731Fl5++eW2NmPHjm357PXXX8eSJUvwuc99rvnZ\n", - "iBEjsNdee3Wdw8qVKzFp0qS2zydNmoTnnnuuZ5vDDjus2YYQQpkipBLWWjQaDUyePBkTJ07E7bff\n", - "3vzu3nvvxYYNG/DJT36yLevyi1/8AqeddhquvPJKrF69GkuXLsXy5cvxhS98AQCwZs0anHrqqfin\n", - "f/onvPXWWzj33HNbZOTNN9/EVVddhVtuuQWrVq3CKaecgtmzZ+O9995rttl///2dMmIvvfQS5syZ\n", - "gy9/+cvNbMVrr72Gf/7nf8aIESMwYcIE7L777vjSl76Ed999t2s/c+fOxUsvvYTrr78eAHDNNddg\n", - "v/32w5w5c7o+c/rpp+Oee+7Bm2++ifnz5+POO+/EBRdc0Px+w4YNuPrqq7H99tvj6KOPxi677IJz\n", - "zz0Xa9asKfzns9Zi1qxZOP/887FmzRp85StfwY033ohNmzbhhRdewP33349jjz0WmzZtwsqVKwFs\n", - "fnPy+OOPx5lnnolXX30VTz/9NBqNBj7zmc80+200GrjwwguxYcMGPPTQQ3jllVdw3nnnYebMmbjz\n", - "zjs7zmWrrbbCZZddhjlz5uDhhx/GunXr8Mgjj2DOnDk4+uijsc0222C33XbDX/7lX2L27Nl4+umn\n", - "sXbtWixatAiXXXYZZsyY0dLfd7/7XXzuc59ryzL14re//S223Xbbts+322675r/Xbm3Gjh3r9O+e\n", - "kOxJucdIiHauueYa+/nPf95aa+2NN95ojzrqqOZ3p556qr3iiiustdZeeeWVLTVTRx99tL3//vtb\n", - "+tqwYYPdeuut7erVq+2yZcvsmDFj7FtvvdU25vPPP28bjYZdvnx5y+e77rpr6VqiN9980x544IH2\n", - "z/7sz1o+v/LKK+3w4cOtMcY+/vjjduHChfbwww+3J510Us/+Hn30UbvNNtvYW265xf7hH/6hfe21\n", - "15zm86tf/coOHz7cPvfcc9Zaa2+++WbbaDTsRRddZB977DG7ePFi+6d/+qf2sMMOsxs3bizU51tv\n", - "vWWHDRtmn3322Y7fd6qZOu200+x3v/vdtrb77bef/e///m9rrbXHHnusPf3009va3HTTTXbixIld\n", - "57Np0yZ7/fXX2913392OGDHC7r///rbRaNj58+c322zYsMFeccUVdqeddrKjRo2yEyZMsMOGDbOP\n", - "P/54Sz/77befffrpp7uO1almaubMmfamm25qa3vPPffYAw880Fpr7YEHHmgXLVrU1uZb3/qWnTlz\n", - "ZtfxCKkbzEwR4onTTz8dzzzzDJ555hm8+eabuOuuu3Deeee1tdu4cSMeffRRTJ8+veUogJEjR2Ld\n", - "unVYvnw5DjnkEEyfPh0TJkzA2WefjR/+8IfYsGFDs48dd9wRBxxwQEu/e++9N1avXu0877Vr1+Kk\n", - "k07CjjvuiDvuuKPlu0WLFuGSSy7BNddcg8mTJ2PmzJn4z//8Tzz44IN45JFHuvZ55JFH4gtf+ALO\n", - "O+88XHfdddhpp52c5vTRj34U++yzDx577LHmPE499VTccMMNOOKII3Dcccfh7rvvxm9/+1v8+7//\n", - "e6E+x44diwsuuACHH344TjvtNMydOxdvv/12z2ceeughnHPOOW3HNjz33HNYvnw5gM2ZqdNPP73t\n", - "2U996lN49tln8cYbb3Tsu9Fo4JJLLsGLL76I9evXY+rUqTjmmGPw6U9/utlmq622wle+8hW89tpr\n", - "WLduHfbZZx+cdtppLbVWixcvxo477uj8luW4cePwu9/9ru3zt956CzvssEPfNttvv73TeITkDGWK\n", - "EE9su+22mD17Nm6++WZ8//vfx8c//vHm4ZpDj0VoNBp4+umnW44CGDgOYMqUKWg0GliwYAHuuusu\n", - "7LvvvviHf/gHTJ06tVlHNGbMmLbxhw8f3reIeygbN27EKaecgk2bNmHhwoVtr9C//fbb+PjHP97y\n", - "2fjx43HAAQf0rJl59913sWDBAhxyyCH4t3/7N6c5DbDVVls1z5rqNI+RI0fiiCOOwIoVKwr3+a1v\n", - "fQs///nPccQRR2DevHmYPHlyR1kYYNiwYbjrrrs6/v/053/+5812rv/eh/LUU0/he9/7Hv7lX/6l\n", - "a5u7774bS5cuxde//vWWz+fOndtzC7Ub++67L5YtW9b2+ZNPPol99923b5sJEyY4j0lIrlCmCKnA\n", - "UEmaM2cOfvCDH2DevHldf8ANHz4cxxxzDH74wx+2fP7BBx/gmWeeaf7z+++/j6OPPhpXXXUVli1b\n", - "huXLl+Opp57yNndrLc466yz85je/waJFi7D11lu3tdl///1b5gQA69atw8qVK7Hnnnt27fvSSy/F\n", - "Rz/6UTz00EP49a9/ja997Wtd2z733HP49re/3fLZypUrsWLFChx11FFd57Fp0yY888wzPYush/L+\n", - "++/j0EMPxaWXXorHHnsMw4cPbxb2NxqNthPEjz322Lb/nwC0CIa1Fj/+8Y/b2tx5552YOHFioTcg\n", - "L774Ypx11lk4/PDDO36/ceNGfOlLX8IVV1yBXXfdtfn5G2+8gfvvvx+nnXZa3zGGcsIJJ+BnP/sZ\n", - "1q9f3/JnWbhwYfMMrxNOOKHtTKt169bhgQceUH/OFyFeSbrJSIhyBtdMDXDwwQfb8ePH2/fee6/5\n", - "2dCaqV/+8pd2zJgx9pvf/KZdvXq1ff755+3s2bPtjBkzrLXWPvDAA3a//fazTz75pH333XftHXfc\n", - "YUePHm1fe+21rudMTZs2zT7wwAPNf/785z9vv/GNb3Sd+5e//GU7YcIEu2rVqq5tHn/8cbv99tvb\n", - "H//4x/btt9+2K1assCeffLL9xCc+0fWZBQsW2F122cWuXr3aWmvtz3/+cztq1Cj72GOPdWz/i1/8\n", - "wm611Vb2G9/4hn3nnXfs448/bidPnmwvv/zyZpuXX37Z7rDDDvbb3/62XbNmjX355Zft+eefbw8+\n", - "+GC7YcOGZrtTTz3V/tVf/VXHcX7961/b3XbbzT7wwAN2/fr19r777rPbbLONffLJJ6211v7P//yP\n", - "3Xnnne3rr79u/+///s9aa+2LL75ox44da6+44gr76quv2lWrVtkLL7zQHnTQQfaDDz6w1m6umdpz\n", - "zz3txRdfbFeuXGnXrFljb731Vrv99tvbO++8s+u/pwHmz59vt9tuO/vGG290bXP99dfbffbZp+XP\n", - "aq21X/va1+xf//Vf9x2jU82UtZvrpmbPnm1XrVplV69ebS+88EI7efLk5p9t48aN9tBDD7UXXXSR\n", - "XbNmjX3llVfsKaecYj/5yU/2HZOQOkGZIqQCxhj7F3/xFy2f3XDDDfbSSy9t+eyqq66yZ599dstn\n", - "//Vf/2X/5E/+xI4cOdLuvPPO9sILL7TvvPNO8/trr73W7rHHHnbkyJH2j/7oj+y9995rrd1cgL73\n", - "3nu3zWXatGn2wQcfbP7zEUccYS+55JKuc582bZodNmyYbTQabf+59dZbm+0efvhhe8wxx9iRI0fa\n", - "nXbayV5yySUt8xzMK6+8YnfaaSd7zz33tHx+9dVX2wkTJtjf//73HZ+799577ZFHHmlHjRpl99xz\n", - "T3vDDTe0tVm+fLmdMWOGHT16tB03bpw9++yzWwrbN23aZHfccUf7s5/9rOufee7cuXbixIl25MiR\n", - "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\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation completed!\n", - "Monitored for: 0:00:50.653178.\n" - ] - } - ], - "source": [ - "monitor_simulation(refresh=1);" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_start": false - } - }, - "source": [ - "If you execute the following cell before the MPI code is finished running, it will stop the simulation at that point, which you can verify by calling the monitoring again:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false, - "slideshow": { - "slide_start": false - } - }, - "outputs": [], - "source": [ - "view['stop'] = True" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"stdin_port\": 65310, \n", - " \"ip\": \"127.0.0.1\", \n", - " \"control_port\": 58188, \n", - " \"hb_port\": 58187, \n", - " \"key\": \"e4f5cda8-faa8-48d3-a62c-dbde67db9827\", \n", - " \"shell_port\": 65083, \n", - " \"transport\": \"tcp\", \n", - " \"iopub_port\": 54934\n", - "}\n", - "\n", - "Paste the above JSON into a file, and connect with:\n", - " $> ipython --existing \n", - "or, if you are local, you can connect with just:\n", - " $> ipython --existing kernel-64604.json \n", - "or even just:\n", - " $> ipython --existing \n", - "if this is the most recent IPython session you have started.\n" - ] - } - ], - "source": [ - "%%px --target 0\n", - "from IPython.parallel import bind_kernel; bind_kernel()\n", - "%connect_info" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px --target 0\n", - "%qtconsole" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Monitoring an MPI Simulation - 2.ipynb b/examples/Parallel Computing/Monitoring an MPI Simulation - 2.ipynb deleted file mode 100644 index 7f82c42..0000000 --- a/examples/Parallel Computing/Monitoring an MPI Simulation - 2.ipynb +++ /dev/null @@ -1,533 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Interactive visualization of MPI simulaitons" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this example, which builds on our previous one of interactive MPI monitoring, we now demonstrate how to use the IPython data publication APIs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Load IPython support for working with MPI tasks" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you have not done so yet, use [the cluster tab in the Dashboard](/#tab2) to start your `mpi` cluster, it should be OK to leave the number of engines field empty (IPython will auto-detect the number of cores on your machine), unless you want to limit the run to use less cores than available in total. Once your MPI cluster is running, you can proceed with the rest of the code.\n", - "\n", - "We begin by creating a cluster client that gives us a local handle on the engines running in the (possibly remote) MPI cluster. From the client we make a `view` object, which we set to use blocking mode by default as it is more convenient for interactive control. Since the real computation will be done over MPI without IPython intervention, setting the default behavior to be blocking will have no significant performance impact.\n", - "\n", - "**Note:** if on first try the following cell gives you an error message, wait a few seconds and run it again. It's possible that the system is simply initializing all your MPI engines, which may take a bit of time to be completely ready if you hadn't used any MPI libraries recently and the disk cache is cold." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython.parallel import Client, error\n", - "cluster = Client(profile=\"mpi\")\n", - "view = cluster[:]\n", - "view.block = True" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's also load the plotting and numerical libraries so we have them ready for visualization later on." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we load the MPI libraries into the engine namespaces, and do a simple printing of their MPI rank information to verify that all nodes are operational and they match our cluster's real capacity. \n", - "\n", - "Here, we are making use of IPython's special `%%px` cell magic, which marks the entire cell for parallel execution. This means that the code below will not run in this notebook's kernel, but instead will be sent to *all* engines for execution there. In this way, IPython makes it very natural to control your entire cluster from within the notebook environment:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[stdout:0] MPI rank: 2/4\n", - "[stdout:1] MPI rank: 0/4\n", - "[stdout:2] MPI rank: 3/4\n", - "[stdout:3] MPI rank: 1/4\n" - ] - } - ], - "source": [ - "%%px\n", - "# MPI initialization, library imports and sanity checks on all engines\n", - "from mpi4py import MPI\n", - "# Load data publication API so engines can send data to notebook client\n", - "from IPython.kernel.zmq.datapub import publish_data\n", - "import numpy as np\n", - "import time\n", - "\n", - "mpi = MPI.COMM_WORLD\n", - "bcast = mpi.bcast\n", - "barrier = mpi.barrier\n", - "rank = mpi.rank\n", - "print(\"MPI rank: %i/%i\" % (mpi.rank,mpi.size))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We write a utility that reorders a list according to the mpi ranks of the engines, since all gather operations will return data in engine id order, not in MPI rank order. We'll need this later on when we want to reassemble in IPython data structures coming from all the engines: IPython will collect the data ordered by engine ID, but our code creates data structures based on MPI rank, so we need to map from one indexing scheme to the other. This simple function does the job:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "ranks = view['rank']\n", - "engine_mpi = np.argsort(ranks)\n", - "\n", - "def mpi_order(seq):\n", - " \"\"\"Return elements of a sequence ordered by MPI rank.\n", - "\n", - " The input sequence is assumed to be ordered by engine ID.\"\"\"\n", - " return [seq[x] for x in engine_mpi]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## MPI simulation example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is our 'simulation', a toy example that computes $\\sin(f(x^2+y^2))$ for a slowly increasing frequency $f$ over a gradually refined mesh. In a real-world example, there typically is a 'simulate' method that, afer setting up initial parameters, runs the entire computation. But having this simple example will be sufficient to see something that changes visually as the computation evolves and that is quick enough for us to test.\n", - "\n", - "And while simple, this example has a realistic decomposition of the spatial domain in one array per MPI node that requires care in reordering the data for visualization, as would be needed in a real-world application (unless your code accumulates data in the rank 0 node that you can grab directly)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px\n", - "\n", - "# Global flag in the namespace\n", - "stop = False\n", - "\n", - "def simulation(nsteps=100, delay=0.1):\n", - " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n", - " over an increasingly fine mesh.\n", - "\n", - " The purpose of this code is simply to illustrate the basic features of a typical\n", - " MPI code: spatial domain decomposition, a solution which is evolving in some \n", - " sense, and local per-node computation. In this case the nodes only communicate when \n", - " gathering results for publication.\"\"\"\n", - " # Problem geometry\n", - " xmin, xmax = 0, np.pi\n", - " ymin, ymax = 0, 2*np.pi\n", - " dy = (ymax-ymin)/mpi.size\n", - "\n", - " freqs = np.linspace(0.6, 1, nsteps)\n", - " for j in range(nsteps):\n", - " nx, ny = 2+j/4, 2+j/2/mpi.size\n", - " nyt = mpi.size*ny\n", - " Xax = np.linspace(xmin, xmax, nx)\n", - " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n", - " X, Y = np.meshgrid(Xax, Yax)\n", - " f = freqs[j]\n", - " Z = np.cos(f*(X**2 + Y**2))\n", - " \n", - " # We are now going to publish data to the clients. We take advantage of fast\n", - " # MPI communications and gather the Z mesh at the rank 0 node in the Zcat variable:\n", - " Zcat = mpi.gather(Z, root=0)\n", - " if mpi.rank == 0:\n", - " # Then we use numpy's concatenation to construct a single numpy array with the\n", - " # full mesh that can be sent to the client for visualization:\n", - " Zcat = np.concatenate(Zcat)\n", - " # We now can send a dict with the variables we want the client to have access to:\n", - " publish_data(dict(Z=Zcat, nx=nx, nyt=nyt, j=j, nsteps=nsteps))\n", - " \n", - " # We add a small delay to simulate that a real-world computation\n", - " # would take much longer, and we ensure all nodes are synchronized\n", - " time.sleep(delay)\n", - " # The stop flag can be set remotely via IPython, allowing the simulation to be\n", - " # cleanly stopped from the outside\n", - " if stop:\n", - " break" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## IPython tools to interactively monitor and plot the MPI results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now define a local (to this notebook) plotting function that fetches data from the engines' global namespace. Once it has retrieved the current state of the relevant variables, it produces and returns a figure:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython.display import display, clear_output\n", - "\n", - "def plot_current_results(ar, in_place=True):\n", - " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n", - " as a contour plot.\n", - " \n", - " Parameters\n", - " ----------\n", - " ar : async result object\n", - "\n", - " in_place : bool\n", - " By default it calls clear_output so that new plots replace old ones. Set\n", - " to False to allow keeping of all previous outputs.\n", - " \"\"\"\n", - " # Read data from MPI rank 0 engine\n", - " data = ar.data[engine_mpi[0]]\n", - " \n", - " try:\n", - " nx, nyt, j, nsteps = [data[k] for k in ['nx', 'nyt', 'j', 'nsteps']]\n", - " Z = data['Z']\n", - " except KeyError:\n", - " # This can happen if we read from the engines so quickly that the data \n", - " # hasn't arrived yet.\n", - " fig, ax = plt.subplots()\n", - " ax.plot([])\n", - " ax.set_title(\"No data yet\")\n", - " display(fig)\n", - " return fig\n", - " else:\n", - " \n", - " fig, ax = plt.subplots()\n", - " ax.contourf(Z)\n", - " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n", - " plt.axis('off')\n", - " # We clear the notebook output before plotting this if in-place \n", - " # plot updating is requested\n", - " if in_place:\n", - " clear_output(wait=True)\n", - " display(fig)\n", - " \n", - " return fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, this is a convenience wrapper around the plotting code so that we can interrupt monitoring at any point, and that will provide basic timing information:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def monitor_simulation(ar, refresh=5.0, plots_in_place=True):\n", - " \"\"\"Monitor the simulation progress and call plotting routine.\n", - "\n", - " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n", - " figure is always displayed and provide basic timing and simulation status.\n", - "\n", - " Parameters\n", - " ----------\n", - " ar : async result object\n", - "\n", - " refresh : float\n", - " Refresh interval between calls to retrieve and plot data. The default\n", - " is 5s, adjust depending on the desired refresh rate, but be aware that \n", - " very short intervals will start having a significant impact.\n", - "\n", - " plots_in_place : bool\n", - " If true, every new figure replaces the last one, producing a (slow)\n", - " animation effect in the notebook. If false, all frames are plotted\n", - " in sequence and appended in the output area.\n", - " \"\"\"\n", - " import datetime as dt, time\n", - " \n", - " if ar.ready():\n", - " plot_current_results(ar, in_place=plots_in_place)\n", - " plt.close('all')\n", - " print('Simulation has already finished, no monitoring to do.')\n", - " return\n", - " \n", - " t0 = dt.datetime.now()\n", - " fig = None\n", - " try:\n", - " while not ar.ready():\n", - " fig = plot_current_results(ar, in_place=plots_in_place)\n", - " plt.close('all') # prevent re-plot of old figures\n", - " time.sleep(refresh)\n", - " except (KeyboardInterrupt, error.TimeoutError):\n", - " msg = 'Monitoring interrupted, simulation is ongoing!'\n", - " else:\n", - " msg = 'Simulation completed!'\n", - " tmon = dt.datetime.now() - t0\n", - " if plots_in_place and fig is not None:\n", - " clear_output(wait=True)\n", - " plt.close('all')\n", - " display(fig)\n", - " print(msg)\n", - " print('Monitored for: %s.' % tmon)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Interactive monitoring in the client of the published data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can monitor the published data. We submit the simulation for execution as an asynchronous task, and then monitor this task at any frequency we desire." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# Create the local client that controls our IPython cluster with MPI support\n", - "from IPython.parallel import Client\n", - "cluster = Client(profile=\"mpi\")\n", - "# We make a view that encompasses all the engines\n", - "view = cluster[:]\n", - "# And now we call on all available nodes our simulation routine,\n", - "# as an asynchronous task\n", - "ar = view.apply_async(lambda : simulation(nsteps=10, delay=0.1))" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "image/png": [ - "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\n", - "LIsHHngg3n333c2WWb58eaxYsSIOO+yw5Pltqa2LwrfUVpg1NDTE4MGDt3rg5YMPPhijR4/e7l18\n", - "QMcJJ+hCWx6xGDFiRAwYMCBWr14dX/nKV9pd7uc//3lcf/318Ytf/CLWrFkTr7zySpx++ukxZcqU\n", - "iIh44oknYtiwYdHU1BTvvvtu3HvvvbFp06btxsb/bmfChAnbfKL51KlTY8mSJbFgwYLo3bt30tf7\n", - "17/+Na6++uqYO3dudOvWLebNmxe33XZb3HvvvW0u//nPfz4OO+ywmDBhQqxcuTLWrl0bN9xwQ9x/\n", - "//1x0UUXtS43ffr0GDZsWJtjNDc3x4EHHhjTp0+PDRs2xAsvvBBPP/107LPPPq3L9O/fP5588slY\n", - "s2ZNrF+/Ps4666zo06dPnHzyyfHCCy/Ev//977jpppti9OjR8fLLL7eu9/7778cJJ5wQzz33XPzn\n", - "P/+Jxx9/PMaOHRuTJ0+OPffcM+l7sqU33ngjFi9eHKeccso2l2vvaNdll10WkydPjqeffjo2bNgQ\n", - "Dz74YFx11VXR2NjYqfkA2yacoAsVCoU2jzp9/etfj1122aXd5Q4++OB44okn4u67744999wzRowY\n", - "EXvssUfcddddEfHBEZMzzzwzTjzxxOjdu3dcc801cd9990W/fv1ax2tvPh/6xz/+0XprflsWL14c\n", - "L7/8cgwYMGCra4Hmzp271fLr16+Pr33ta3H55Ze3PpdoyJAhcd1118U555wTr7/+epvbmT9/fgwe\n", - "PDgOP/zwGDx4cNx3333x+OOPR0NDQ+syjz32WLt3n+28885xww03xK233hq77757fPGLX4wxY8bE\n", - "5MmTW5e59NJL4/jjj49Pf/rT8eabb0ZdXV0sWrQoBg0aFCNHjoz+/fvH/Pnz45FHHom999679Xs1\n", - "YcKEGDduXJx22mnRp0+fOO+88+KCCy6Ia6+9tt3v25a2/FnMmTMnxo8fv92jQ239txMRcdppp8UV\n", - "V1zRGn/Tpk2LefPmxaGHHpo8JyBdIevoRQ4AJdTS0hJ9+/aNhQsXxoEHHthl27300kujubk5Lrvs\n", - "slzHXbt2bWzatCn69OmT67hAcexc6gkAdMRf/vKXqK+v79JoKqb6+vpSTwHoAKfqgIpyyCGHbHbd\n", - "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\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation completed!\n", - "Monitored for: 0:00:01.229672.\n" - ] - } - ], - "source": [ - "monitor_simulation(ar, refresh=1)" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Monte Carlo Options.ipynb b/examples/Parallel Computing/Monte Carlo Options.ipynb deleted file mode 100644 index 9c7af67..0000000 --- a/examples/Parallel Computing/Monte Carlo Options.ipynb +++ /dev/null @@ -1,2557 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Parallel Monto-Carlo options pricing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook shows how to use `IPython.parallel` to do Monte-Carlo options pricing in parallel. We will compute the price of a large number of options for different strike prices and volatilities." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Problem setup" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import sys\n", - "import time\n", - "from IPython.parallel import Client\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here are the basic parameters for our computation." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "price = 100.0 # Initial price\n", - "rate = 0.05 # Interest rate\n", - "days = 260 # Days to expiration\n", - "paths = 10000 # Number of MC paths\n", - "n_strikes = 6 # Number of strike values\n", - "min_strike = 90.0 # Min strike price\n", - "max_strike = 110.0 # Max strike price\n", - "n_sigmas = 5 # Number of volatility values\n", - "min_sigma = 0.1 # Min volatility\n", - "max_sigma = 0.4 # Max volatility" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "strike_vals = np.linspace(min_strike, max_strike, n_strikes)\n", - "sigma_vals = np.linspace(min_sigma, max_sigma, n_sigmas)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Strike prices: [ 90. 94. 98. 102. 106. 110.]\n", - "Volatilities: [ 0.1 0.175 0.25 0.325 0.4 ]\n" - ] - } - ], - "source": [ - "print \"Strike prices: \", strike_vals\n", - "print \"Volatilities: \", sigma_vals" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Monte-Carlo option pricing function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following function computes the price of a single option. It returns the call and put prices for both European and Asian style options." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def price_option(S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000):\n", - " \"\"\"\n", - " Price European and Asian options using a Monte Carlo method.\n", - "\n", - " Parameters\n", - " ----------\n", - " S : float\n", - " The initial price of the stock.\n", - " K : float\n", - " The strike price of the option.\n", - " sigma : float\n", - " The volatility of the stock.\n", - " r : float\n", - " The risk free interest rate.\n", - " days : int\n", - " The number of days until the option expires.\n", - " paths : int\n", - " The number of Monte Carlo paths used to price the option.\n", - "\n", - " Returns\n", - " -------\n", - " A tuple of (E. call, E. put, A. call, A. put) option prices.\n", - " \"\"\"\n", - " import numpy as np\n", - " from math import exp,sqrt\n", - " \n", - " h = 1.0/days\n", - " const1 = exp((r-0.5*sigma**2)*h)\n", - " const2 = sigma*sqrt(h)\n", - " stock_price = S*np.ones(paths, dtype='float64')\n", - " stock_price_sum = np.zeros(paths, dtype='float64')\n", - " for j in range(days):\n", - " growth_factor = const1*np.exp(const2*np.random.standard_normal(paths))\n", - " stock_price = stock_price*growth_factor\n", - " stock_price_sum = stock_price_sum + stock_price\n", - " stock_price_avg = stock_price_sum/days\n", - " zeros = np.zeros(paths, dtype='float64')\n", - " r_factor = exp(-r*h*days)\n", - " euro_put = r_factor*np.mean(np.maximum(zeros, K-stock_price))\n", - " asian_put = r_factor*np.mean(np.maximum(zeros, K-stock_price_avg))\n", - " euro_call = r_factor*np.mean(np.maximum(zeros, stock_price-K))\n", - " asian_call = r_factor*np.mean(np.maximum(zeros, stock_price_avg-K))\n", - " return (euro_call, euro_put, asian_call, asian_put)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can time a single call of this function using the `%timeit` magic:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(12.478072469211625, 7.5692079226372924, 6.9498346596114704, 4.5592719279729934)\n", - "1 loops, best of 1: 111 ms per loop\n" - ] - } - ], - "source": [ - "%timeit -n1 -r1 print price_option(S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Parallel computation across strike prices and volatilities" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Client is used to setup the calculation and works with all engines." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "rc = Client()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A `LoadBalancedView` is an interface to the engines that provides dynamic load\n", - "balancing at the expense of not knowing which engine will execute the code." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "view = rc.load_balanced_view()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Submit tasks for each (strike, sigma) pair. Again, we use the `%%timeit` magic to time the entire computation." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "async_results = []" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1 loops, best of 1: 810 ms per loop\n" - ] - } - ], - "source": [ - "%%timeit -n1 -r1\n", - "\n", - "for strike in strike_vals:\n", - " for sigma in sigma_vals:\n", - " # This line submits the tasks for parallel computation.\n", - " ar = view.apply_async(price_option, price, strike, sigma, rate, days, paths)\n", - " async_results.append(ar)\n", - "\n", - "rc.wait(async_results) # Wait until all tasks are done." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "30" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(async_results)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Process and visualize results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Retrieve the results using the `get` method:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "results = [ar.get() for ar in async_results]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Assemble the result into a structured NumPy array." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "prices = np.empty(n_strikes*n_sigmas,\n", - " dtype=[('ecall',float),('eput',float),('acall',float),('aput',float)]\n", - ")\n", - "\n", - "for i, price in enumerate(results):\n", - " prices[i] = tuple(price)\n", - "\n", - "prices.shape = (n_strikes, n_sigmas)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot the value of the European call in (volatility, strike) space." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "iVBORw0KGgoAAAANSUhEUgAABGcAAAMvCAYAAAB/e73nAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", - "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3Xm4LGdd7+3vzkAgzCAgMhgIiIKCYQyRIYYAMijTeQwc\n", - "DRAEQXyRQURBD5GjoExCZJR5UDE8ckBAICSQGGRUmafDwQwMBhPAQCIh097vH9WL3VlZQ6/u6q6q\n", - "7vu+rn3V6tXVVbVWcmH2x189lQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n", - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCi7R79ef0G7x009v6xC7wmAGAC+3R9AQCwIIdn719O\n", - "d/LHX2SZxO2S/GmSDyf5epIfJvlBkm8kef/ovTst6Fr2zPg+ALBg+3V9AQDQke3+grprtI+/yLKV\n", - "n0lyXJIj131/T5p/h35i9OfIJM9I8pUkf5nk5Qu8RgCg58QZAFbRa5L83YT7nj7PC2HQfjnJ3ya5\n", - "8uj1fyR5W5JPJPlOkqsmuWGSX0xyRJIDk/xUkmdHnAEAAGAFHZ69tyo9rdtLYQncPskFaf59ujRN\n", - "cDlgi/2vmeRZSc5L8t05XdPav9+v2+C9g8bef+aczg8ATMmaMwAAO7N/kuOzN8Y8OckfJrlwi8/8\n", - "V5r1iw5J8rG5Xh0AMDjiDADszCOzdwLh7tvse8Zov5M3eX/903VukuQvknwxzWKylyQ5cYPPXTHJ\n", - "E5J8MMl/JrlotD0xyW+liQebOSiXXex4V5IHJXlrmoVsL0xydpJ/SHM7ziQOSvK8JJ9KEyEuTHOL\n", - "zzuS/I9tPntUklckOTXN4rkXpPm5v5fks0leneSu2xzjlNHPs3YL2o+n+dk+keTcNIvzfnl0jdea\n", - "8GfayiPS/LNKknenWUNmUl9Nct913zsgyZOSvDnJvyQ5J83v8KIk307y0SR/luSm018yAAAAdO/w\n", - "tHNb0yOz91aWu22z7xmjfT+4yftr13N8mnBw8dj3Lh1tP7nuMz+XJkLs3mDftT//N8nNNznnQWP7\n", - "vS3JZ9Z9dv2fp2/zMz41TUjY6npOSLP+yka+usE5139+d5JXpglJGzlltM/Xk/xxmhiz2c/z1STX\n", - "2+Zn2s6Hx67zDjMeK2nWpdnoWtf/Hn6Y5JgtjuO2JgAYKAsCA0D3ymj7/TQLzJ6aZl2Saya52th+\n", - "P5nkn5JcI83TgN6ZZjrlP5NcP82Uyn3ShJkPJfn5JN/a4rwPGm2/nOQtaSZVkiZkPS7NRMezk3w6\n", - "yXs3+PwfZ+9f9L+S5A1JvpAmMt0kyf9M8gtJ7jl67yEbHOPS0WdOHV3Ht9JMzVw9yW3S/G5unuQ3\n", - "k3w+yUu3+HluMLqeS9L8Xt6V5KzR9x+T5I5ppk9enORhWxxnK1cZHSdpJn3+ZcrjrHdRkn9N88/t\n", - "jDS/h4uSXDvJoWmu95pJXpUm2H2mpfMCAADAwhyevZMDr05yjzSPN97uz03WHeeRaX9yZneSv07y\n", - "Y9sc74Sxcz98k30eP3bMv9/g/YPG3v/2Fse5W/ZO8nx2g/d/IZf9fW72//B5ydh+h23w/oGbfG7N\n", - "FZN8bvT5L22yzylj5zgxyS022OcKY8e5KE3gmsahY+ea9Ilf29knWy8mnCQHZ+8CxK/YZB+TMwAA\n", - "APTa4dn69p3N/hy77jiPTPtx5m0TXP/Pju3/lm32fdfYNR687r2DMvlf0v9mbN9br3vvvaPvfzpb\n", - "r2F3YJLzR/set835NvNn2fvzbBRzThm9/7VtjvP7Y8c5YspruX/2/k5eNOUxpvXR0Xk/scn74gwA\n", - "DJTbmgBYVXta3m8W35tgn/uMfb3RX77HvTbJ/dKs0XLvJC+f8rrekb23/xyavRM0V00zVZQkb0rz\n", - "F/7N/CDNAse3T/Okos38TJJfTnMr082TXDfNbTwHpJl6WXOt0TE3cskWx0+SM0fbXUmus82+m7n6\n", - "2NfnTXmMzeyTZvHjI9OsLXRQmuu8Wprfw9p/t7WxqDEA0CPiDACr6A/SLMA7JLcZbfekWZtkK+Pv\n", - "32bTvbb35bGvxydwbptk39HXLxj9mcR1N/jeLZO8LJs/+Wp9HJvlSZP/Nfb1drcRbeb7Y19vtsjx\n", - "NO6fZrJo/W10a8Z/D562CQBLRpwBgGG49mi7O5eNDBs5Z4PPTWP8POMTI+sjy3bTRWtPWbrCuu/f\n", - "Os1CwGuLHv9w9PozaW5R+naa6ZSHJvn1yS55Sxe2cIzx3+1PtHC8pLlVbnwa6ttpbtX6UppFh7+T\n", - "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\n", - "dnl1kq8l+aNZT7KMkzPbuWqa+8q+v8n7P0hTzq627vufSvM0p1elGV36UJJbJfmTJB8vpVx9LlcL\n", - "EWagb4QZaI8wAztnagYWp9b6wyRHJjklyS2SvDzJt5J8NU1fuGOt9ZuznmcVJ2fWXLLJ9zccaaq1\n", - "3m7990op10mzavPPJ3l6kj9o7epgRJiBfhFmoD3CDOycMAOLVUq5SpJ/SHLLNE9o+mGSByb5tTSx\n", - "5p9KKaXW+vlZzrOKkzPnpQkwV9rk/QPH9ttSrfWcJE8avdxuESHYMWEG+kWYgfYIM7Bzwgx04nlJ\n", - "fjHJ42qt/1pr/Xyt9U/TxJrfTHLzJCeUUq46y0lWcXLm9CSHJPnJXH5NmSS5QZLdo/0m8d3R9iqz\n", - "XxrsJcxAvwgz0B5hBnZOmKGPrnDrG3R9CYtQ0iyN8v7xb46e+PyaUspDktw7yV2TvGfak6zi5MxH\n", - "RtvLTbqUUm6eZjHgz9daL5jweIeMthuFHpiKMAP9IsxAe4QZ2DlhBjp1wGh7403e33fddiqrGGfe\n", - "muSiJI8opfwo85VS9kmzuG+SvHH8A6WUx5ZS7rb+QKWUGyZ5dpqK9pq5XTErRZiBfhFmoD3CDAAD\n", - "9N40S6O8ZP2tS6WUI5McnuS/0iwYPLWluK1pFEkeOnq59pzxW5ZSnjr6+nO11hOSpNb6jVLKHyZ5\n", - "fpLPlFLeneax2HdN8nNJPp7kZetOcWiSV5RSzkjz/PLvpKlmR6ZZu+Yva63vncfPxmoRZqBfhBlo\n", - "jzAD0zEeRniwAAAgAElEQVQ1A+3bSUNI8pQkt0tyjyT/Xkr5QJoY89NpwswPkvxarXWmv8wtRZxJ\n", - "crM0i/Ss2ZPmdqPbjl6/Ic1TlZIktdYXllJOS/LENKssH5BmjZk/SfLcWutF647/siQXJLlDmoWA\n", - "rp3mUdwfSvLyWuu7Wv55WEHCDPSLMANA14QZmJuJG0Kt9ZullEOS/G6SByT55TQt5RtJ/irJ82ut\n", - "p816QRs+Npr5O+mkk/YkybUPPmS7XVkBwgz0izAD7TI1AzsnzAzbbR97nSTJkUceuZR/5177++wn\n", - "/6r7/31flt/1skzOwCCJMtA/wgy0S5iBnRNmYPWIM9ARYQb6RZSB9gkzADCZVXxaE3ROmIF+EWag\n", - "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\n", - "OWoiFnxMbfzsZ6YJM8kWf093nlpt4szs3pTkOkkeU2t9/9qLpZSjkvzPJK9K8tgZP+vAJEdlxm+e\n", - "SynXT/J7SS5Nclit9dPr1r0yyS8n+Y12yXgs7Zja4Nha61/N8T6GZ5HH1LFJ/mjjvxCVUl6U5MVJ\n", - "Xp3mX5TWXneemqalHVMb3+s8NQmLPJ5ek+TjtdZz1r9YSrljklOSPKWU8txa6zfb152jpmlpx9QG\n", - "zlHTschj6t+VUn4iyeuTvD/Jj2+xjfPUitu37AGMQSnlnknunGb62fvXr6u1vj7Jl5I8ppRy41k+\n", - "r9Z6Zq11X611vyS3m+Etj0py0+atV/1P2jomTVl9cinF7+dIDOCYYmI6OKZessXU3de2y3tteN15\n", - "amIGcEwxIR0cT3+y8Zvo1hlpzjcXJvnOutedoyZmAMcUE7PoY2rd5z4oyduTvC/JL26zqfPUivMb\n", - "O5sHtMuTtlh/YppZSPeb47Nnue/PlvuvtV6Y5LNp/ke+wxz7ZzmWfUztZXuGqctjar3rt8uvz7p/\n", - "56nRWvYxtZ7z1Ph1ejyVUm7Y3jPkf6f5++2RtdbLZ9m/c9RoLfuYWs85ahoWfkyVUu6U5vKkTyZ5\n", - "XJpLene9f+ep1eCyptmszUT42hbr1yr7IQPY/2kdjYHFWvYxtd77SinXSTOF8itJPpLk1bXWz/Ww\n", - "bxanr2Pqce3yI3vYv/PUOCz7mFrPeWr8OjueSimfSXLX9ssPJbnrJjeYdo6anmUfU+s5R03DQo+p\n", - "Usqtk3wgzTHx6FrrpaWURe3feWqCzJyZzQ3a5be2WH9Ru7zhRPfP4g3h9/ScNCX/zUl+N8mfpLn5\n", - "2VOSnNw+WYXx6PyYKqXcLskL0/zl83/0vX96t+xjKnGempIuj6fjkrwhyV+luSn+O9p/re5r/yzH\n", - "so+pxDlqahZ2TLWXPn0gzayqR854E1/nqRVn5szufG+L1/uayrjs/bN4S/s9rbW+YONr7TWsICTM\n", - "hQAADBlJREFUR6f5ZumNpZTb1Fq3m37J8HRyTJVSbpXkg0lulORnaq2n9Ll/lmppx5Tz1CQt/Hiq\n", - "tf7O2s9LKfdJ8ndJ3l1KuWut9ZKu98/SLe2Yco6arD0dU+0x8J4kt0rykFrrl/rcP+Nl5sxsvt0u\n", - "r7fF+utv2G5q+2fxBvl7Wmu9otZ6dJIzk9wyzaMDGYfOjqlSysFJ/ibNNNrn1Frf3Of+WZplH1Ob\n", - "cp4arV7OEe3j2P86yQ/m6k//co6anmUfU1tt7xw1Xos6pm6Y5EFJPpfkaaWUV639SPLr7TaHtK+9\n", - "pIP9M1JmzszmjHZ52y3WH9QuT5/o/lm8of+enp/k4CTft6T9s3udHFPtvxi+N83shifXWt/W5/5Z\n", - "qmUfUztxnhqXPs8R57fL9U9UcY6anmUfU7O85+A4R43Joo+pw5I8eJvPen6SbyR5UUf7Z2TEmdmc\n", - "2C4fvnFFO23tgUkuT3Jyh/t/frv/N27Y/w3T3LDs/CT/3NH+WbxlH1NbKqVcP8kPpZlSud3N7xiW\n", - "hR9TpZTHprmO/uIkR9Ra/3aH/TtPTcuyj6ntPsd5anx6+XOvlHKtXHUj1zPWrXKOmp5lH1Pbvcc5\n", - "apwWcky195fZ9AqVUspt0xxHf1dr3TgTy3lqxbmsaQa11k8lOTXJvUopR2xYfWSaivn+Wuu/Pwa0\n", - "lHJ8KeW0UspvLmAIH0hyXpJHl1LusmHdC5MckOStrmcdj2UfU6WUu5VSntP+5WH96/vS3NDu+5L8\n", - "aa31gr3ui34s+pgqpbw0yTuTfCHJfWb4Jtp5amKWfUw5T03LIo+nUsoPl1JeU0q5xSa7+o0kd0py\n", - "Sq31E+ted46amGUfU85R09PT38+3u2+M89SKM3Nmds9K8pdJ3ltKeV+SL6Up4ocnOTdN5VzvNmme\n", - "QX+Nk3wp5Qbt5yVXTY+8dSnll9ufn11rfefa9rXWi0opRyV5e5ITSynvTfL1JPdM8oA0f9E9Zs+/\n", - "Qvq2tGOq3eZ3k7yslPK3aQr+DdMcT/9Pks8n+YU9/epYhoUcU6WUhyR5QZp/8TsxyVFbPPrxE2vH\n", - "lfPUZC3tmIrz1BQt6s+9A5I8L8kvlFI+nuSUJNdJcv8kd2w/67+uf4Nz1GQt7ZiKc9RULezv57vl\n", - "PIWZMzOqtX40zQn6PWlu8PSsNBX9uDT/AvivG95yZftjMz+Q5JXtj19rt7vtuteevcn+a5opbn+b\n", - "5JFJfi7NSeB3k9y/1nr+xvcwbEs+pj6d5hulj6f5S8fTkvxUkgvTPGHg3rXWc+f+xbEUCzym1v5V\n", - "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\n", - "12+rtX6ng3EBACQxcwYARqfWelKSM9svH9IuT9hi2/cl+ZU0AebnkpxRSrk8yZdzVZj57VrrsbsY\n", - "wi8muSjJndJcAnRp+/VfJ3lYkvdv8973JvnH9uevTfKNNDNb/jzNU5L26hr3u6m1fi1XXYL1lDS/\n", - "9m+2Y7/fJttflKv+e7oRMADQOXEGAMZpfTz4apK/3GrDWuurkzwgzeVQX07y3TQ3/31PkiNqrb+8\n", - "xVuvzFWPx17/eSe3n/feNJdTfTfJGUl+L8ldkvzWNmP5bpKHp4klX2nf+9UkH06yNmvmGvvcaUwb\n", - "1m/mqDSXdn0hyWVJLkjyiSSnb7H92uufqrV+apv9AQDs2ZifpgAAsHDtE6k+n+QHkzyz1voHSx4S\n", - "ADBxZs4AAFzdI9OEmW9li8vFAAAWSZwBALi6o9rlCe39ZwAAOiXOAAC0SimHJPnxuBEwANAjcQYA\n", - "4CpHprkn39/XWv9h2YMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n", - "AAAAAAAAAAAAAAAAAAAAAAAAAAAYnv8LTrFLgrpADnoAAAAASUVORK5CYII=\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 407, - "width": 563 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure()\n", - "plt.contourf(sigma_vals, strike_vals, prices['ecall'])\n", - "plt.axis('tight')\n", - "plt.colorbar()\n", - "plt.title('European Call')\n", - "plt.xlabel(\"Volatility\")\n", - "plt.ylabel(\"Strike Price\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot the value of the Asian call in (volatility, strike) space." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "iVBORw0KGgoAAAANSUhEUgAABGUAAAMvCAYAAAB7jm3aAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", - "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3XmULWdB7+/vOTGEBBJAiMgvTCYMXpBREMgFIiEEBBQE\n", - "XwYlBFRE+akgIHrxApeLUwAFlElkCINGeBUUEAhTQmQQQWQKRMUMEEwkgCEJBEJyzv2jqjk7nd7d\n", - "e6g91vOs1at676pd9e7OWrrOh7feSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n", - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFguJybZ0/5s5dR231lzGg8AsIR2L3oAANBDP5Z9\n", - "/2Dfk+QLc7ru2QPXvPGcrrkI103yhCRvTvO3vTDJZUm+muTjSV6Z5GeSXHMOY9k75X4AAACgQ3+a\n", - "K0eZPUnuNofrntVe64qsZ5Q5IMkfJrkkV/37XrHFexcneWmSH5zBWE4cuO5WTm33nzmDawMAK+L7\n", - "Fj0AAOiZ/ZM8ov39v5Jcv/39uCQfmfG1fy7J1QeuvU6un+StSe7cvv5ukrcneV+Sc9MEkB9M8qNJ\n", - "7pfkJkmukeSXk7yr/SwAAACwxn4y+2ZQ/GKSL7avv5om2DC+qyX5cPbNgPlIkpvt8JmHJPl0mv8O\n", - "PzWDMZ0YM2UAAABgqbwpzT/GL01yrSTPy76Y8OAFjmuVPSf7/oYfzL7ZQDvZP8lzk9x/BmM6MaIM\n", - "AAAALI1rpYkxe5L8bfveHbMvKPz1iOe5XpL/leS0JF9Lc6vON5J8KsmfJ3lomtkjm52a7Z/4c+sk\n", - "v5vkbUnOSHJRksuTfDPJOWluB/qlIefe8JiB73NUmlulfzbN9z0vzYK7/5VmEd677vhNd3addpx7\n", - "2nHesINz3jfJi5K8P83iyN9M83e4KMnnk7whyQN3OMeJEWUAAABgafxC9gWLhw28f0b2zZ659g7n\n", - "ODbNrU47LWL7T1t89tRsHwL+9xbn2erc/5rkpkPO8ZiBzz07yb9v8fmNn+9m3/o6k3rcwPleMuW5\n", - "Nrw3o/0d/i7DZ+WcOPC5rZwaUQYAes9CvwAwP8e120ty5YVlT0ryrDRPD3pYklcM+fzN04SAA5J8\n", - "J8lfpJnN8fU0s2funOQBSQ7PZI973pvkK2luAfpYmpktX2mvd6M0M0ge0I7jTWke7T3MriTPaH8/\n", - "vR3rZ9PMsjk2TaDaL813fU+aGT+TuPfA76PONNrJniT/kWYm0qfTzOz5epq/6Q+nmYl0hzTrA/1u\n", - "kqd2dF0AAABgBm6cfTMsXr9p380H9v3DNud40cBx290+c78kb9zi/VOz/eyMg7Y554bB2TR32WL/\n", - "Ywb2n5/kkUPO8/SB4351hOsOszHL6IokB05xnkHX2GH/riQnt9e9MFsv0HxizJQBAHawe9EDAICe\n", - "+LmB30/atO/fk3yi/f3IDL816Jbtdk+axzgP864kDx9zfEnyrRGOqQO/32mHY38uV/2uG1418Pt2\n", - "M252cr12e1Ga27+68M0d9u9NsyZOkhyc5BYdXRcA6Bm3LwHAfGzcuvS1NLMsNjspzaK/u5I8Ks1t\n", - "MZt9ud3uTjMDZfOMm65cK81jou+SZvHfG6SJHweluZVpw3W2OcfeNGvGDPNfaRb9vVqSQ6cca5Jc\n", - "PMU5hrlJmidi3T7NbUs/kOS6adaR2Zgdsyvb/x0AAACABfrR7LtV52VDjjks+xaTPWPIMffKlRea\n", - "fXeaW3/ulCvHkmFOzfa3zByY5A+TfDtXXdR2q59nbnGOx2TfbTv33GE857XHvn+EsQ/ztfYc/z3F\n", - "OTa7UZK/yfDvvXnR362+54lx+xIAsAMzZQBg9jZmyexN8ldDjvlymgV275Hmdpgfy1WfoHRKkv+T\n", - "ZgHd/ZIc0/4kzT/+P5vkLUleneTcMce4f5pFhDfOt7c930fShIPz0qyfcu0krx3z3MN8p4NzXJBm\n", - "psohaWbyjHIL1nZumORD2fdo7cvb1/+S5vHYF6R5/Pi9kjxlymsBAAAAM7RfmgVvR5l5MvjzJ9uc\n", - "8xZpZrR8PE3Y2Dxz48I0tx9tdmqGz8547MDnv5BmbZut3HTguGlnypyd6WfKvHlgPPeZ4jwbXjtw\n", - "vpOzL85s9phs/z1PjJkyAMAOLPQLALN1bJq1SMb1iAyf0fpvSX47zW1L10qz9stvZN+Tmw5JExfG\n", - "uW5pt3uT/HSSD4853kV538DvP9PB+TbOcW6SB2X8GUcAACNz+xIAzNZxA78/Jc0aKMPsSjMD46g0\n", - "C+veL8nbdzj/t9PMmPl4mkdmPz/Jk9PEmqNy5aclbecm7fa8JJ8Z8TPL4M1pvvMBSY5P8gdpZuBM\n", - "4tDse6z2aWn+tgAAMyPKAMDsHJxmtkWSfDrJC0b4zH+niSlJE3QGo8y109yatJ0PpIkySXL90YaZ\n", - "ZN9tNtfY4birj3HOeTgvyZ+nWfD4amki1FEZbW2Z3WnW5/lYknfkyrcaHbzDZ5ft7wAArCC3LwHA\n", - "7Dwk+2ZejPr46ndk32yan0xzK9KGdyV5dpLv3+bzD2i3ezPejJePtduNx2Fv5aGZbv2XWXlGmlu6\n", - "kuZJVx9McqsdPnNMmgV8n5V9/yPV15Oc1f5+VLZeT2b/JP87yQvb17smGzIAgJkyADBLG7cuXZHk\n", - "L0f8zOVJ3pjkCWlmY/xMmqcppX39jCRPTRNoPpDki+35D0uzFszGYrcfbveP6k+TPDrNwsQnJXlF\n", - "kn9MM+Pk8HYc/3OM883TN5I8MM3CvD+U5PZpZia9M8l7su9v9ANJbpvmtrCbt5/du+lcL0xzG9gh\n", - "ST6a5MVJPp/m7/IjSR6V5IjZfRUAAABgWodl31ORTh7zs3fJvicAnTLw/l9ntCc3fSBbz6Y5Nds/\n", - "8eeX0kShYec9N8lvZbSnL+3JfJ6+NOhaSV6X5LsZ7e/09TS3lB266Tyv3eFzH00Tbrb7nifG05cA\n", - "AABgIZ6Wff8of9QEn//X9rOXJ7nRwPs/muT304SX89MsRvvNJP+RZobNg7c55yntObcLAXdL8tY0\n", - "t1BdluS/0txS9fNpFtO9SfZ9r62izPED+3eKMme1x3V9S9RNk/xOmpkyZyW5KMmlSb6c5J/SBJUH\n", - "pfk+wxyXZrbRxWn+xmenuQXtJ9r9O33P12T7KDPKfwsAAAAAAAAAAAAAWANr9cSAUsotk5ye5KRa\n", - "63EjHP+LaRYyfFyt9VXbHLd/kl9LswDizdNMJf9ckpfXWl/bxdgBAACA+RqnI5RSjkjy/6d5sMIN\n", - "k1wzyRdqrf9j0uuv/NOX2j/Kk5PcIMmxaR7zvflJCoPH3zfN0yluluTo9u2hx7c27tE/M83if/un\n", - "ecrDa0opt661Pm2a7wAAAADMx7gdof3Mryb5ozSTNE5Jsybe1XLltf/GtvJRJs0f4Feyc1jZcNc0\n", - "T5cY6fhSykPTBJnTkhxba72sff/aaZ6+8JRSyhtqrZ8ed+AAAADA3I3VEUopP5fkT5K8K8nxtdYL\n", - "uhrI7q5OtCi11lNrrbtrrftl38yX7Y5/9sDxzx7hEse322dvBJn2PBcmOSHNLWDHb/VBAAAAYLmM\n", - "0xFKKQcn+dMkn03yoC6DTLIeM2UGjbtGzijH3y1NPfvHLfZ9uN0eOeZ1AQAAgMXbqQs8Ism1kzwl\n", - "yS3aJVGul+RrST5Za33fNBdftyjTqbaIXTfJJbXWS7c45Mvt9vD5jQoAAACYkx9vt7+e5Habd5ZS\n", - "Tkvy0Frr1yY5+crfvjRjB7fbi4bs/1a7PWQOYwEAAADm6xbt9t+THJPkB5JcPcldkpya5J5J/nLS\n", - "k5spM5rLh7w/8SPF3/ve9466MDEAAAAr6Jhjjpn434zLbBn/PTvDv/W10ixp8tRa6xcH3v9YKeUn\n", - "0zyl+T6llFvWWv913JObKbO9i9vtgUP2H7TpOAAAAGB9bDzw56DNO2qt30zywfblrSc5uZky26i1\n", - "XlxK+XqS7y+lXKP9gw86rN2eOek1bnfXu088PoCunXvB5v8zBzB7Z53vf98Clt9551w48rG3vN5X\n", - "ZziS5fHGB0x8105nHv73PzvrS3w5ya2S3DTJGVvsn2qyi5kyO/twkv2SHLXFvo2istWTmQBWiiAD\n", - "LIIgA6yCcYIMa+e0dvvAzTtKKbuS3CbN7U2fmeTkoszOXt9un1pK2X/jzVLKtZP8Zpo//usWMTCA\n", - "rggywCIIMsCyO++cCwUZXp3k20keV0q536Z9T0rzNOZTaq3/PsnJV/72pVLKDdM8NzxJjmi3tyql\n", - "PLX9/TO11pMHjj8yyZHty43tfUsp39/+/o5a6+c2jq+11lLKcWmq2GdLKe9Psn+S+yf5wSQvqrV+\n", - "ouvvBTAPYgywKIIMsMyEmPU2TkeotZ5XSvnlNHHm70sp705ydprHY981zXImj550LCsfZZLcLMlz\n", - "B17vTXKHJHdsX5+Y5OSB/fdJ8qyBY/cmKe3P3iRfSfK5XNlDkzwxyXFp/thXJDk9ydNrrSd28zUA\n", - "5kuQARZBjAGWmRjTG2N1hFrr60opX0jytDTLmByd5IvtOf6g1vqNSQeylo/nWgUbjxCz0C+wCIIM\n", - "sAiCDLCsuo4xGwv9rvsjsZdpod9V/VtbUwagZwQZYBEEGWBZmR3DIq3D7UsAjEiQARZBkAGWkRjD\n", - "MhBlAHpCkAHmTYwBlpEYwzIRZQB6QJAB5k2QAZaNGMMyEmUA1pgYAyyCIAMsG0GGZSXKAKwpQQZY\n", - "BEEGWCZiDMtOlAFYQ4IMMG9iDLBMxBhWhSgDsGYEGWDeBBlgWYgxrJrdix4AAN0RZIB5E2SAZSHI\n", - "sIrMlAFYE4IMMG+CDLAMxBhWmSgDsAYEGWCexBhgGYgxrANRBmCFiTHAvAkywKKJMawTa8oArChB\n", - "Bpg3QQZYNEGGdWOmDMAKEmSAeRNkgEUSY1hXogzAihFkgHkSY4BFEmNYd6IMwAoRZIB5EmSARRFj\n", - "6AtRBmBFCDLAPAkywCKIMfSNhX4BVoAgA8yTIAMsgiBDH5kpA7DExBhgnsQYYBHEGPpMlAFYUoIM\n", - "ME+CDDBvYgyIMgBLSZAB5kmQAeZJjIF9rCkDsGQEGWCeBBlgngQZuDIzZQCWiCADzIsYA8yTGANb\n", - "E2UAloQgA8yLIAPMixgD2xNlAJaAIAPMiyADzIMYA6MRZQAWSIwB5kmQAeZBkIHRiTIACyLIAPMi\n", - "xgDzIMbA+EQZgAUQZIB5EWSAWRNjYHKiDMCcCTLAvAgywCyJMTC93YseAECfCDLAvAgywCwJMtAN\n", - "M2UA5kSQAeZBjAFmSYyBbokyAHMgyADzIMgAsyLGwGyIMgAzJMYA8yLIALMgxsBsiTIAMyLIAPMi\n", - "yABdE2NgPkQZgBkQZIB5EGOAWRBkYH5EGYCOCTLAPAgyQNfEGJg/UQagQ4IMMA+CDNAlMQYWR5QB\n", - "6IggA8yDIAN0RYyBxRNlADogyACzJsYAXRJkYDmIMgBTEGOAeRBkgK6IMbBcRBmACQkywDwIMkAX\n", - "xBhYTqIMwAQEGWAeBBlgWmIMLDdRBmBMggwwa2IM0AVBBpafKAMwBkEGmDVBBpiWGAOrQ5QBGJEg\n", - "A8yaIANMQ4yB1SPKAIxAkAFmTZABJiXGwOoSZQC2IcYAsybGANMQZGC1iTIAQwgywKwJMsCkxBhY\n", - "D6IMwBYEGWDWBBlgEmIMrBdRBmATQQaYNUEGGJcYA+tJlAEYIMgAsyTGAOMSY2C97V70AACWhSAD\n", - "zJIgA4xLkIH1Z6YMQAQZYLYEGWAcYgz0hygD9J4gA8ySIAOMSoyB/hFlgN4SY4BZEmOAUYkx0F/W\n", - "lAF6SZABZkmQAUYlyEC/mSkD9I4gA8ySIAOMQowBElEG6BlBBpglQQbYiRgDDBJlgN4QZIBZEWOA\n", - "nYgxwFasKQP0giADzIogA+xEkAGGMVMGWGtiDDBLggywHTEG2IkoA6wtQQaYJUEGGEaMAUYlygBr\n", - "SZABZkWMAYYRY4BxWVMGWDuCDDArggwwjCADTMJMGWCtCDLArAgywFbEGGAaogywNgQZYBbEGGAr\n", - "YgzQBVEGWHliDDArggywmRgDdEmUAVaaIAPMiiADDBJjgFmw0C+wsgQZYFYEGWCQIAPMipkywEoS\n", - "ZIBZEGOAQWIMMGuiDLByBBlgFgQZYIMYA8yLKAOsFEEGmAVBBkjEGOijUsotk5ye5KRa63EjfuaQ\n", - "JKcluW2Sx9VaXzXp9UUZYCWIMcCsCDJAIshAn5RSjkjy5CQ3SHJsmvV294742QOS/G2aIJNRPzeM\n", - "KAMsPUEGmAUxBkjEGOipGyX5lYwZVEopu5O8PsmRSd6f5OhpByLKAEtNkAFmQZABxBjor1rrqWmf\n", - "Rl1KOSrJKSN+9IVJHprkEUlulQ6ijEdiA0tLkAFmQZCBfjvvnAsFGWDQrlEOKqX8ryS/muQ3aq11\n", - "1M/tRJQBlpIgA8yCIAP9JsYAkyilPCbJ7yV5bq31T7o8t9uXgKUjyABdE2Og38QYYFKllPsneUWS\n", - "19daf7vr84sywNIQY4BZEGSgv8QYYBqllDskeVOS9yX5+VlcQ5QBloIgA8yCIAP9JMYAHTkqyYFJ\n", - "zk5yQillcN+R7fZhpZRbJflArfVt415AlAEWTpABZkGQgX4SZGB+bnLTWy16CLO2N82Cvo/f5phj\n", - "k9wnzZq9ogywWgQZoGtiDPSTGAN0rdb6oiQv2mpfKeVZSZ6V5Bdrra+e9BqiDLAwggzQNUEG+keM\n", - "ARakk0diizLAQggyQNcEGegXMQaYVCnlhkke0b48ot3eqpTy1Pb3z9RaT57HWEQZYK7EGGAWBBno\n", - "DzEG6MDNkjx34PXeJHdIcsf29YlJdooye9ufqYgywNwIMkDXxBjoF0EG6EKt9dQ0C/NOc45nJ3n2\n", - "tGMRZYC5EGSArgky0B9iDLCuRBlg5gQZoGuCDPSDGAOsO1EGmClBBuiaIAPrT4wB+kKUAWZGkAG6\n", - "JMZAPwgyQJ+IMkDnxBiga4IMrD8xBugjUQbolCADdE2QgfUmxgB9JsoAnRFkgK4JMrC+xBgAUQbo\n", - "iCADdEmMgfUmyAA0RBlgaoIM0CVBBtaXGANwZaIMMBVBBuiSIAPrSYwB2JooA0xEjAG6JsjA+hFj\n", - "ALYnygBjE2SALokxsJ4EGYCdiTLAWAQZoEuCDKwfMQZgdKIMMDJBBuiSIAPrRYwBGJ8oA4xEkAG6\n", - "JMjA+hBjACYnygA7EmSArogxsD7EGIDpiTLAUGIM0CVBBtaHIAPQDVEG2JIgA3RJkIH1IMYAdEuU\n", - "Aa5CkAG6JMjA6hNjAGZDlAGuRJABuiTIwGoTYwBma/eiBwAsD0EG6JIgA6tNkAGYPTNlgCSCDNAd\n", - "MQZWmxgDMD+iDPScGAN0SZCB1SXGAMyfKAM9JsgAXRJkYDWJMQCLY00Z6ClBBuiSIAOrSZABWCwz\n", - "ZaCHBBmgS4IMrB4xBmA5iDLQM4IM0CVBBlaLGAOwXEQZ6BFBBuiSIAOrQ4wBWE6iDPSAGAN0SYyB\n", - "1SLIACwvUQbWnCADdEmQgdUhxgAsP09fgjUmyABdEmRgdQgyAKtBlIE1JcgAXRJkYHUIMgCrw+1L\n", - "sIYEGaBLggysBjEGYPWYKQNrRpABuiTIwGoQZABWk5kysCbEGKBrggysBkEGYHWJMrAGBBmgS2IM\n", - "rAYxBmD1uX0JVpwgA3RJkIHVIMgArAczZWCFCTJAlwQZWH5iDMB6MVMGVpQgA3RJkIHlJ8gArB9R\n", - "BlaQIAN0SZCB5SfIAKwnty/BChFjgK4JMrDcxBiA9WamDKwIQQbomiADy02QAVh/ZsrAChBkgC6J\n", - "MbD8BBmAfhBlYMkJMkCXBBlYbmIMQL+4fQmWmCADdEmQgeUmyAD0j5kysITEGKBLYgwsP0EGoJ9E\n", - "GVgyggzQJUEGlpsYA9BvogwsCTEG6JIYA8tPkAFAlIEFE2OArgkysNzEGAA2iDKwQIIM0CUxBpaf\n", - "IAPAIFEGFkCMAbokxsDyE2MA2IpHYsOcCTJAlwQZWH6CDADDmCkDcyLGAF0SY2A1CDIAbEeUgTkQ\n", - "ZICuiDGwGsQYAEYhysAMiTFAlwQZWA2CDACjEmVgRgQZoCtiDKwGMQaAcYky0DExBuiKGAOrQ5AB\n", - "YBKiDHREjAG6JMjAahBjAJiGKAMdEGSArogxsDoEGQCmJcrAFMQYoEuCDKwOQQaALogyMCFBBuiK\n", - "GAOrQ4wBoEuiDIxJjAG6IsbAahFkAOiaKANjEGSArggysDrEGABmRZSBEYgxQFfEGFgtggwAsyTK\n", - "wDbEGKArYgysFjEGgHkQZWAIQQboiiADq0WQASZ16RkXNL/cfddiB8LKEGVgEzEG6IoYA6tFjAGm\n", - "8b0gA2MQZWCAIAN0QYyB1SPIAJMSY5iGKAMRY4DuCDKwegQZYFKCDNMSZeg9QQboghgDq0eMASYl\n", - "xtAVUYbeEmOALogxsJoEGWASYgxdW7soU0q5ZZLTk5xUaz1um+MelORJSW6f5IAk5yR5Y5ITaq2X\n", - "bnH8nh0u/dFa690mHjhzI8YAXRFkYPWIMcCkBJn1tFNDKKUcnOQXkhyT5HZJfiDJZUn+PclfJXlR\n", - "rfU7k15/LaJMKeWIJE9OcoMkxybZnWTvNsc/MckLklyY5K1JLkpyVJJnJrl3KeXoWut3t/joxUn+\n", - "bMhpz5n4CzA3ggzQBTEGVpMgA0xCjFk/YzaEuyT54yTfSHJakrOTXDvJTyT5wyQ/VUr58Vrr5ZOM\n", - "ZS2iTJIbJfmVbBNiNpRSDkvzh7sgyZ1qrV9q39+V5KQkD0vy+CQv3uLj36i1Pq2rQTM/YgzQFUEG\n", - "Vo8YA0xCjFlrIzeEJF9N8ktJXldrvWzjzVLKNZN8KMmRSR6d5NWTDGQtokyt9dQ0ZSullKOSnLLN\n", - "4Q9Pc7vSyzeCTHuOvaWUp6eJMo/N1lGGFSTIAF0QY2A1CTLAJASZ9TZOQ6i1fjLJJ7d4/5JSymvS\n", - "zKL50UwYZXZP8qElt2uH/Rvrvnxk845a65lJvpLkdqWUq3c9MObr3Au+KcgAUzvr/IsFGVhRggww\n", - "rkvPuECQ6Z+dGsJ2Dmq3X5v0BGsxU2ZMh7fbrwzZ/+Ukhyb5oSSf37TvsFLKd9L83S5Js7DPW9Is\n", - "7HPJDMbKhMQYoAtiDKwmMQYYlxDDuNolUEr78rRJz9PHKHNwmvvGLhqy/1tpStkhm97/lyT/mqaA\n", - "7U5ykyT3TnLHJD9bSjmy1vqNmYyYkYkxQBfEGFhdggwwLkGGCT0pzdOYPlRrfe+kJ+ljlNkwbGXk\n", - "Lacu1Vp/dPN7pZRDk5yc5rHa/yvJb3c2OsYmyADTEmNgdYkxwLjEGCZVSnlEkuenudPm4dOcax3X\n", - "lNnJxWnCy4FD9h80cNy2aq0XpKljSXL09ENjEtaOAbogyMDqEmSAcQkyTKqUcnySNyT5zyT3qrX+\n", - "5zTn6+NMmbOS3CHN7Ueb14xJksOS7GmPG8XX2+01px8a4xBigC6IMbC6xBhgXGJMt65228MWPYS5\n", - "KqU8M8n/SXJ6kvsPPtF5Un2MMh9O8pA0M1veNbijlHLzNIv8frrWeumI57tDu90q8DAjggwwLTEG\n", - "VpsgA4xDjGEapZQDkvx5kkcleV+Sh9Zah61TO5Y+3r70piSXJTm+lPK9rFdK2Z3kOe3L1w5+oJTy\n", - "+FLKPTefqJRywyS/l2bh4FfObMR8j1uVgC4IMrC6zjvnQkEGGIsgwzTabnBamiDzp0nu11WQSdZk\n", - "pkwbRx7Rvjyi3d6qlPLU9vfP1FpPTpJa67mllN9J8rwknyqlvD3N463vkeQ2ST6a5CWbLnHXJC8r\n", - "pZyd5CNpnsB04yTHpFmb5k9qre+cxXdjHzEGmJYYA6tNjAHGIcYwzDgNIc3kjTsn+UKaCR4nlFKy\n", - "hZfWWs8cdyxrEWWS3CzJcwde701zW9Ed29cnpnlKUpKk1vpHpZQzkzwxyYOTHJBmDZnnJDmh1nrZ\n", - "pvO/JMmlaf5D3CvJddM8Uvsf0vzh39bx92GAGANMS4yB1SfIAKMSYxjBOA1hV7v/iCRPGXK+vUne\n", - "mmTsKLPl45+Zvfe+9717k+R2d737ooey1AQZYFqCDKw2MQYYx7IEmdvfvfmn9jHHHLOW/+be+Pfs\n", - "J/5s8X/vOz7+0CSr+7del5kyrBkxBpiWGAOrT5ABRrUsMQbGJcqwVMQYoAuCDKw2MQYYlRjDqhNl\n", - "WBqCDDAtMQZWnyADjEqQYR2IMiycGANMS4yB1SfGAKMSY1gnuxc9APpNkAGmJcjA6hNkgFEJMqwb\n", - "M2VYCDEGmJYYA6tPjAFGJcawrkQZ5k6QAaYhxsB6EGSAUYgxrDtRhrkRY4BpCTKwHgQZYBSCDH0g\n", - "yjBzYgwwLTEG1oMYA4xCjKFPRBlmSpABpiHGwPoQZICdiDH0kSjDTIgxwLQEGVgPYgwwCkGGvhJl\n", - "6JwgA0xDjIH1IcgAOxFj6DtRhs6IMcA0xBhYH2IMsBMxBhq7Fz0A1oMgA0xDkIH1IcgAOxFkYB8z\n", - "ZZiKGANMQ4yB9SLIANsRY+CqRBkmJsgA0xBkYH2IMcBOBBnYmijD2MQYYBpiDKwXQQbYjhgD2xNl\n", - "GJkYA0xDjIH1IsYA2xFjYDQW+mUkggwwDUEG1osgA2xHkIHRmSnDtsQYYBpiDKwXMQbYjhgD4xNl\n", - "GEqQASYlxsD6EWSAYcQYmJwow1WIMcA0BBlYL2IMsB1BBqYjynAlggwwKTEG1o8gAwwjxkA3RBmS\n", - "iDHA5MQYWE+CDLAVMQa6Jcr0nBgDTEOQgfUjxgDDCDLQPVGmxwQZYFJiDKwnQQbYihgDsyPK9JAY\n", - "A0xKjIH1JMYAwwgyMFuiTM8IMsCkBBlYT4IMsBUxBuZDlOkJMQaYlBgD60mMAbYixsB87V70AJg9\n", - "QQaYlCAD60mQAbYiyMD8mSmzxsQYYFJiDKwvQQbYTIyBxRFl1pAYA0xKjIH1JcYAm4kxsHhuX1oz\n", - "ggwwKUEG1pcgA2wmyMByMFNmTYgxwKTEGFhfYgywmRgDy0WUWQOCDDAJMQbWmyADDBJjYDmJMitM\n", - "jAEmJcjA+hJjgM0EGVheosyKEmSASYgxsN4EGWCQGAPLT5RZMWIMMAkxBtabGANsJsjAahBlVogg\n", - "A0xCkIF+1cbcAAAgAElEQVT1JsgAg8QYWC2izAoQY4BJiDGw/gQZYIMYA6tJlFliYgwwCTEG1p8Y\n", - "AwwSZGB1iTJLSpABJiHIwPoTZIANYgysPlFmyYgxwCTEGFh/YgywQYyB9bF70QNgH0EGmIQgA+tP\n", - "kAE2CDKwXsyUWQJiDDAJMQbWnxgDbBBjYD2JMgsmyADjEmOgHwQZIBFjYN25fQlghQgy0A+CDJAI\n", - "MtAHZsoArAAxBvpBjAESMQb6RJQBWGJiDPSHIAMkggz0jSgDsKQEGegHMQZIxBjoK1EGYMmIMdAf\n", - "ggwgxkC/iTIAS0KMgf4QY4BEkAFEGYClIMhAfwgygBgDbBBlABZIjIH+EGMAMQbYTJQBWAAxBvpF\n", - "kAEEGWArogzAnAky0C+CDPSbGANsR5QBmBMxBvpFjIF+E2OAUexe9AAA+kCQgX4RZKDfBBlgVGbK\n", - "AMyQGAP9IsZAv4kxwLhEGYAZEGOgfwQZ6DdBBpiEKAPQMUEG+kWMgX4TY4BpiDIAHRFjoH8EGegv\n", - "MQbogigDMCUxBvpJkIH+EmSArogyAFMQZKB/xBjoLzEG6JooAzABMQb6SZCBfhJjgFkRZQDGIMZA\n", - "P4kx0F+CDDBLogzAiAQZ6CdBBvpJjAHmQZQB2IEYA/0kxkA/iTHAPIkyAEOIMdBfggz0kyADzJso\n", - "A7AFQQb6SYyBfhJjgEURZQAGiDHQX4IM9JMgAyySKAPQEmSgvwQZ6B8xBlgGogzQe2IM9JcYA/0j\n", - "xgCDSim3THJ6kpNqrcdtc9yDkjwpye2THJDknCRvTHJCrfXSSa8vygC9JcZAvwky0D+CDJAkpZQj\n", - "kjw5yQ2SHJtkd5K92xz/xCQvSHJhkrcmuSjJUUmemeTepZSja63fnWQsogzQS4IM9JcYA/0jxgCb\n", - "3CjJr2SbELOhlHJYkj9MckGSO9Vav9S+vyvJSUkeluTxSV48yUB2T/IhgFV11vkXCzLQY4IM9Mul\n", - "Z1wgyABXUWs9tda6u9a6X5Kjdzj84WluV3r5RpBpz7E3ydPbl4+ddCyiDNALYgz023nnXCjIQM+I\n", - "McCIdu2w/27t9iObd9Raz0zylSS3K6VcfZKLu30JWHtiDPSbGAP9IsYAHTu83X5lyP4vJzk0yQ8l\n", - "+fy4JxdlgLUlxgCCDPSHGAPMyMFp1p65aMj+b6WZbXPIJCcXZYC1JMhAv4kx0C+CDDAHlw95f6fb\n", - "n7YlygBrRYwBBBnoDzEGFmv3ra+36CHMw8VpwsuBQ/YfNHDc2Cz0C6wNQQb6zWK+0C+CDDAnZ7Xb\n", - "mwzZf1iSPQPHjUWUAVaeJysBYgz0h8dcA3P24XZ7lUdnl1JunmaR38/WWi+d5OSiDLCyxBjA7Bjo\n", - "DzEGWJA3JbksyfGllMM23iyl7E7ynPblayc9uTVlgJUkxgBiDPSHGAN0qZRywySPaF8e0W5vVUp5\n", - "avv7Z2qtJydJrfXcUsrvJHlekk+VUt6e5JIk90hymyQfTfKSScciygArRYwBxBjoDzEGmJGbJXnu\n", - "wOu9Se6Q5I7t6xOTnLyxs9b6R6WUM5M8McmDkxyQZg2Z5yQ5odZ62aQDEWWAlSHIAIIM9IMYA8xS\n", - "rfXUjLmcS631LUne0vVYRBlg6YkxQCLIQF8IMkCfiDLAUhNkADEG+kGMAfpIlAGWkhgDJIIM9IEY\n", - "A/SZKAMsFTEGSMQY6AtBBug7UQZYGoIMkAgy0AdiDEBDlAEWTowBEjEG+kKQAdhHlAEWSpABEkEG\n", - "+kCMAbgqUQZYCDEG2CDIwHoTYwCGE2WAuRNkgESMgT4QZAC2J8oAcyPGABsEGVhvYgzAaEQZYObE\n", - "GGCDGAPrTYwBGM9Mokwp5eAkd05yaJIDaq2vG9h3vSQHJbm81vqfs7g+sHhCDDBIjIH1JsYATKbT\n", - "KFNKOSTJHyU5Lsn+SXYl2ZvkdQOH3TXJW5NcUUq5ca31vC7HACyWGAMMEmNg/QkyAJPb3dWJSilX\n", - "T/L+JL/Qnvff0gSZK6m1vj3JKUn2S/LIrq4PLNZZ518syADfc945FwoysOYuPeMCQQZgSp1FmSS/\n", - "luSOaWLMj9Ra/0eS7w459pXt9ic7vD4wZxshRowBNogxsP7EGIDudHn70sPa7ZNrrf+2w7Hvb7e3\n", - "7vD6wJyIMMBmQgysPyEGoHtdRpkfTnO70odGOPYr7bHX6vD6wAwJMcBmQgz0hyADMBtdRpnvSxNa\n", - "Lhnh2GumWQT4mx1eH5gBMQbYTIyB/hBjAGaryyjzpSRHtD873b5073b7hQ6vD3RIjAE2E2OgP8QY\n", - "gPnoMsq8K8mvJnlCkicNO6iUco0kv9u+fHeH1wemJMQAWxFjoD/EGID56jLKPD/JLyZ5QinlzCQv\n", - "GdxZStmV5F5J/jjJrdLcuvSSzScB5k+MAbYixkC/CDIA89fZI7FrrV9M8sg068q8MMn5SfZPsquU\n", - "8i9JvprkPUlum+TyJI+ptZ7X1fWB8XmcNbAVj7WGfvGIa4DF6SzKJEmt9e+S3C3JB5NcN81ivkly\n", - "uyTXaV9/Kskxtda/6fLawGg2QowYA2wmxkC/iDEAi9fl7UtJklrrJ5Lcs5RyeJIjk9wgyX5pHoP9\n", - "sVrrZ7q+JrAzEQYYRoiBfhFiAJZH51FmQ631zCRnzur8wGjEGGArQgz0kyADsFw6izKllP2SvDTN\n", - "OjJ/W2t965Dj7p+kJPl2kifUWvd2NQagIcQAw4gx0E9iDMBy6nKmzE8leVyS85I8cZvjTkvyijS3\n", - "Nb0zyZbxBhifGAMMI8ZAP4kxAMuty4V+j2u3L6y1Dv2XYa31kjSPxd6V5DEdXh96y8K9wDAW74V+\n", - "sogvwGrocqbM3dI8DvuvRzj2zUmen+SuHV4fekWEAbYjxEB/iTEAq6PLKHPdJHtqrWeNcOwX0wSc\n", - "63Z4fegFMQbYjhgD/SXGAKyeLm9f+kaS3aWUQ0Y49pppbl+6qMPrw1pzixKwHbcpQX+5VQlgdXU5\n", - "U+YTSe6T5slKr9rh2Ie02892eH1YOyIMsBMhBvpLiAFYfV3OlHldu31eKeVuww4qpfxYmvVkkuSN\n", - "HV4f1oZZMcBOzIyBfhNkANZDlzNlTkry2CRHJ/lAKeVtSd6b5Nw068fcKMkxaR6dvV+STyV5dYfX\n", - "h5UmwgA7EWEAMQZgvXQWZWqte0opD03yF0nun+Sn25+t/FOSh9RaL+vq+rCqxBhgJ2IMIMYArKcu\n", - "Z8qk1vqNJA8spdw/yaPTPPL6+u3ur6aJMW9qDq17urw2rBoxBtiJGAOIMQDrrdMos6HW+o4k75jF\n", - "uWGVCTHAKMQYQIwB6IeZRBngysQYYBRiDJAIMgB9IsrADIkxwCjEGCARYwD6aOIoU0o5Jcl3aq33\n", - "a1+/Js1TlsZSa/35SccAy0iIAUYlxgCJGAPQZ9PMlDkqybcHXh8/wTn2JhFlWAtiDDAqMQZIxBgA\n", - "posypyX5zsDrv5zgHGPPrIFlI8YAoxBigEGCDADJFFGm1vrjm14/aurRwIoQYoBRiTHAIDEGgEGd\n", - "LfRbSrlvku+rtf59V+eEZSPGAKMSY4BBYgwAW+ny6UtvabcHdXhOWApiDDAqMQYYJMYAsJ0uo8x+\n", - "Sa7o8HywUEIMMA4xBthMkAFgJ7s7PNfZSQ4opRzY4Tlh7s46/2JBBhjZeedcKMgAV3LpGRcIMgCM\n", - "pMuZMm9N8pQkxyR5W4fnhbkQYoBxCDHAZkIMAOPqMsq8KMkTkjw9ogwrQogBxiXGAJuJMQBMqsso\n", - "84Ak/5zk7qWUlyb55CgfqrW+osMxwEjEGGBcYgywmRgDwLS6jDIvG/j9l0f8zN4kogxzIcQA4xJi\n", - "gGEEGQC60GWU+eIEn9nb4fVhS2IMMC4xBhhGjAGgS51FmVrrTbs6F3RBjAHGJcYAw4gxAMxClzNl\n", - "YOGEGGASYgwwjBgDwCx1EmVKKVdLcrMk10zypVrreV2cF0YlxgCTEGOA7QgyAMzaVFGmlLJfkmck\n", - "+fUk1xp4/+NJfqvWeupUo4MdiDHAJMQYYDtiDADzsnvKz78iyTOTXDvJroGfOyd5TynlkVOeH67i\n", - "rPMv/t4PwDjOO+dCQQYY6tIzLhBkAJiriaNMKeVeSR7bvnx9knsk+ZEkJcmHk+yX5JWllMOmHSQk\n", - "EWKAiYkxwHbEGAAWZZrbl36+3b6x1nr8wPufK6X8XZL3pQk1v57kt6a4Dj0nxACTEGGAUYgxACzS\n", - "NLcv3aXdvnDzjlrr5Ul+t3157ymuQU+5RQmYlFkxwCjMjgFgGUwzU+awJHuT/POQ/f/Ubn9oimvQ\n", - "MyIMMCkhBhiFEAPAMplmpsyBSS5rZ8VcRa31G0n2JDlkimvQE2bFAJMyMwYYhZkxACyjqR6JnWam\n", - "zHYuT7L/lNdgTYkwwDSEGGAUQgwAy2zaKLOrlHKLYfvan2xzTGqt/zblGFgxYgwwDTEGGJUgA8zb\n", - "ntO/2vxy90MXOxBWxrRR5oAkn99m/652u9Uxu9LMtNlvyjGwIsQYYBpiDDAqMQZYhO8FGRjDtFEm\n", - "2RdeJjlmlM+ywoQYYFpiDDAqMQZYBDGGaUwTZQ7vbBSsHTEGmJYYA4xKjAEWQYyhCxNHmVrr2R2O\n", - "gzUgxADTEmKAcQkywLyJMXSpi9uX6DkxBpiWGAOMS4wBFkGQoWuiDBMTY4BpiTHAuMQYYBHEGGZF\n", - "lGEsQgzQBTEGGJcYAyyCGMOsrVWUKaXcMsnpSU6qtR63zXEPSvKkJLdP81jvc5K8MckJtdZLtzh+\n", - "/yS/luTRSW6e5PIkn0vy8lrra7v+HstIjAG6IMYAkxBkgHkTY/qhlPLAJL+a5M5JDkpybpKPJ3lu\n", - "rfVf5jGG3fO4yCyVUo4opbyklPLmJP+c5jvt3eb4JyZ5S5LbJXlrklcl+W6SZyZ5dxtgNntjkucn\n", - "uWaS1yZ5U5KbJnlNKeW53X2b5XPW+RcLMsDUzjvnQkEGGNulZ1wgyABzJ8j0Qynl99M0gR9L8q4k\n", - "f57kC0lKko+XUo6fxzjWYabMjZL8SrYJMRtKKYcl+cMkFyS5U631S+37u5KclORhSR6f5MUDn3lo\n", - "kgcnOS3JsbXWy9r3r53ko0meUkp5Q631011+qUUSYYCuCDHAJIQYYBHEmP4opdw6yW8n+bckd621\n", - "Xjiw725JTk3ywlLKX9ZavzvLsaz8TJla66m11t211v2SHL3D4Q9Pc7vSyzeCTHuOvUme3r587KbP\n", - "bNSxZ28EmfYzFyY5IcmugWNWmlkxQFfMjAEmYWYMsAh7Tv+qINM/t2m37xwMMklSa/1Iks8mOSTJ\n", - "dWc9kJWPMpvs2mH/3drtRzbvqLWemeQrSW5XSjlw02f2JvnHLc734XZ75JjjXCpiDNCFjRAjxgDj\n", - "EmOARRBjeu1z7fanSinXH9zRLmlyoyTn1FrPn/VA1uH2pXEc3m6/MmT/l5Mcmma9mM+XUg5OU8Yu\n", - "2WoB4Pb4wfOuDBEG6IoIA0xDjAEWQYzpt1rrp0spz0/y1CSfK6W8OMkbkpyd5CVJDk7ys/MYS9+i\n", - "zMFpZr1cNGT/t9LMtjlk4PjscHwGjl96YgzQFTEGmIYYAyyCGMOGWuvTSimXpVnK5Bntz4VJ9k9y\n", - "dHsb08x1HmVKKYcn+aU0t/1cP8nVaq2HD+x/cJIHJflOkifUWvd0PYYRXD7k/WG3P417/NIRY4Cu\n", - "iDHANMQYYBHEGDYrpZyQ5MlJfiHJO9M84OfhSY5K8vellMfXWuusx9FplGkfGfXyNIvpbtj8VKRT\n", - "krw6ybWS/E2S93Q5hh1cnCakHDhk/0EDxw1uRz1+qQgxQJfEGGAaYgywKILM7Bz4w4cueghJxv/v\n", - "W0p5WJLfTPIntdbXtG+/PMnLSylHpmkVJ5VSzq61fqyzoW6hs4V+Syl3SvLKNEHmDUkemS1mmNRa\n", - "v5HkZWniyCO6uv6Izmq3Nxmy/7AkezaOq7VenOTrSb6/lHKNIccnyZldDnJaFu4FumTxXmBaggyw\n", - "CBbyZRul3b57845a64eTvCBNLymb93ety6cvPSXJfkleUGt9dK31jWkCx1b+pt3+zw6vP4qNpyVd\n", - "5dHZpZSbp1nk97ObFvX9cJrvddQW57t7u93qyUxzJ8YAXRJjgGl5qhKwCGIMI9i4u+fGQ/Zv3FW0\n", - "36wH0mWUuWeaW5VeMsKxG4+fulGH1x/Fm5JcluT4UsrGLJeUUnYneU778rWbPvP6dvvU9tFYG5+5\n", - "dprpTnuTvG5mI97BRogRY4CuiDHAtMQYYBHEGMbwznb7jFLKEYM7Sik3SvLLaf6t/7ezHkiXa8oc\n", - "mmbQZ49w7GXtsVMvlFtKuWH23Qa18ce8VSnlqe3vn6m1npwktdZzSym/k+R5ST5VSnl7kkuS3CPJ\n", - "bZJ8NJuiUq21llKOS/LAJJ8tpbw/zWrM90/yg0leVGv9xLTfY1wiDNAlEQboghADLIoYw5hekeQB\n", - "af5df3op5eQkX0ry/yX5iSRXS/J/a63/MOuBdBllLkpynfbnazsce7M0QaaL/899syTPHXi9N8kd\n", - "ktyxfX1ikpM3dtZa/6iUcmaSJ6ZZXfmANGvIPCfJCbXWy7a4xkPb449L8ugkVyQ5PcnTa60ndvAd\n", - "RiLEAF0TY4CuCDLAIogxTKLWekUp5SeTPDbNv/HvmeSaaVYNfkeaBYA/MI+xdBll/iXJvdOss/J3\n", - "Oxz7uHb7T9NetNZ6asa8DavW+pYkbxnj+O8meX77M3diDNA1MQboihgDLIIYw7RqrXvTPBn61Ysc\n", - "R5drymysxfL77XorW2pvBfqN9uXrhx2HhXuB7lkvBuiKdWOARbBuDOumy5kyf5Hm9p77JPlYKeXF\n", - "adeMKaU8KMnhSX46+55Y9O5a61s7vP5aEGGAWRBigK4IMcCiiDGso86iTK11bynloUlek2YNlhcM\n", - "7N58q9C7kzy8q2uvAzEGmAUxBuiKGAMsihjDOutypkxqrZckKaWUo5M8JsmRSW6Q5tneF6RZQ+b1\n", - "tdaZP1ZqVYgxwCyIMUCXBBlgEcQY+qDTKLOh1vr+JO+fxbkBGE6MAbokxgCLIMbQJ50t9FtK+YEJ\n", - "PvOErq4P0GcW8AW6ZBFfYFEEGfqmy5ky/1BKuXet9dydDiyl7ErzeOknJXlph2MA6A0RBuiaEAMs\n", - "ihhDX3X5SOybJ/lgKeXm2x1USrl6kprmsdi7Orw+QC+YFQPMgiADLIJHXNN3XUaZf0xy4ySnlVJu\n", - "u9UBpZRDk5yS5CFJ9ib5nQ6vD7DWxBhgFtyqBCyKGAPdRpljkrwjyfWTnFJKuevgzlLKLZJ8JMld\n", - "knw7ySNqrX/Q4fUB1pIYA8yCGAMsitkxsE9nUabW+q0kD07yuiTXSfLu9tHYKaXcI02QOTzNo7GP\n", - "rrXWrq4NsI7EGGAWxBhgUcQYuKouZ8qk1np5ksemWcT3mkneXkp5XpL3pAk1ZyS5a631H7u8LsA6\n", - "EWOAWRFjgEUQY2C4Lp++lCSpte5N8rRSyvlp4sxT2l2nJHlIrfUbXV8TYB0IMcCsiDHAoogxsL1O\n", - "Z8oMqrX+cZJHJ7kiyeVJnizIAFyVmTHArLhVCVgUs2NgNBPNlCml3DfN05N2ckGSFyd5Ypo1Zp6Q\n", - "5OLBA2qt755kDACrTIQBZkmIARZFiIHxTHr70jszWpRJkl3t9tAkdeBzu9rf95twDAArR4wBZkmM\n", - "ARZFjIHJTLOmzK6dD9nxc5OeA2CliDHArAkywKIIMjC5iaJMrXVma9EArBMxBpg1MQZYFDEGptf5\n", - "05cAEGOA2RNjgEURY6A7ogxAh8QYYNbEGGCRBBnoligD0AExBpgHQQZYFDEGZmPiKFNKOSXJd2qt\n", - "92tfvyajP5Hpe2qtPz/pGAAWTYwB5kGMARZFjIHZmmamzFFJvj3w+vgJzrE3iSgDrBwxBpgHMQZY\n", - "FDEG5mOaKHNaku8MvP7LCc4x9swagEURYoB5EWOARRJkYH4mjjK11h/f9PpRU48GYAmJMcA8CTLA\n", - "oogxMH+dLfRbSrlvku+rtf59V+cEWCQxBpgnMQZYFDEGFqfLpy+9pd0e1OE5AeZOjAHmSYwBFkWM\n", - "gcXrMsrsl+SKDs8HMFdiDDBPYgywSIIMLIfdHZ7r7CQHlFIO7PCcADN33jkXCjLA3Fx6xgWCDLAw\n", - "e07/qiADS6TLKPPWJLuSHNPhOQFmRowB5k2MARZFjIHl1OXtSy9K8oQkT0/ytg7PC9ApIQaYNzEG\n", - "WBQhBpZbl1HmAUn+OcndSykvTfLJUT5Ua31Fh2MA2JIQAyyCGAMskiADy6/LKPOygd9/ecTP7E0i\n", - "ygAzI8YAiyDGAIskxsDq6DLKfHGCz+zt8PoASYQYYLEEGWBRxBhYPZ1FmVrrTbs6F8AkxBhgkcQY\n", - "YJEEGVhNXc6UAZg7IQZYNDEGWCQxBlZbZ1GmlPKsJN+ttf7+CMfeIclPJflMrfXNXY0B6A8xBlg0\n", - "MQZYJDEG1kOXM2WeleTbSXaMMkmuaI//ZBJRBhiJEAMsC0EGWBQxBtbLom5f+o92e/iCrg+sEDEG\n", - "WBZiDLBIggysn0VFmeu22wMWdH1gBYgxwLIQY4BFEmNgfc01ypRS9k9yhyT/t33rC/O8PrD8hBhg\n", - "mYgxwCKJMbD+Jo4ypZQ9SfZuevvqpZQrRvj4rnb7kkmvD6wXMQZYJmIMsEhiDPTHtDNldo343mb/\n", - "neR5tdaXT3l9YIUJMcAyEmSARRJkoF+miTLHttu9aULMu5N8N8n9MzzMXJ7kgiRn1FpHmVEDrCEx\n", - "BlhGYgywSGIM9NPEUabW+t7B16WU05J8p9b6vqlHBawdIQZYVmIMsEhiDPRbZwv91lp/vKtzAetD\n", - "jAGWlRgDLJogA8zt6UullO9Pckmt9bJ5XRNYDCEGWHaCDLBIYgywYaooU0p5bJKDk1xca33NFvsP\n", - "TPKsJI9PckiSK0op70nytFrr6dNcG1g+Ygyw7MQYYJHEGGCzaR6J/UNJXpVmod9fH3LYK5M8ctP1\n", - "fiLJPUsp96u1fmjS6wPLQYgBVoEYAyySGAMMs3uKzz6w3Z6b5GWbd5ZSjsq+IPPBJA9L8pAk70ly\n", - "jSR/0c6kAVbQeedcKMgAS+/SMy4QZICFEmSA7Uxz+9I92u1ra617ttj/mHZ7XpKfqLV+M0lKKW9L\n", - "8qEkP5bk+CQvn2IMwByJMMAqEWOARRJjgFFMM1PmNu32vUP2H9tu/2ojyCRJrfWKJH/cvnzQFNcH\n", - "5sSsGGCVmB0DLNKe078qyAAjm2amzA3SrCfzmc07SinXb/cnzayYzTbeu90U1wdmTIgBVokQAyyS\n", - "EANMYpooc40ke2qt/73Fvtu2271JPr7F/vPbfdeZ4vrADAgxwKoRY4BFE2SASU1z+9K3kuwupRy8\n", - "xb6NKHNRrfWLW+z/viS7prg20DG3KAGrxm1KwKK5VQmY1jQzZc5KE19+JMlHNu27W7s9fchnb9xu\n", - "L5ri+sCURBhgVYkxwCIJMUBXpoky708TZX4tA1GmlHK9JPdrX5465LNHtdszp7g+MCExBlhVYgyw\n", - "SGIM0LVposyfpQkyDy+lnJPktUl+MMnvJTkoyZ4krx/y2dJuPznF9YExCDHAKhNjgEUTZIBZmHhN\n", - "mVrrvyZ5dpq1YX4rza1K78u+W5de0h5zJaWU2ya5T5qFfk+e9PrAaKwVA6wy68YAi2bdGGCWplno\n", - "N7XW303ym0kuThNndiX5dpITkjx58/GllN1pZtjk/7V35/HW1QW9x7+AimES3STHFLxqKs5EzuTA\n", - "xTF91ctfaJmKWYqoec26mXO+slJTKyXFruGE4s+b5kComWUJKs4GYRmDiqYIIso83T/WOjyH85xz\n", - "njPsvdf0fr9e57Wes/fae/0O5/cszvk8a0hyfpK/3872gdUthRgxBhgyMQbokhgDLMJ2Tl9KktRa\n", - "/6yUclSSO6aJMqfUWi9eY/WfShNl3pjk67XWS7e7fWAHEQYYAzEG6JoYAyzKtqNMkrQR5nMbWO+c\n", - "JMfMYptAQ4gBxkKMAbomxgCLNpMoAyyOCAOMiRADdE2IAbokysAACDHAmAgxQB+IMUAfiDLQQyIM\n", - "MEZiDNAHYgzQJ6IM9IAIA4yVEAP0hRgD9JEoAx0RYoCxEmKAvhBigL4TZWBBRBhgzIQYoE/EGGAo\n", - "RBmYExEGGDMRBugjMQYYGlEGZkiIAcZMiAH6SowBhkqUgW0QYYCxE2KAvhJigDEQZWATRBhgCoQY\n", - "oM/EGGCWSil7J3l6kl9Mcrsk+yQ5P8nBtdZ/n/f2RRnYBSEGmAIhBug7MQaYtVLKfZO8N8mNkpyU\n", - "pCa5PMmtkuy2iDGIMrCCCANMhRAD9J0QA8xLKeV2ST6c5NtJDq21frGLcYgyTJ4IA0yJEAMMgRgD\n", - "LMDr0xwN85Ba6+ldDUKUYXJEGGBqhBhgKMQYYBHao2QenORtSS4spfxWmlOWfpTkP5N8sNZ6ySLG\n", - "IsowCUIMMDVCDDAkYgywYL/QLg9KckaS6694/hullF+qtX5+3gMRZRglEQaYIiEGGBIhBujQ7drl\n", - "j5IcnuSTSf47yS2T/E6SI5IcX0q5fa11rr9cijKMgggDTJUQAwyNGAP0wE+0y9fVWo9b9vjpSY4s\n", - "peyX5GFJDkvyxnkORJRhsIQYYKqEGGCIxBgYn5veap+uh5BcuKV9y2Xtcq81nj8hTZQ5YCtvvhmi\n", - "DIMhwgBTJsQAQyXGAD10drvcb43nd1/QOEQZ+kuEAaZOiAGGSogBeu4T7fIRSX5/lefv2i6/Mu+B\n", - "LKz+wEZ8+6zzr/kAmKKLTzvnmg+AobnqlO8JMkDv1Vo/meRLSQ4opbxk+XOllHsmeXySc5Mct/Or\n", - "Z8uRMnRKfAFwRAwwfEIMMEC/nuaImReVUh6V5DNJbp7koUkuSfLYWusF8x6EKMNCiTAADSEGGAMx\n", - "BhiqWuu/lVLuluT5aS7q++Q0R8e8K8nLaq3/sYhxiDLMnRADIMIA4yHEAGNRa/16kqd2OQZRhpkT\n", - "YQAaQgwwJmIMwOyJMmybCAOwgxADjI0YAzA/ogxbIsQA7CDEAGMjxAAshijDhogwANcmxABjJMYA\n", - "LM2sjb0AACAASURBVJYow6pEGICdCTHAWIkxAN0QZbiGEAOwMyEGGDMxBqBbosyEiTAAqxNigDET\n", - "YgD6Q5SZEBEGYG1CDDB2YgxA/4gyIybCAKxPiAGmQIwB6C9RZmSEGID1CTHAFAgxAMMgygycCAOw\n", - "a0IMMAVCDMDwiDIDI8IAbIwQA0yFGAMwXKLMAAgxABsjxABTIcQAjIMo00MiDMDGCTHAVAgxAOMj\n", - "yvSACAOwOUIMMCViDMB4iTIdE2QANkaIAaZEiAGYBlEGgN4SYoApEWIApkeUAaA3RBhgaoQYgGkT\n", - "ZQDolBADTJEYA0AiygDQASEGmCIhBoCVRBkAFkKIAaZIiAFgPaIMAHMjxABTJcYAsBGiDAAzJcQA\n", - "UyXEALBZogwA2ybEAFMlxACwHaIMAFsixABTJcQAMCuiDAAbJsQAUybGADBrogwA6xJigCkTYgCY\n", - "J1EGgJ0IMcCUCTEALIooA0ASIQZAjAFg0UQZgAkTYoCpE2IA6JIoAzAxQgwwdUIMAH0hygBMgBAD\n", - "TJ0QA0AfiTIAIyXEAIgxAPSbKAMwIkIMgBADwHCIMgADJsIANIQYoGuXffnsZZ/t29k4GBZRBmBg\n", - "hBiAHcQYoGvXjjGwOaIMwAAIMQA7CDFA14QYZkWUAegpIQZgByEG6AMxhlkTZQB6RIgB2EGIAfpA\n", - "iGGeRBmAjgkxANcmxgB9IMawCKIMQAeEGIBrE2KAPhBiWDRRBmBBhBiAaxNigL4QY+iKKAMwR0IM\n", - "wM7EGKAPhBj6QJQBmDEhBmBnQgzQF2IMfSLKAMyAEAOwMyEG6Ashhr4SZQC2SIgBWJ0YA/SFGEPf\n", - "iTIAmyDEAKxOiAH6QohhSEQZgF0QYgBWJ8QAfSLGMESiDMAqhBiA1QkxQJ8IMQydKAPQEmIA1ibG\n", - "AH0ixjAWogwwaUIMwNqEGKBPhBjGSJQBJkWEAVifEAP0jRjDmIkywKiJMAAbI8YAfSLEMBWiDDAa\n", - "AgzA5ggxQN+IMUyNKAMMkgADsDVCDNA3QgxTJsoAvSfAAGyPEAP0kRgDogzQMwIMwOyIMUDfCDFw\n", - "baIM0BkBBmD2hBigj8QYWJ0oAyyEAAMwP0IM0EdCDOyaKAPMnAADsBhiDNBHYgxsnCgDbJsIA7A4\n", - "QgzQR0IMbI0oA2yKAAOweEIM0FdiDGyPKAOsSYAB6I4QA/SVEAOzI8oASQQYgL4QY4C+EmNg9kQZ\n", - "mCABBqBfhBigr4QYmC9RBkZOgAHoJyEG6DMxBhZDlIEREWAA+k+MAfpKiIHFE2VgoAQYgOEQYoA+\n", - "E2OgO6IMDIAAAzA8QgzQZ0IM9IMoAz0kwgAMkxAD9J0YA/0iykDHBBiA4RNjgD4TYqC/JhtlSimP\n", - "S/K0JHdPct0kX0vyniSvqrVeuGLdf0py8C7e8vq11svmMFRGRIABGA8hBug7MQY2r5Ty5iRPSvKO\n", - "Wuuvz3t7k4sypZTdkxyT5PFJ/jvJ+5JcnOQBSV6c5DGllPvVWn+wysv/Osn5a7z1lTMfLIMmwACM\n", - "jxAD9J0QA1tXSnl5miCTJFcvYpuTizJJfiNNkDkpyaFLR8WUUvZI8uokz0zyJ0mOWOW1f1JrPX1R\n", - "A2U4BBiAcRNjgL4TY2B7SinPSPL7SY5P8vBFbXf3RW2oR36tXb50+WlKtdYrk/xeku8neVIp5fpd\n", - "DI7+u/i0c3b6AGB8rjrle9d8APTRZV8++5oPYOtKKSXJa5O8PskrF7ntKUaZm6Y5DOmMlU/UWi9N\n", - "8qkkeyY5cJXX7jbfodE3AgzAtAgxwBAIMTA7pZQHJHlbkvfWWp+ZBf/eP8XTl85Octskd0nyn6s8\n", - "f167/OlVnjullHK9JJck+UaSj6a5MPCZcxgnCya4AEyTAAMMgQgDs1dKuUua68yelB1n1SzUFKPM\n", - "MWku6ntUKeW6SU5IclGSmyV5UJL7tuvtuew1pyc5N8l3k1yW5MZJHpzk6UkeX0o5pNb62UUMntkQ\n", - "YAAQY4AhEGNgPkopt0rTA85K8uiu7qY8uShTa31rKWX/JM9PcuyKp7+f5iiYpT8vvebJK9+nlLJn\n", - "kqOSHJ7kdUnuNZcBMxMiDACJEAMMgxAD81VK2TvJh9McdPGwWusFXY1lclEmSWqtLy2lHJPkIWmu\n", - "MXNJmlOZPpzkxCQ3SXLaLt7j0lLK05M8LslBpZS9aq0XzXXgbIgAA8ByQgwwFGIMLMytk9wuyQeT\n", - "PKe5zu81fqZdHlhKeVWSb9ZaXzuvgUwyyiRJrfWsJEcvf6yUcvMkd05yZvv8rt7j0lLKRWlOdfrx\n", - "NKdBsUACDABrEWOAIRBiGLL9b3LDroeQc/9rSy+7ul0+Iskj11jnDu3HF9PcmWkuJhtl1vDidnn0\n", - "umu1Sik/k+R/JDm31vrduY2KJAIMALsmxABDIcZAd2qtX8oad6MupfxCko8neXut9QnzHosok6SU\n", - "cp0kf5DkKUlOSfLqZc8dkuYUp3fVWi9f9vj1k7yx/fTNixvtNAgwAGyUEAMMhRADg+CW2PNWSjki\n", - "zfVkvp5knyQPTHLzJJ9L8sgVV12+RZro8ppSyr+kuRX2jZIcnOaOTSdmxxE2bIEAA8BmCTHAkIgx\n", - "wFomGWWSXJzmltbXTfK9JF9Ic6TM22utV69Y9yNJ/ihNhLl7koemuULzvyd5RZKjaq1XLGjcgyfA\n", - "ALAdYgwwFEIMsBGTjDK11mOSHLPBdb+V5IXzHM9YCTAAzIIQAwyJGAPDVmv9p6xxvZl5mGSUYfYE\n", - "GABmSYgBhkSIAbZKlGFLRBgA5kGMAYZEjAG2S5RhlwQYAOZJiAGGRIgBZkmU4VoEGAAWQYgBhkaM\n", - "AeZBlJkwAQaARRJigKERYoB5E2UmQoABoAtCDDBEYgywKKLMCAkwAHRJiAGGSIgBuiDKDJwAA0Af\n", - "CDHAUIkxQJdEmQERYADoCxEGGDIhBugLUaanBBgA+kaIAYZOjAH6RpTpCREGgD4SYoChE2KAPhNl\n", - "OibGANAnIgwwFmIMMASiDABMnBADjIUQAwyNKAMAEyPCAGMjxgBDJcoAwAQIMcDYCDHAGIgyADBS\n", - "QgwwRmIMMCaiDACMhAgDjJUQA4yVKAMAAybEAGMmxgBjJ8oAwMAIMcCYCTHAlIgyANBzIgwwBWIM\n", - "MEWiDAD0kBADTIEQA0ydKAMAPSHEAFMhxgA0RBkA6IgIA0yJEAOwM1EGABZIiAGmRowBWJsoAwBz\n", - "JsQAUyPEAGyMKAMAMybCAFMlxgBsjigDADMgxABTJcQAbJ0oAwBbIMIAUyfGAGyfKAMAGyDCAAgx\n", - "ALMmygDAKkQYABEGYN5EGQCICAOwRIgBWBxRBoDJEWAArk2IAeiGKAPA6IkwADsTYgC6J8oAMDoi\n", - "DMDqhBiAfhFlABg8EQZgbUIMQH+JMgAMigADsGtCDMAwiDIA9JoIA7AxQgzA8IgyAPSKCAOwcUIM\n", - "wLCJMgB0SoQB2BwhBmA8RBkAFkaAAdgaIQZgnEQZAOZGhAHYHjEGYNxEGQBmRoQB2D4hBmA6RBkA\n", - "tkyEAZgNIQZgmkQZADZEgAGYLSEGAFEGgFWJMACzJ8QAsJwoA0ASEQZgXoQYANYiygBMlAgDMD9C\n", - "DAAbIcoATIAAAzB/QgwAmyXKAIyQCAOwGEIMANshygCMgAgDsDhCDACzIsoADJAIA7B4YgwAsybK\n", - "AAyACAPQDSEGgHkSZQB6RoAB6JYQA8CiiDIAHRNhALonxADQBVEGYMFEGIB+EGIA6JooAzBnIgxA\n", - "fwgxAPSJKAMwQwIMQP8IMQD0lSgDsA0iDEA/CTEADIEoA7AJIgxAfwkxAAyNKAOwDhEGoP/EGACG\n", - "SpQBaAkwAMMhxAAwBqIMMFkiDMCwCDEAjI0oA4ye+AIwXEIMAGMmygCDJ7oAjIsQA8BUiDJA74ku\n", - "AOMnxAAwRaIM0CnBBWC6hBgApk6UAeZKdAFgOSEGAHYQZYBtEV0A2BUhBgBWJ8oAaxJcANgOMQYA\n", - "1ifKwISJLgDMmhADABsnysCIiS4ALIIQAwBbI8rAQAkuAHRJiAFgyEopv5bkYUl+Lsktk+ye5BtJ\n", - "Tkjy8lrrtxcxDlEGekp0AaBvhBgAxqCUcp0kb0tyeZKTknwsTR+5f5Ijm1XKvWutZ8x7LKIMdEBw\n", - "AWAohBgARuiqJC9P8ppa67lLD5ZSdkvypiRPTvLSJE+Y90BEGZgD0QWAIRNiABizWutVSV6wyuNX\n", - "l1JelybKHLiIsYgysAWiCwBjI8QAQJJkr3Z57rprzYgoAysILgBMhRADADs5rF1+YhEbE2WYHNEF\n", - "gKkTYwBgZ6WUeyZ5WpLzkvz5IrYpyjA6ogsA7EyIAYC1lVLumOSDSa5O8tha6zmL2K4ow6AILgCw\n", - "cUIMwOKcdeap1/z5XrlbhyNhs0op90hyQpIbJjms1voPi9q2KEOviC4AsD1CDMDiLA8xU3SLfW/Q\n", - "9RBy7n9t7/WllIcnOS7J5UkeVmv9+AyGtWGiDAslugDA7AkxAIsx9QgzNqWUZyZ5TZJvJHlErXXh\n", - "32BRhpkRXABgcYQYgMUQYsanlLJnkqOSHJ7kn5M8pta6kFtgryTKsGGiCwB0S4gBWAwhZvQOSxNk\n", - "fpTkS0meV0pZbb0P11o/Os+BiDIkEVwAoK+EGID5E2EmZ7d2eYMkz1pjnauTXJBElGH7RBcAGA4h\n", - "BmD+hJjpqrW+Jclbuh5HIsqMhugCAMMnxgDMlxBD34gyAyC4AMB4CTEA8yPC0HeiTA+ILgAwLUIM\n", - "wPwIMQyJKNMxQQYApkGIAZgfIYahEmUAAOZEiAGYDxGGsRBlAABmSIgBmA8hhjESZQAAtkmIAZgP\n", - "IYaxE2UAALZAiAGYPRGGqRFlAAA2SIgBmD0hhikTZQAA1iHEAMyeEAMNUQYAYBViDMDsiDCwOlEG\n", - "AKAlxADMjhADuybKAACTJsQAzI4QA5sjygAAkyPEAMyGCAPbI8oAAJMgxADMhhADsyPKAACjJcQA\n", - "bJ8IA/MjygAAoyLEAGyfEAOLIcoAAIMnxABsnxADiyfKAACDJMQAbI8IA90TZQCAQRFjALZOiIF+\n", - "EWUAgN4TYgC2ToiB/hJlAIBeEmIAtkaEgeEQZQCA3hBiALZGiIFhEmUAgE4JMQBbI8TA8IkyAMDC\n", - "CTEAmyfCwPiIMgDAQggxAJsnxMC4iTIAwMwJMABbJ8TAdIgyAMCWiS8A2yfCwHSJMgDAhggwALMj\n", - "xACJKAMArCC+AMyHEAOsJMoAwESJLwDzJcIAuyLKAMAECDAAiyHEAJshygDAiIgvAIsnxABbJcoA\n", - "wACJLwDdEWGAWRFlAKDnBBiA7gkxwDyIMgDQE+ILQL8IMcC8iTIAsGDiC0A/iTDAookyADBHAgxA\n", - "vwkxQJdEGQCYAfEFYBhEGKBPRBkA2ATxBWB4hBigr0QZAFiDAAMwXEIMMASiDACTJ74ADJ8IAwyR\n", - "KAPAZIgvAOMixABDJ8oAMEoCDMA4CTHAmIgyAAya+AIwbiIMMGaiDACDIL4ATIcQA0yFKANAr4gv\n", - "ANMkxABTJMoA0BkBBmC6RBgAUQaABRBfAEiEGICVRBkAZkZ8AWAlIQZgbaIMAFsiwACwGhEGYONE\n", - "GQDWJb4AsCtCDMDWiDIAJBFfANgcIQZg+0QZgAkSYADYLBEGYPZEGYARE18A2A4hBmC+RBmAERBf\n", - "AJgVIQZgcUQZgIERYACYJREGoDuiDEBPiS8AzIsQA9APogxAx8QXABZBiAHoH1EGYIEEGAAWRYQB\n", - "6D9RBmAOxBcAuiDEAAyLKAOwDeILAF0TYgCGS5QB2CABBoC+EGIAxkGUAVhBfAGgj4QYgPERZYDJ\n", - "El8A6DshBmDcRBlgVIQWAIZOiAGYDlEG6B1hBYCpEWIApkmUAeZCWAGA9QkxAIgywKpEFQCYPSEG\n", - "gOVEGRgxYQUAuifEALAWUQZ6TlgBgOERYgDYCFEG5kxUAYBpEGIA2KzJRplSyuOSPC3J3ZNcN8nX\n", - "krwnyatqrReusv6jkzw7yd2S7JnkrCTHJfnTWuvFixo33RBWAIC1iDEAw1RKuVOSFyU5OMk+Sc5J\n", - "8pEkL6m1fmMRY5hclCml7J7kmCSPT/LfSd6X5OIkD0jy4iSPKaXcr9b6g2Wv+e0kr0lyfpL3J7kg\n", - "yS+k+eY9uJTyoFrr5Qv8MtgCYQUAmBUhBmDYSin3TvKxJHsk+fs0B17cIcnhSR5RSrlXrfXMeY9j\n", - "clEmyW+kCTInJTl06aiYUsoeSV6d5JlJ/iTJEe3jN28/PyfJzy3VslLKbknemeRXkjw1yesW+2VM\n", - "j6gCAHRJiAEYlTcmuV6SR9Vaj196sJRyZJK/TPKqJI+Z9yB2n/cGeujX2uVLl5+mVGu9MsnvJfl+\n", - "kieVUvZsnzoszelKb1h++FKt9eokf9B+evjcRz0Sl3357C1/AAAs2llnnnrNBwDjUEq5R5I7Jfnk\n", - "8iCTJLXW1yf5ZpJHlVJ+ct5jmeKRMjdNcnWSM1Y+UWu9tJTyqSQPS3JgkhOT3Lt9+qRV1j+9lPLd\n", - "JHctpVy/1nrJ/IbdD+IIADB2AgzA6K35e37rxDRnxdwzyQnzHMgUo8zZSW6b5C5J/nOV589rlzdu\n", - "l7dul99d5/32TbJ/kn+f0RjnSlgBALg2IQZgUjbye37S/J4/V1OMMsekuajvUaWU66apXhcluVmS\n", - "ByW5b7ve0ulLN0xzZM0Fa7zfRUl2S7L3fIa7OmEFAGB7hBiAybphu1zv9/xkAb/nTy7K1FrfWkrZ\n", - "P8nzkxy74unvJ7lk2Z+Xu2KNt9xtO+O5x1P33eIrt/o6AACS5F65W9dDABi0L33qX7sewnbN5ff8\n", - "zZjihX5Ta31pmlOYnpbkpUmel+aqyrdM8r00R8ac1q7+wzTfkB9b4+32WrYeAAAA0G9Lv793/nv+\n", - "5I6UWVJrPSvJ0csfa29/feckZ7bPJ80Fge+e5FZZ/ZoxN09yVVa5cPB6DjnkkIWVNwAAAJiVEfw+\n", - "u/T7+63WeP7m7fL0eQ9kkkfKrOPF7XJ5rDmxXT5o5cqllNumOY/o32qtF895bAAAAMD2rfd7/u5J\n", - "7pPkyiQnz3sgokySUsp1SikvSvKUJKckefWyp9+d5LIkT2yPpFl6ze5JXtZ++pZFjRUAAADYulrr\n", - "55OcmuTAUsqhK54+Is2RMsfXWs+d91iGfsjRlpRSjkjykCRfT7JPkgem+Y/+uSSPrLV+Z8X6v5Pk\n", - "lWlul/3BJD9Kcv80pzp9Oskv1FovW9gXAAAAAGxZKeW+Sf4hzcEqH0ryzSQ/m+SQNNeavU+t9b/m\n", - "PY495r2BPjrggAPulOTIJD+f5MZJvpjkj5I8s9b6o5Xrn3rqqScdcMABX05zL/MHJDkoza2z/irJ\n", - "b9VaL1n5GgAAAKCfTj311G8ccMABH0zTBO6f5H5pLvD7/5L8Wq31zA6HBwAAAAAAAAAAAAAAAAAA\n", - "AAAAAAAAAAAAAAAAAAAAAEt263oAQ1JKuVOSFyU5OMk+Sc5J8pEkL6m1fmOL77lvkq8mOaXWev9d\n", - "rHu/JM9Lcs8kP57k7CTvT/KyWut5W9k+3epyTpVSjknyhF283e1rrf+xlXHQjVnNqVLKI5P8UpKf\n", - "T7J/kusm+XaSjyd5ea31P9d4nf3UyHQ5p+ynxmeG8+k+SR6X5D5JbpNkryQXJPlCkrcleWut9epV\n", - "XmcfNTJdzin7qHGax8/ny977xUlenOSTa/2cbj81PdfpegBDUUq5d5KPJdkjyd8nOSvJHZIcnuQR\n", - "pZR71VrP3OB77ZPkZUl+Oskhaf6y7/SDw4rX/HKSmuSSJB9I8p0kByX57SQPa7d//ua/MrrS9Zxa\n", - "5t1Jvr7Gc3b8AzLLOZXkDUlukuSzSd6e5Ko0+5wnJnlMKeVBtdaTV2zffmpkup5Ty9hPjcCM59Mr\n", - "ktw7yaeTHJfkwiQ/k+TQJA9K8sAkT1qxffuokel6Ti1jHzUSM55TK9/7t9IEmWSNn9Ptp6ZJlNm4\n", - "Nya5XpJH1VqPX3qwlHJkkr9M8qokj9nge+2T5Mhs8JfmUspeSf4qyaVJ7ldr/cKy516R5LlJXtAu\n", - "GY7O5tQKR9da/3ELr6N/Zjmnjk7yNyv/RaiU8qIkL0nyZ2n+BWnpcfupcepsTq18rf3UKMxyPr06\n", - "yadrrWcvf7CUcvskpyR5Qinlt2utP2gft48ap87m1Ar2UeMxyzl1jVLKo5O8PsnxSR6+xjr2UxO1\n", - "e9cDGIJSyj2S3CnNYWbHL3+u1vr6JN9M8qhSyk9u5P1qrWfWWnevte6R5NYbeMlDk+zbvHTHX87W\n", - "S9OU1F8vpfh+DkQP5hQjM4c59YdrHKL7F+3ywBWP20+NTA/mFCMyh/n0tyt/eW6dkWZ/c2GSHy17\n", - "3D5qZHowpxiZWc+pZe973yTvSvKhJM9aZ1X7qYnyDd2Ye7fLk9Z4/sQ0Rx3dcwvvvZHr+qy5/Vrr\n", - "hUm+nOYv8O22sH260fWc2s769NM859Rye7XLcze6ffupwep6Ti1nPzV8c51PpZS922uC/F2an2+P\n", - "qLVeuZHt20cNVtdzajn7qHGY+ZwqpdwxzWlIn0tyWJpTdze9ffupcXP60sYsHXnw3TWeX6rq+/dg\n", - "+6fNaQzMVtdzarkPlVKul+ZQyW8n+USSP6u1fmUB22Z2FjWnDmuXn9jG9u2nhqHrObWc/dTwzW0+\n", - "lVK+mOQu7acfSXKXVS4cbR81Pl3PqeXso8ZhpnOqlHKLJCekmROPrLVeWkqZ1fbtp0bEkTIbc8N2\n", - "ecEaz1/ULvce6faZvT58T89OU+7fkuTPk/xtmouaPSHJye2dUhiOuc+pUsqtk7wwzQ+df7zo7bNw\n", - "Xc+pxH5qTOY5n45JclSSf0xzsfvj2n+dXtT26UbXcyqxjxqbmc2p9hSnE9IcRfWQDV6c135qohwp\n", - "szlXrPH4og5Z7Hr7zF5n39Na6/NXPtaeo/riNL8kvaGUcsta63qHWdI/c5lTpZSbJflwkp9I8uRa\n", - "6ymL3D6d6mxO2U+N0sznU631tUt/LqUclORfk7yvlHKXWusl894+netsTtlHjda25lQ7B96f5GZJ\n", - "Dq61fnOR22d4HCmzMT9slz+2xvN7rVhvbNtn9nr5Pa21XlVrfXGSM5PcNM0tABmGuc2pUsp+Sf45\n", - "zeGyz6y1vmWR26czXc+pVdlPDdZC9hHtbdU/nuQ2ufbdvOyjxqfrObXW+vZRwzWrObV3kvsm+UqS\n", - "J5VSXrX0keQP2nX2bx/7wzlsn4FxpMzGnNEub7XG8zdvl6ePdPvMXt+/p+cl2S/JDTraPps3lznV\n", - "/gvhB9IczfDrtdZ3LnL7dKrrObUr9lPDssh9xHntcvkdUuyjxqfrObWR1+wX+6ghmfWcul+S+6/z\n", - "Xs9Jcn6SF81p+wyEKLMxJ7bLB618oj087T5Jrkxy8hy3/5x2+29Ysf2901yI7Lwk/zGn7TN7Xc+p\n", - "NZVS9krys2kOnVzvonb0y8znVCnlMWnOk784yaG11n/Zxfbtp8al6zm13vvYTw3PQv6/V0rZLTsu\n", - "0HrGsqfso8an6zm13mvso4ZpJnOqvX7MqmeklFJulWYe/WutdeWRV/ZTE+X0pQ2otX4+yalJDiyl\n", - "HLri6SPSVMvja63X3M6zlPLWUspppZSXz2AIJyT5XpJHllLuvOK5FybZM8k7nK86HF3PqVLKXUsp\n", - "z2x/aFj++O5pLlR3gyTvrbV+f7vbYjFmPadKKS9L8u4kX0ty0AZ+ebafGpmu55T91LjMcj6VUu5Q\n", - "Snl1KeUmq2zqBUnumOSUWutnlj1uHzUyXc8p+6jxWdDP5+tdF8Z+aqIcKbNxT03yD0k+UEr5UJJv\n", - "pinghyQ5J03VXO6Wae4hv9POvZRyw/b9kh2HQd6ilPLc9s9fr7W+e2n9WutFpZQjk7wryYmllA8k\n", - "OTfJPdLcz/5rSV667a+QRetsTrXr/HmSPyql/EuaYr93mvn0P5N8NckztvXV0YWZzKlSysFJnp/m\n", - "X/hOTHLkGrdw/MzSvLKfGq3O5lTsp8ZoVv/f2zPJs5M8o5Ty6SSnJLleknsluX37Xr+6/AX2UaPV\n", - "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\n", - "AABJRU5ErkJggg==\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 407, - "width": 562 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure()\n", - "plt.contourf(sigma_vals, strike_vals, prices['acall'])\n", - "plt.axis('tight')\n", - "plt.colorbar()\n", - "plt.title(\"Asian Call\")\n", - "plt.xlabel(\"Volatility\")\n", - "plt.ylabel(\"Strike Price\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot the value of the European put in (volatility, strike) space." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "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\n", - "eZ2V5HmL/RMAAAAAM3hEkjOTvLGU8qYkFyT5uSQnJNmb5LEbtj86yfWTHLnJvn43ya2TvLCUcu8k\n", - "/9luf1KaT916xDz+AFsZ60TPxUl+KcnD04xqfTTJg5Pcqtb65Y0b11qfleS/J/n3JP93kt9IcsU0\n", - "Ez2/tD4MAQAAAP1Wa31vktskOSPJ7dPEmBskeVmSW9ZaP73hKfvaX5vt6z+S3CLJ6WkGQh7R7vt1\n", - "SW5Ta13YZVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3TAsg9glZRSbpjkKUnumOSI\n", - "JHuTvC3JU2utn5tyn3uSfDLJ2bXWO+yw7fFJHp/k1kl+Isnnk5yR5I9qrRdO8/os1zLPqVLKy5I8\n", - "aIfdHVdr/Y9pjoPl6OqcKqXcM8mvJLlVkmOTXDHJF5O8M8nTaq3/ucXzvE8NzDLPKe9Tw9Ph+XS7\n", - "JA9Icrsk10tyaJJvJflIklckOb3Wum+T53mPGphlnlPeo4ZpHt+fr9v3qUlOTfLerb5P9z5FFw5a\n", - "9gGsilLKbZO8I8kVkvxDkvOT/HyShyY5qZRym1rreRPu64gkf5Tkp5KckOYNZL9vRjY8595JapLv\n", - "JXljki8nuWWS30ly9/b1v7H7PxnLsuxzap3XJPnsFo/5j8kK6fKcSvLCJEcm+XCSv0nyozTvOQ9O\n", - "cp9Syl1qrR/a8PrepwZm2efUOt6nBqDj8+kZSW6b5Kwkr05yUZKfTnJikrskuXOSh2x4fe9RA7Ps\n", - "c2od71ED0fE5tXHfD08TeZItvk/3PkVXhJ7JvSjJlZLcq9b65rU7SymnJPnfSZ6Z5D4T7uuIJKdk\n", - "wh/ESymHJnlBkkuSHF9r/ci6x56R5PeSPKm9ZXUs7Zza4MW11n+c4nn0T5fn1IuT/PXGf7kqpTwl\n", - "yVOTPCvNv3St3e99apiWdk5tfK73qUHo8nx6dpKzaq2fX39nKeW4JGcneVAp5Xdqrd9s7/ceNUxL\n", - "O6c28B41HF2eUz9WSvnlJM9L8uYk99hiG+9TdObAZR/AKiil3CzJDdOM2L15/WO11ucluSDJvUop\n", - "PznJ/mqt59VaD6y1XiHJdSd4yt2S7Gmeetn/4VunpSm+v1ZK8fe5InpwTjEwczin/nCL8eTntrc3\n", - "33C/96mB6cE5xYDM4Xx63cYfyFvnpnm/uSjJd9bd7z1qYHpwTjEwXZ9T6/Z7+yR/l+RNSR69zabe\n", - "p+iMk2Qyt21v37/F4+9LMx116yn2Pck6SVu+fq31oiT/X5o3hetP8fosx7LPqVm2p5/meU6td2h7\n", - "+7VJX9/71Mpa9jm1nvep1TfX86mUcni7xsr/SfP97cm11ksneX3vUStr2efUet6jhqHzc6qUcoM0\n", - "l2D9S5L7pblsedev732K3XLp1mTWJiS+ssXja/X/2B68/jlzOga6texzar03lVKulGZM9ItJ3p3k\n", - "WbXWjy3gtenOos6p+7W3757h9b1PrYZln1PreZ9afXM7n0opH01y4/bLtyW58SaLe3uPGp5ln1Pr\n", - "eY8ahk7PqVLKtZO8Jc05cc9a6yWllK5e3/sU2zLRM5mrtLff2uLx77a3hw/09eleH/5OP5/mXxhe\n", - "nuTPk7wuzcJzD0ryofYTclgdcz+nSinXTfLkNN/I/q9Fvz4Lt+xzKvE+NSTzPJ9eluT5Sf4xzQcS\n", - "vLr9V/RFvT7LsexzKvEeNTSdnVPt5V1vSTPtddcJF1D2PkVnTPTszg+3uH9R45rLfn26t7S/01rr\n", - "Ezfe117ze2qaH7xeWEo5uta63Ygp/TOXc6qUcs0kb01y1SS/Xms9e5Gvz1It7ZzyPjVInZ9PtdY/\n", - "W/t9KeWWSd6T5A2llBvXWr8379dn6ZZ2TnmPGqyZzqn2HDgjyTWT3LHWesEiXx8SEz2T+nZ7e8gW\n", - "jx+6YbuhvT7d6+Xfaa31R7XWU5Ocl+SoNB8nyWqY2zlVSrlOknelGRV+VK315Yt8fZZm2efUprxP\n", - "rayFvEfUWj+U5J1JrpfLf4qb96jhWfY5tdX23qNWV1fn1OFJbp/kY0keUkp55tqvJE9otzm2ve8P\n", - "5/D6YKJnQue2t8ds8fi12tvPDPT16V7f/04vTHKdJIct6fXZvbmcU+2/ZL4xzdTFr9Va/3aRr89S\n", - "Lfuc2on3qdWyyPeIC9vb9Z+M4z1qeJZ9Tk3ynOvEe9Qq6fqcOj7JHbbZ12OTfCPJU+b0+oyY0DOZ\n", - "97W3d9n4QDuad7sklyb50Bxf/7Ht679ww+sfnmaxuAuT/MecXp/uLfuc2lIp5dAkP5dmbHS7hQfp\n", - "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\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 407, - "width": 573 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure()\n", - "plt.contourf(sigma_vals, strike_vals, prices['eput'])\n", - "plt.axis('tight')\n", - "plt.colorbar()\n", - "plt.title(\"European Put\")\n", - "plt.xlabel(\"Volatility\")\n", - "plt.ylabel(\"Strike Price\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot the value of the Asian put in (volatility, strike) space." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "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\n", - "JPdon7eyfOmaaSZrRi12vjhNlPHdAwDAlLjWDAAzdM32dtzP+ckcfs4fXJSptZ5YSjkgyTOSnLTm\n", - "4e8l+cmqf17tshFvudt29uezp//rdl4OAAAAC9GDn2dn8nP+Zgxx+VJqrcem+TjsxyU5NsnT0lxV\n", - "+cZJvpNmMubz7dN/mOYP5Ooj3m7vVc8DAAAAltvKz+8L/zl/cJMyK2qt5yY5bvV97cdf3zbJOe3j\n", - "SXNB4DskuUmazyxf60ZJfpZ1Lhw8zqGHHjq38gYAAADT0oOfZ1d+fr/JiMdv1N5+edY7MshJmTGO\n", - "aW9Xx5pT29v7rn1yKeXmSa6b5D9qrT+e8b4BAAAA2zfu5/zdk9w9yeVJzpj1jogySUope5ZSnp3k\n", - "MUnOTPKyVQ+/LcmlSR7ZTtKsvGb3JM9rvzxhXvsKAAAAbF2t9VNJzkpyUCnlfmsePirNpMwptdbv\n", - "znpfuj5ytCWllKOS3D/JV5Lsk+Q+af6lfzLJg2qt31rz/D9K8pI0H5f9niQ/SnKvNEudTk/yq7XW\n", - "S+f2GwAAAAC2rJRyjyQfTDOs8t4k5yX55SSHprnW7N1rrf896/3YY9YbWEYHHnjgbZIcneTOSa6f\n", - "5DNJnp/kCbXWH619/llnnXXagQce+Nk0n2V+7yQHp/norL9K8ge11p+sfQ0AAACwnM4666yvHnjg\n", - "ge9J0wTuleSeaS7w+/+SPLzWes4Cdw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABW7Lbo\n", - "HeiSUsptkjw7ySFJ9klyfpL3J3lOrfWrW3zP6yb5QpIza6332uC590zytCR3SfILSb6W5F1Jnldr\n", - "vWAr22exFnlMlVKOT3LEBm93y1rrF7eyHyzGtI6pUsqDkvxmkjsnOSDJVZJ8I8mHkryg1vpfI17n\n", - "PNUzizymnKf6Z4rH092TPCzJ3ZPcLMneSS5M8ukkb0xyYq31inVe5xzVM4s8ppyj+mkW35+veu9j\n", - "khyT5N9GfZ/uPDU8ey56B7qilHK3JP+UZI8k/5Dk3CS3SnJkkt8opdy11nrOhO+1T5LnJblekkPT\n", - "/Me+yzcOa17zW0lqkp8keXeSbyU5OMmTkjyg3f73N/87Y1EWfUyt8rYkXxnxmBN/h0zzmEry2iT7\n", - "JvlEkjcl+Vmac84jkxxWSrlvrfWMNdt3nuqZRR9TqzhP9cCUj6cXJ7lbktOTnJzkoiS/lOR+Se6b\n", - "5D5JHrVm+85RPbPoY2oV56iemPIxtfa9/yBNkElGfJ/uPDVMoszkXpfkqkkeXGs9ZeXOUsrRSf4y\n", - "yUuTHDbhe+2T5OhM+ENzKWXvJH+V5JIk96y1fnrVYy9O8tQkz2xv6Y6FHVNrHFdr/ectvI7lM81j\n", - "6rgkf7v2b4RKKc9O8pwkf57mb5BW7nee6qeFHVNrX+s81QvTPJ5eluT0WuvXVt9ZSrllkjOTHFFK\n", - "eVKt9Qft/c5R/bSwY2oN56j+mOYx9XOllIckeXWSU5I8cMRznKcGavdF70AXlFLumOQ2acbMTln9\n", - "WK311UnOS/LgUsovTvJ+tdZzaq2711r3SHLTCV7y60mu27x053+crWPTlNTfLaX48+yIJTim6JkZ\n", - "HFPPHTGi+xft7UFr7nee6pklOKbokRkcT3+39ofn1tlpzjcXJfnRqvudo3pmCY4pembax9Sq971H\n", - "krcmeW+SJ455qvPUQPkDnczd2tvTRjx+apqpo7ts4b0nua7PyO3XWi9K8tk0/wHfYgvbZzEWfUxt\n", - "5/ksp1keU6vt3d5+d9LtO0911qKPqdWcp7pvpsdTKWVHe02Qv0/z/e1RtdbLJ9m+c1RnLfqYWs05\n", - "qh+mfkyVUm6dZhnSJ5Mcnmbp7qa37zzVb5YvTWZl8uDbIx5fqeoHLMH2Pz+jfWC6Fn1MrfbeUspV\n", - "04xKfiPJR5L8ea31c3PYNtMzr2Pq8Pb2I9vYvvNUNyz6mFrNear7ZnY8lVI+k+R27ZfvT3K7dS4c\n", - "7RzVP4s+plZzjuqHqR5TpZT9krwvzTHxoFrrJaWUaW3feapHTMpM5prt7YUjHr+4vd3R0+0zfcvw\n", - "Z/q1NOX+hCSvTPJ3aS5qdkSSM9pPSqE7Zn5MlVJumuRZab7p/LN5b5+5W/QxlThP9cksj6fjk7wm\n", - "yT+nudj9ye3fTs9r+yzGoo+pxDmqb6Z2TLVLnN6XZorq/hNenNd5aqBMymzOZSPun9fI4qK3z/Qt\n", - "7M+01vqMtfe1a1SPSfND0mtLKTeutY4bs2T5zOSYKqXcMMk/JrlWkkfXWs+c5/ZZqIUdU85TvTT1\n", - "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\n", - "lkmetfLkWuu5tdarp5n6SZITa617r/n11U3uw4ZqrbdMclT75TnrbPPNtdafJln5tKbfW+99SinX\n", - "SfKbaf59LcXHgwMA3Wf5EgB0w9uTvDrJXkl+K8nfrvOcw9vb1UuXXtzeHldrfeLKnbXWbyV5Tinl\n", - "sjRx5vmllBMn+cjt9vX3Wue+C5K8oZ2QeVGS/53kyWuetohryUyyzb9O8tQkty6l3LXW+rE1jx+R\n", - "5CpJPrfOYwAAW2JSBgA6oNb6vSSntF/usoSplHJQkpulWRb0lva+2ya5dZrpjheOeOuXJ/lxmuVM\n", - "/2tKu/vp9vZGU3q/mau1fjHJR9IEnPWmhlbuMyUDAEyNSRkA6I6T0lzo9z6llOvWWs9f9djK0qWP\n", - "1Fq/3v7zwe3t19uPjN5FrfWiUsqnk9w9yUFJ3jvpzpRSbp7kt5PcOclN01zs95rtr6SZLOmS16e5\n", - "EPJvl1KetHKh4lLKPdMsx7ooyRsXuH8AQM+IMgDQHe9K8qMkv5CkJHlNkpRSdksTR5IrL126Xnv7\n", - "rQ3e9xvt7fUn2YlSyh5JXpHkD3PlpUErF9i9LMkek7zXknl7kr9I8otpppH+pr1/9QV+f7iIHQMA\n", - "+snyJQDoiFrrT5K8s/1y9RKmeyTZL82nG9U57MpzkxydJsh8KMkjk9w+yXVqrXskud8c9mHqaq2X\n", - "ZOckzGOSpJSyT5oA5gK/AMDUmZQBgG45Kckjkty9lLJfrfW87Fy69L41H9W8MiFzww3e8wbt7bc3\n", - "2ng7lXN0++Vra61/uM7TFnEx32l5fZInJrlzKeXAJPdOcrU0n2h1xiJ3DADoH5MyANAtH0hyfpr/\n", - "hx9eStk9yWHtY29e89xPtLfXb6//sotSyjXTfKz16uePc9001465IjuX92zGZe3tXlt47VZNvM1a\n", - "65lJPpadF/xdWbpkSgYAmDpRBgA6pNZ6eZK3tV8+LMmhaULJhUnevea5n0tyVprA8KwRb/lHaSZB\n", - "zk8TfDZyyap/vt6I59xhzOvPn+A507ayzeuXUia5bs7r29vHJrldmuv4rA1eAADbZvkSAHTPSWmW\n", - "EN0xydPb+97RXhNlrf+bJtY8opTy4yQvqLWeW0rZN82Fep+ZZurlWbXWSzfacK31B6WU05PcJclL\n", - "SinfTfMR2Hu09z0t468pc1p7e8tSyuOTnJjmU5rulOTUGV1I94wkl7f7+KJSyp+m+SSlWyX5Qa31\n", - "C2uef3Kajwrf0X79llrrj2awXwDAwJmUAYCOqbWeluSc9stD2tuTRjz3vUn+OE14+f0kZ5dSLk/y\n", - "9ewMMi+vtR63iV14YpKLk9w6zVKfS9qvP5TkPklOGfPadyf5j/af/yLJ99NMsvxDmk892q5drmdT\n", - "a/12di61OiLN7/0H7b7fZZ3nX5yd/z5d4BcAmBlRBgC6aXU0+FaSD456Yq31z5PcLc2yp68n+Wma\n", - "i/q+K8n9aq1PHfHSK7LzY65Xv98Z7fu9O82yqZ8mOTvJXyW5bZKXjNmXnya5b5pI8o32td9K8k9J\n", - "VqZkdtnmRvu05vH1HJ1mCdeXklya5HtJPp7kyyOev3L/p2qtnxqzPQCALevypyMAAExd+wlTX0hy\n", - "syR/UGv96wXvEgDQUyZlAACu7P5pgsyFGbEsDABgGkQZAIArO7q9Pam9vgwAwEyIMgAArVLKAUke\n", - "GBf4BQDmQJQBANjpqDTX3PtErfXfF70zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n", - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo/1/MrL3Cbz9qMUAAAAASUVORK5CYII=\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": { - "image/png": { - "height": 407, - "width": 562 - } - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure()\n", - "plt.contourf(sigma_vals, strike_vals, prices['aput'])\n", - "plt.axis('tight')\n", - "plt.colorbar()\n", - "plt.title(\"Asian Put\")\n", - "plt.xlabel(\"Volatility\")\n", - "plt.ylabel(\"Strike Price\")" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Parallel Decorator and map.ipynb b/examples/Parallel Computing/Parallel Decorator and map.ipynb deleted file mode 100644 index 6118bdb..0000000 --- a/examples/Parallel Computing/Parallel Decorator and map.ipynb +++ /dev/null @@ -1,131 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Load balanced map and parallel function decorator" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from __future__ import print_function\n", - "from IPython.parallel import Client" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "rc = Client()\n", - "v = rc.load_balanced_view()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simple, default map: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n" - ] - } - ], - "source": [ - "result = v.map(lambda x: 2*x, range(10))\n", - "print(\"Simple, default map: \", list(result))" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Submitted tasks, got ids: ['b4d86123-967a-4f21-b9c5-8a77a69a3ced', '97401998-891d-4729-8288-ae1b97c28235', '1586cf7e-32c7-4864-bd0e-6aab16e07a3f', 'f6770223-59c3-4344-a69a-5d555d1dfb7f', '0ebb71da-6e16-44ac-917a-be978fd3787d', '582443c5-937e-45b0-9f13-5607c53cba60', '8d453d87-d70d-4fbd-a2c4-994ab3a8b6c7', '0a680757-1a59-4826-969c-523a45f3d76f', '63a3e983-a205-4f07-b494-ec86b2cdd004', '79b34cbb-aa93-4d11-8746-28969dbdd3ec']\n", - "Using a mapper: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n" - ] - } - ], - "source": [ - "ar = v.map_async(lambda x: 2*x, range(10))\n", - "print(\"Submitted tasks, got ids: \", ar.msg_ids)\n", - "result = ar.get()\n", - "print(\"Using a mapper: \", result)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using a parallel function: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n" - ] - } - ], - "source": [ - "@v.parallel(block=True)\n", - "def f(x): return 2*x\n", - "\n", - "result = f.map(range(10))\n", - "print(\"Using a parallel function: \", result)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Parallel Magics.ipynb b/examples/Parallel Computing/Parallel Magics.ipynb deleted file mode 100644 index 60596e2..0000000 --- a/examples/Parallel Computing/Parallel Magics.ipynb +++ /dev/null @@ -1,521 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using Parallel Magics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has a few magics for working with your engines.\n", - "\n", - "This assumes you have started an IPython cluster, either with the notebook interface,\n", - "or the `ipcluster/controller/engine` commands." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython import parallel\n", - "rc = parallel.Client()\n", - "dv = rc[:]\n", - "rc.ids" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Creating a Client registers the parallel magics `%px`, `%%px`, `%pxresult`, `pxconfig`, and `%autopx`. \n", - "These magics are initially associated with a DirectView always associated with all currently registered engines." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can execute code remotely with `%px`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px a=5" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px print(a)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "with dv.sync_imports():\n", - " import sys" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px from __future__ import print_function\n", - "%px print(\"ERROR\", file=sys.stderr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You don't have to wait for results. The `%pxconfig` magic lets you change the default blocking/targets for the `%px` magics:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxconfig --noblock" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px import time\n", - "%px time.sleep(5)\n", - "%px time.time()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But you will notice that this didn't output the result of the last command.\n", - "For this, we have `%pxresult`, which displays the output of the latest request:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxresult" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Remember, an IPython engine is IPython, so you can do magics remotely as well!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxconfig --block\n", - "%px %matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`%%px` can also be used as a cell magic, for submitting whole blocks.\n", - "This one acceps `--block` and `--noblock` flags to specify\n", - "the blocking behavior, though the default is unchanged.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "dv.scatter('id', dv.targets, flatten=True)\n", - "dv['stride'] = len(dv)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px --noblock\n", - "x = np.linspace(0,np.pi,1000)\n", - "for n in range(id,12, stride):\n", - " print(n)\n", - " plt.plot(x,np.sin(n*x))\n", - "plt.title(\"Plot %i\" % id)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxresult" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It also lets you choose some amount of the grouping of the outputs with `--group-outputs`:\n", - "\n", - "The choices are:\n", - "\n", - "* `engine` - all of an engine's output is collected together\n", - "* `type` - where stdout of each engine is grouped, etc. (the default)\n", - "* `order` - same as `type`, but individual displaypub outputs are interleaved.\n", - " That is, it will output the first plot from each engine, then the second from each,\n", - " etc." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px --group-outputs=engine\n", - "x = np.linspace(0,np.pi,1000)\n", - "for n in range(id+1,12, stride):\n", - " print(n)\n", - " plt.figure()\n", - " plt.plot(x,np.sin(n*x))\n", - " plt.title(\"Plot %i\" % n)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When you specify 'order', then individual display outputs (e.g. plots) will be interleaved.\n", - "\n", - "`%pxresult` takes the same output-ordering arguments as `%%px`, \n", - "so you can view the previous result in a variety of different ways with a few sequential calls to `%pxresult`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxresult --group-outputs=order" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Single-engine views" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When a DirectView has a single target, the output is a bit simpler (no prefixes on stdout/err, etc.):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from __future__ import print_function\n", - "\n", - "def generate_output():\n", - " \"\"\"function for testing output\n", - " \n", - " publishes two outputs of each type, and returns something\n", - " \"\"\"\n", - " \n", - " import sys,os\n", - " from IPython.display import display, HTML, Math\n", - " \n", - " print(\"stdout\")\n", - " print(\"stderr\", file=sys.stderr)\n", - " \n", - " display(HTML(\"HTML\"))\n", - " \n", - " print(\"stdout2\")\n", - " print(\"stderr2\", file=sys.stderr)\n", - " \n", - " display(Math(r\"\\alpha=\\beta\"))\n", - " \n", - " return os.getpid()\n", - "\n", - "dv['generate_output'] = generate_output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also have more than one set of parallel magics registered at a time.\n", - "\n", - "The `View.activate()` method takes a suffix argument, which is added to `'px'`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "e0 = rc[-1]\n", - "e0.block = True\n", - "e0.activate('0')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px0 generate_output()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%px generate_output()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As mentioned above, we can redisplay those same results with various grouping:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxresult --group-outputs order" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%pxresult --group-outputs engine" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Parallel Exceptions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When you raise exceptions with the parallel exception,\n", - "the CompositeError raised locally will display your remote traceback." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px\n", - "from numpy.random import random\n", - "A = random((100,100,'invalid shape'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Remote Cell Magics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Remember, Engines are IPython too, so the cell that is run remotely by %%px can in turn use a cell magic." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px\n", - "%%timeit\n", - "from numpy.random import random\n", - "from numpy.linalg import norm\n", - "A = random((100,100))\n", - "norm(A, 2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Local Execution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As of IPython 1.0, you can instruct `%%px` to also execute the cell locally.\n", - "This is useful for interactive definitions,\n", - "or if you want to load a data source everywhere,\n", - "not just on the engines." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%%px --local\n", - "import os\n", - "thispid = os.getpid()\n", - "print(thispid)" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Using Dill.ipynb b/examples/Parallel Computing/Using Dill.ipynb deleted file mode 100644 index a63554b..0000000 --- a/examples/Parallel Computing/Using Dill.ipynb +++ /dev/null @@ -1,559 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using dill to pickle anything" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython.parallel doesn't do much in the way of serialization.\n", - "It has custom zero-copy handling of numpy arrays,\n", - "but other than that, it doesn't do anything other than the bare minimum to make basic interactively defined functions and classes sendable.\n", - "\n", - "There are a few projects that extend pickle to make just about anything sendable, and one of these is [dill](http://www.cacr.caltech.edu/~mmckerns/dill).\n", - "\n", - "To install dill:\n", - " \n", - " pip install --pre dill" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, as always, we create a task function, this time with a closure" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def make_closure(a):\n", - " \"\"\"make a function with a closure, and return it\"\"\"\n", - " def has_closure(b):\n", - " return a * b\n", - " return has_closure" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "closed = make_closure(5)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "10" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "closed(2)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import pickle" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Without help, pickle can't deal with closures" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "ename": "PicklingError", - "evalue": "Can't pickle : it's not found as __main__.has_closure", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mPicklingError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclosed\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36mdumps\u001b[1;34m(obj, protocol)\u001b[0m\n\u001b[0;32m 1372\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mprotocol\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1373\u001b[0m \u001b[0mfile\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mStringIO\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1374\u001b[1;33m \u001b[0mPickler\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfile\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mprotocol\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdump\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1375\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mfile\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mgetvalue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1376\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36mdump\u001b[1;34m(self, obj)\u001b[0m\n\u001b[0;32m 222\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mproto\u001b[0m \u001b[1;33m>=\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 223\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mPROTO\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mchr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mproto\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 224\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msave\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 225\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mSTOP\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 226\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36msave\u001b[1;34m(self, obj)\u001b[0m\n\u001b[0;32m 284\u001b[0m \u001b[0mf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdispatch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 285\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 286\u001b[1;33m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# Call unbound method with explicit self\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 287\u001b[0m \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 288\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36msave_global\u001b[1;34m(self, obj, name, pack)\u001b[0m\n\u001b[0;32m 746\u001b[0m raise PicklingError(\n\u001b[0;32m 747\u001b[0m \u001b[1;34m\"Can't pickle %r: it's not found as %s.%s\"\u001b[0m \u001b[1;33m%\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 748\u001b[1;33m (obj, module, name))\n\u001b[0m\u001b[0;32m 749\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 750\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mklass\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mobj\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mPicklingError\u001b[0m: Can't pickle : it's not found as __main__.has_closure" - ] - } - ], - "source": [ - "pickle.dumps(closed)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But after we import dill, magic happens" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import dill" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\"cdill.dill\\n_load_type\\np0\\n(S'FunctionType'\\np1\\ntp2\\nRp3\\n(cdill.dill...\"" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pickle.dumps(closed)[:64] + '...'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So from now on, pretty much everything is pickleable." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Now use this in IPython.parallel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As usual, we start by creating our Client and View" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from IPython import parallel\n", - "rc = parallel.Client()\n", - "view = rc.load_balanced_view()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's try sending our function with a closure:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "ename": "ValueError", - "evalue": "Sorry, cannot pickle code objects with closures", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mview\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapply_sync\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclosed\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36mapply_sync\u001b[1;34m(self, f, *args, **kwargs)\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36mspin_after\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 73\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mspin_after\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 74\u001b[0m \u001b[1;34m\"\"\"call spin after the method.\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 75\u001b[1;33m \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 76\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 77\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mret\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36mapply_sync\u001b[1;34m(self, f, *args, **kwargs)\u001b[0m\n\u001b[0;32m 245\u001b[0m \u001b[0mreturning\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m.\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 246\u001b[0m \"\"\"\n\u001b[1;32m--> 247\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_really_apply\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mblock\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 248\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 249\u001b[0m \u001b[1;31m#----------------------------------------------------------------\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36msync_results\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 64\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_in_sync_results\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 65\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 66\u001b[1;33m \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 67\u001b[0m \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 68\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_in_sync_results\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mFalse\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36msave_ids\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 49\u001b[0m \u001b[0mn_previous\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhistory\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 50\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 51\u001b[1;33m \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 52\u001b[0m \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 53\u001b[0m \u001b[0mnmsgs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhistory\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mn_previous\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n\u001b[0;32m 1049\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1050\u001b[0m msg = self.client.send_apply_request(self._socket, f, args, kwargs, track=track,\n\u001b[1;32m-> 1051\u001b[1;33m metadata=metadata)\n\u001b[0m\u001b[0;32m 1052\u001b[0m \u001b[0mtracker\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mNone\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mtrack\u001b[0m \u001b[1;32mis\u001b[0m \u001b[0mFalse\u001b[0m \u001b[1;32melse\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'tracker'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1053\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/client.pyc\u001b[0m in \u001b[0;36msend_apply_request\u001b[1;34m(self, socket, f, args, kwargs, metadata, track, ident)\u001b[0m\n\u001b[0;32m 1252\u001b[0m bufs = serialize.pack_apply_message(f, args, kwargs,\n\u001b[0;32m 1253\u001b[0m \u001b[0mbuffer_threshold\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbuffer_threshold\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1254\u001b[1;33m \u001b[0mitem_threshold\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mitem_threshold\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1255\u001b[0m )\n\u001b[0;32m 1256\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/kernel/zmq/serialize.pyc\u001b[0m in \u001b[0;36mpack_apply_message\u001b[1;34m(f, args, kwargs, buffer_threshold, item_threshold)\u001b[0m\n\u001b[0;32m 163\u001b[0m \u001b[0minfo\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnarg_bufs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marg_bufs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkw_keys\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mkw_keys\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 164\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 165\u001b[1;33m \u001b[0mmsg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcan\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 166\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 167\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marg_bufs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/utils/codeutil.pyc\u001b[0m in \u001b[0;36mreduce_code\u001b[1;34m(co)\u001b[0m\n\u001b[0;32m 36\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mreduce_code\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mco\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 37\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_freevars\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_cellvars\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 38\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Sorry, cannot pickle code objects with closures\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 39\u001b[0m args = [co.co_argcount, co.co_nlocals, co.co_stacksize,\n\u001b[0;32m 40\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_flags\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_code\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_consts\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_names\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mValueError\u001b[0m: Sorry, cannot pickle code objects with closures" - ] - } - ], - "source": [ - "view.apply_sync(closed, 3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Oops, no dice. For IPython to work with dill,\n", - "there are one or two more steps. IPython will do these for you if you call `DirectView.use_dill`:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rc[:].use_dill()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is equivalent to\n", - "\n", - "```python\n", - "from IPython.utils.pickleutil import use_dill\n", - "use_dill()\n", - "rc[:].apply(use_dill)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's try again" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "15" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "view.apply_sync(closed, 3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Yay! Now we can use dill to allow IPython.parallel to send anything.\n", - "\n", - "And that's it! We can send closures and other previously non-pickleables to our engines.\n", - "\n", - "Let's give it a try now:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "20" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "remote_closure = view.apply_sync(make_closure, 4)\n", - "remote_closure(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But wait, there's more!\n", - "\n", - "At this point, we can send/recv all kinds of stuff" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def outer(a):\n", - " def inner(b):\n", - " def inner_again(c):\n", - " return c * b * a\n", - " return inner_again\n", - " return inner" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So outer returns a function with a closure, which returns a function with a closure.\n", - "\n", - "Now, we can resolve the first closure on the engine, the second here, and the third on a different engine,\n", - "after passing through a lambda we define here and call there, just for good measure." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "6" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "view.apply_sync(lambda f: f(3),view.apply_sync(outer, 1)(2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And for good measure, let's test that normal execution still works:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[5, 5, 5, 5, 5, 5, 5, 5]\n" - ] - }, - { - "data": { - "text/plain": [ - "[10, 10, 10, 10, 10, 10, 10, 10]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%px foo = 5\n", - "\n", - "print(rc[:]['foo'])\n", - "rc[:]['bar'] = lambda : 2 * foo\n", - "rc[:].apply_sync(parallel.Reference('bar'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And test that the `@interactive` decorator works" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting testdill.py\n" - ] - } - ], - "source": [ - "%%file testdill.py\n", - "from IPython.parallel import interactive\n", - "\n", - "@interactive\n", - "class C(object):\n", - " a = 5\n", - "\n", - "@interactive\n", - "class D(C):\n", - " b = 10\n", - "\n", - "@interactive\n", - "def foo(a):\n", - " return a * b\n" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import testdill" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5 10\n" - ] - } - ], - "source": [ - "v = rc[-1]\n", - "v['D'] = testdill.D\n", - "d = v.apply_sync(lambda : D())\n", - "print d.a, d.b" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "50" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "v['b'] = 10\n", - "v.apply_sync(testdill.foo, 5)" - ] - } - ], - "metadata": { - "gist_id": "5241793", - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/Using MPI with IPython Parallel.ipynb b/examples/Parallel Computing/Using MPI with IPython Parallel.ipynb deleted file mode 100644 index 2188831..0000000 --- a/examples/Parallel Computing/Using MPI with IPython Parallel.ipynb +++ /dev/null @@ -1,206 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Simple usage of a set of MPI engines" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example assumes you've started a cluster of N engines (4 in this example) as part\n", - "of an MPI world. \n", - "\n", - "Our documentation describes [how to create an MPI profile](http://ipython.org/ipython-doc/dev/parallel/parallel_process.html#using-ipcluster-in-mpiexec-mpirun-mode)\n", - "and explains [basic MPI usage of the IPython cluster](http://ipython.org/ipython-doc/dev/parallel/parallel_mpi.html).\n", - "\n", - "\n", - "For the simplest possible way to start 4 engines that belong to the same MPI world, \n", - "you can run this in a terminal:\n", - "\n", - "
\n",
-    "ipcluster start --engines=MPI -n 4\n",
-    "
\n", - "\n", - "or start an MPI cluster from the cluster tab if you have one configured.\n", - "\n", - "Once the cluster is running, we can connect to it and open a view into it:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from IPython.parallel import Client\n", - "c = Client()\n", - "view = c[:]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's define a simple function that gets the MPI rank from each engine." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "@view.remote(block=True)\n", - "def mpi_rank():\n", - " from mpi4py import MPI\n", - " comm = MPI.COMM_WORLD\n", - " return comm.Get_rank()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[2, 3, 1, 0]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mpi_rank()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To get a mapping of IPython IDs and MPI rank (these do not always match),\n", - "you can use the get_dict method on AsyncResults." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{0: 2, 1: 3, 2: 1, 3: 0}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mpi_rank.block = False\n", - "ar = mpi_rank()\n", - "ar.get_dict()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With %%px cell magic, the next cell will actually execute *entirely on each engine*:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "%%px\n", - "from mpi4py import MPI\n", - "\n", - "comm = MPI.COMM_WORLD\n", - "size = comm.Get_size()\n", - "rank = comm.Get_rank()\n", - "\n", - "if rank == 0:\n", - " data = [(i+1)**2 for i in range(size)]\n", - "else:\n", - " data = None\n", - "data = comm.scatter(data, root=0)\n", - "\n", - "assert data == (rank+1)**2, 'data=%s, rank=%s' % (data, rank)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[9, 16, 4, 1]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "view['data']" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/customresults.py b/examples/Parallel Computing/customresults.py deleted file mode 100644 index 8fb3482..0000000 --- a/examples/Parallel Computing/customresults.py +++ /dev/null @@ -1,61 +0,0 @@ -"""An example for handling results in a way that AsyncMapResult doesn't provide - -Specifically, out-of-order results with some special handing of metadata. - -This just submits a bunch of jobs, waits on the results, and prints the stdout -and results of each as they finish. - -Authors -------- -* MinRK -""" -import time -import random - -from IPython import parallel - -# create client & views -rc = parallel.Client() -dv = rc[:] -v = rc.load_balanced_view() - - -# scatter 'id', so id=0,1,2 on engines 0,1,2 -dv.scatter('id', rc.ids, flatten=True) -print(dv['id']) - - -def sleep_here(count, t): - """simple function that takes args, prints a short message, sleeps for a time, and returns the same args""" - import time,sys - print("hi from engine %i" % id) - sys.stdout.flush() - time.sleep(t) - return count,t - -amr = v.map(sleep_here, range(100), [ random.random() for i in range(100) ], chunksize=2) - -pending = set(amr.msg_ids) -while pending: - try: - rc.wait(pending, 1e-3) - except parallel.TimeoutError: - # ignore timeouterrors, since they only mean that at least one isn't done - pass - # finished is the set of msg_ids that are complete - finished = pending.difference(rc.outstanding) - # update pending to exclude those that just finished - pending = pending.difference(finished) - for msg_id in finished: - # we know these are done, so don't worry about blocking - ar = rc.get_result(msg_id) - print("job id %s finished on engine %i" % (msg_id, ar.engine_id)) - print("with stdout:") - print(' ' + ar.stdout.replace('\n', '\n ').rstrip()) - print("and results:") - - # note that each job in a map always returns a list of length chunksize - # even if chunksize == 1 - for (count,t) in ar.result: - print(" item %i: slept for %.2fs" % (count, t)) - diff --git a/examples/Parallel Computing/daVinci Word Count/pwordfreq.py b/examples/Parallel Computing/daVinci Word Count/pwordfreq.py deleted file mode 100644 index 833b545..0000000 --- a/examples/Parallel Computing/daVinci Word Count/pwordfreq.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python -"""Parallel word frequency counter. - -This only works for a local cluster, because the filenames are local paths. -""" -from __future__ import division - - -import os -import time -import urllib - -from itertools import repeat - -from wordfreq import print_wordfreq, wordfreq - -from IPython.parallel import Client, Reference - -try: #python2 - from urllib import urlretrieve -except ImportError: #python3 - from urllib.request import urlretrieve - -davinci_url = "http://www.gutenberg.org/cache/epub/5000/pg5000.txt" - -def pwordfreq(view, fnames): - """Parallel word frequency counter. - - view - An IPython DirectView - fnames - The filenames containing the split data. - """ - assert len(fnames) == len(view.targets) - view.scatter('fname', fnames, flatten=True) - ar = view.apply(wordfreq, Reference('fname')) - freqs_list = ar.get() - word_set = set() - for f in freqs_list: - word_set.update(f.keys()) - freqs = dict(zip(word_set, repeat(0))) - for f in freqs_list: - for word, count in f.items(): - freqs[word] += count - return freqs - -if __name__ == '__main__': - # Create a Client and View - rc = Client() - - view = rc[:] - - if not os.path.exists('davinci.txt'): - # download from project gutenberg - print("Downloading Da Vinci's notebooks from Project Gutenberg") - urlretrieve(davinci_url, 'davinci.txt') - - # Run the serial version - print("Serial word frequency count:") - text = open('davinci.txt').read() - tic = time.time() - freqs = wordfreq(text) - toc = time.time() - print_wordfreq(freqs, 10) - print("Took %.3f s to calculate"%(toc-tic)) - - - # The parallel version - print("\nParallel word frequency count:") - # split the davinci.txt into one file per engine: - lines = text.splitlines() - nlines = len(lines) - n = len(rc) - block = nlines//n - for i in range(n): - chunk = lines[i*block:i*(block+1)] - with open('davinci%i.txt'%i, 'w') as f: - f.write('\n'.join(chunk)) - - try: #python2 - cwd = os.path.abspath(os.getcwdu()) - except AttributeError: #python3 - cwd = os.path.abspath(os.getcwd()) - fnames = [ os.path.join(cwd, 'davinci%i.txt'%i) for i in range(n)] - tic = time.time() - pfreqs = pwordfreq(view,fnames) - toc = time.time() - print_wordfreq(freqs) - print("Took %.3f s to calculate on %i engines"%(toc-tic, len(view.targets))) - # cleanup split files - map(os.remove, fnames) diff --git a/examples/Parallel Computing/daVinci Word Count/wordfreq.py b/examples/Parallel Computing/daVinci Word Count/wordfreq.py deleted file mode 100644 index 361e1bc..0000000 --- a/examples/Parallel Computing/daVinci Word Count/wordfreq.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Count the frequencies of words in a string""" - -from __future__ import division -from __future__ import print_function - -import cmath as math - - -def wordfreq(text, is_filename=False): - """Return a dictionary of words and word counts in a string.""" - if is_filename: - with open(text) as f: - text = f.read() - freqs = {} - for word in text.split(): - lword = word.lower() - freqs[lword] = freqs.get(lword, 0) + 1 - return freqs - - -def print_wordfreq(freqs, n=10): - """Print the n most common words and counts in the freqs dict.""" - - words, counts = freqs.keys(), freqs.values() - items = zip(counts, words) - items.sort(reverse=True) - for (count, word) in items[:n]: - print(word, count) - - -def wordfreq_to_weightsize(worddict, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0): - mincount = min(worddict.itervalues()) - maxcount = max(worddict.itervalues()) - weights = {} - for k, v in worddict.iteritems(): - w = (v-mincount)/(maxcount-mincount) - alpha = minalpha + (maxalpha-minalpha)*w - size = minsize + (maxsize-minsize)*w - weights[k] = (alpha, size) - return weights - - -def tagcloud(worddict, n=10, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0): - from matplotlib import pyplot as plt - import random - - worddict = wordfreq_to_weightsize(worddict, minsize, maxsize, minalpha, maxalpha) - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_position([0.0,0.0,1.0,1.0]) - plt.xticks([]) - plt.yticks([]) - - words = worddict.keys() - alphas = [v[0] for v in worddict.values()] - sizes = [v[1] for v in worddict.values()] - items = zip(alphas, sizes, words) - items.sort(reverse=True) - for alpha, size, word in items[:n]: - # xpos = random.normalvariate(0.5, 0.3) - # ypos = random.normalvariate(0.5, 0.3) - xpos = random.uniform(0.0,1.0) - ypos = random.uniform(0.0,1.0) - ax.text(xpos, ypos, word.lower(), alpha=alpha, fontsize=size) - ax.autoscale_view() - return ax - - diff --git a/examples/Parallel Computing/dagdeps.py b/examples/Parallel Computing/dagdeps.py deleted file mode 100644 index bdd1d5e..0000000 --- a/examples/Parallel Computing/dagdeps.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Example for generating an arbitrary DAG as a dependency map. - -This demo uses networkx to generate the graph. - -Authors -------- -* MinRK -""" -import networkx as nx -from random import randint, random -from IPython import parallel - -def randomwait(): - import time - from random import random - time.sleep(random()) - return time.time() - - -def random_dag(nodes, edges): - """Generate a random Directed Acyclic Graph (DAG) with a given number of nodes and edges.""" - G = nx.DiGraph() - for i in range(nodes): - G.add_node(i) - while edges > 0: - a = randint(0,nodes-1) - b=a - while b==a: - b = randint(0,nodes-1) - G.add_edge(a,b) - if nx.is_directed_acyclic_graph(G): - edges -= 1 - else: - # we closed a loop! - G.remove_edge(a,b) - return G - -def add_children(G, parent, level, n=2): - """Add children recursively to a binary tree.""" - if level == 0: - return - for i in range(n): - child = parent+str(i) - G.add_node(child) - G.add_edge(parent,child) - add_children(G, child, level-1, n) - -def make_bintree(levels): - """Make a symmetrical binary tree with @levels""" - G = nx.DiGraph() - root = '0' - G.add_node(root) - add_children(G, root, levels, 2) - return G - -def submit_jobs(view, G, jobs): - """Submit jobs via client where G describes the time dependencies.""" - results = {} - for node in nx.topological_sort(G): - with view.temp_flags(after=[ results[n] for n in G.predecessors(node) ]): - results[node] = view.apply(jobs[node]) - return results - -def validate_tree(G, results): - """Validate that jobs executed after their dependencies.""" - for node in G: - started = results[node].metadata.started - for parent in G.predecessors(node): - finished = results[parent].metadata.completed - assert started > finished, "%s should have happened after %s"%(node, parent) - -def main(nodes, edges): - """Generate a random graph, submit jobs, then validate that the - dependency order was enforced. - Finally, plot the graph, with time on the x-axis, and - in-degree on the y (just for spread). All arrows must - point at least slightly to the right if the graph is valid. - """ - from matplotlib import pyplot as plt - from matplotlib.dates import date2num - from matplotlib.cm import gist_rainbow - print("building DAG") - G = random_dag(nodes, edges) - jobs = {} - pos = {} - colors = {} - for node in G: - jobs[node] = randomwait - - client = parallel.Client() - view = client.load_balanced_view() - print("submitting %i tasks with %i dependencies"%(nodes,edges)) - results = submit_jobs(view, G, jobs) - print("waiting for results") - view.wait() - print("done") - for node in G: - md = results[node].metadata - start = date2num(md.started) - runtime = date2num(md.completed) - start - pos[node] = (start, runtime) - colors[node] = md.engine_id - validate_tree(G, results) - nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(), cmap=gist_rainbow, - with_labels=False) - x,y = zip(*pos.values()) - xmin,ymin = map(min, (x,y)) - xmax,ymax = map(max, (x,y)) - xscale = xmax-xmin - yscale = ymax-ymin - plt.xlim(xmin-xscale*.1,xmax+xscale*.1) - plt.ylim(ymin-yscale*.1,ymax+yscale*.1) - return G,results - -if __name__ == '__main__': - from matplotlib import pyplot as plt - # main(5,10) - main(32,96) - plt.show() - diff --git a/examples/Parallel Computing/dependencies.py b/examples/Parallel Computing/dependencies.py deleted file mode 100644 index fdb2bd2..0000000 --- a/examples/Parallel Computing/dependencies.py +++ /dev/null @@ -1,128 +0,0 @@ -from IPython.parallel import * - -client = Client() - -# this will only run on machines that can import numpy: -@require('numpy') -def norm(A): - from numpy.linalg import norm - return norm(A,2) - -def checkpid(pid): - """return the pid of the engine""" - import os - return os.getpid() == pid - -def checkhostname(host): - import socket - return socket.gethostname() == host - -def getpid(): - import os - return os.getpid() - -pid0 = client[0].apply_sync(getpid) - -# this will depend on the pid being that of target 0: -@depend(checkpid, pid0) -def getpid2(): - import os - return os.getpid() - -view = client.load_balanced_view() -view.block=True - -# will run on anything: -pids1 = [ view.apply(getpid) for i in range(len(client.ids)) ] -print(pids1) -# will only run on e0: -pids2 = [ view.apply(getpid2) for i in range(len(client.ids)) ] -print(pids2) - -print("now test some dependency behaviors") - -def wait(t): - import time - time.sleep(t) - return t - -# fail after some time: -def wait_and_fail(t): - import time - time.sleep(t) - return 1/0 - -successes = [ view.apply_async(wait, 1).msg_ids[0] for i in range(len(client.ids)) ] -failures = [ view.apply_async(wait_and_fail, 1).msg_ids[0] for i in range(len(client.ids)) ] - -mixed = [failures[0],successes[0]] -d1a = Dependency(mixed, all=False, failure=True) # yes -d1b = Dependency(mixed, all=False) # yes -d2a = Dependency(mixed, all=True, failure=True) # yes after / no follow -d2b = Dependency(mixed, all=True) # no -d3 = Dependency(failures, all=False) # no -d4 = Dependency(failures, all=False, failure=True) # yes -d5 = Dependency(failures, all=True, failure=True) # yes after / no follow -d6 = Dependency(successes, all=True, failure=True) # yes after / no follow - -view.block = False -flags = view.temp_flags -with flags(after=d1a): - r1a = view.apply(getpid) -with flags(follow=d1b): - r1b = view.apply(getpid) -with flags(after=d2b, follow=d2a): - r2a = view.apply(getpid) -with flags(after=d2a, follow=d2b): - r2b = view.apply(getpid) -with flags(after=d3): - r3 = view.apply(getpid) -with flags(after=d4): - r4a = view.apply(getpid) -with flags(follow=d4): - r4b = view.apply(getpid) -with flags(after=d3, follow=d4): - r4c = view.apply(getpid) -with flags(after=d5): - r5 = view.apply(getpid) -with flags(follow=d5, after=d3): - r5b = view.apply(getpid) -with flags(follow=d6): - r6 = view.apply(getpid) -with flags(after=d6, follow=d2b): - r6b = view.apply(getpid) - -def should_fail(f): - try: - f() - except error.KernelError: - pass - else: - print('should have raised') - # raise Exception("should have raised") - -# print(r1a.msg_ids) -r1a.get() -# print(r1b.msg_ids) -r1b.get() -# print(r2a.msg_ids) -should_fail(r2a.get) -# print(r2b.msg_ids) -should_fail(r2b.get) -# print(r3.msg_ids) -should_fail(r3.get) -# print(r4a.msg_ids) -r4a.get() -# print(r4b.msg_ids) -r4b.get() -# print(r4c.msg_ids) -should_fail(r4c.get) -# print(r5.msg_ids) -r5.get() -# print(r5b.msg_ids) -should_fail(r5b.get) -# print(r6.msg_ids) -should_fail(r6.get) # assuming > 1 engine -# print(r6b.msg_ids) -should_fail(r6b.get) -print('done') diff --git a/examples/Parallel Computing/fetchparse.py b/examples/Parallel Computing/fetchparse.py deleted file mode 100644 index f88890f..0000000 --- a/examples/Parallel Computing/fetchparse.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -An exceptionally lousy site spider -Ken Kinder - -Updated for newparallel by Min Ragan-Kelley - -This module gives an example of how the task interface to the -IPython controller works. Before running this script start the IPython controller -and some engines using something like:: - - ipcluster start -n 4 -""" -from __future__ import print_function - -import sys -from IPython.parallel import Client, error -import time -import BeautifulSoup # this isn't necessary, but it helps throw the dependency error earlier - -def fetchAndParse(url, data=None): - import urllib2 - import urlparse - import BeautifulSoup - links = [] - try: - page = urllib2.urlopen(url, data=data) - except Exception: - return links - else: - if page.headers.type == 'text/html': - doc = BeautifulSoup.BeautifulSoup(page.read()) - for node in doc.findAll('a'): - href = node.get('href', None) - if href: - links.append(urlparse.urljoin(url, href)) - return links - -class DistributedSpider(object): - - # Time to wait between polling for task results. - pollingDelay = 0.5 - - def __init__(self, site): - self.client = Client() - self.view = self.client.load_balanced_view() - self.mux = self.client[:] - - self.allLinks = [] - self.linksWorking = {} - self.linksDone = {} - - self.site = site - - def visitLink(self, url): - if url not in self.allLinks: - self.allLinks.append(url) - if url.startswith(self.site): - print(' ', url) - self.linksWorking[url] = self.view.apply(fetchAndParse, url) - - def onVisitDone(self, links, url): - print(url, ':') - self.linksDone[url] = None - del self.linksWorking[url] - for link in links: - self.visitLink(link) - - def run(self): - self.visitLink(self.site) - while self.linksWorking: - print(len(self.linksWorking), 'pending...') - self.synchronize() - time.sleep(self.pollingDelay) - - def synchronize(self): - for url, ar in self.linksWorking.items(): - # Calling get_task_result with block=False will return None if the - # task is not done yet. This provides a simple way of polling. - try: - links = ar.get(0) - except error.TimeoutError: - continue - except Exception as e: - self.linksDone[url] = None - del self.linksWorking[url] - print(url, ':', e.traceback) - else: - self.onVisitDone(links, url) - -def main(): - if len(sys.argv) > 1: - site = sys.argv[1] - else: - site = raw_input('Enter site to crawl: ') - distributedSpider = DistributedSpider(site) - distributedSpider.run() - -if __name__ == '__main__': - main() diff --git a/examples/Parallel Computing/interengine/bintree.py b/examples/Parallel Computing/interengine/bintree.py deleted file mode 100644 index 66e1236..0000000 --- a/examples/Parallel Computing/interengine/bintree.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -BinaryTree inter-engine communication class - -use from bintree_script.py - -Provides parallel [all]reduce functionality - -""" -from __future__ import print_function - -import cPickle as pickle -import re -import socket -import uuid - -import zmq - -from IPython.parallel.util import disambiguate_url - - -#---------------------------------------------------------------------------- -# bintree-related construction/printing helpers -#---------------------------------------------------------------------------- - -def bintree(ids, parent=None): - """construct {child:parent} dict representation of a binary tree - - keys are the nodes in the tree, and values are the parent of each node. - - The root node has parent `parent`, default: None. - - >>> tree = bintree(range(7)) - >>> tree - {0: None, 1: 0, 2: 1, 3: 1, 4: 0, 5: 4, 6: 4} - >>> print_bintree(tree) - 0 - 1 - 2 - 3 - 4 - 5 - 6 - """ - parents = {} - n = len(ids) - if n == 0: - return parents - root = ids[0] - parents[root] = parent - if len(ids) == 1: - return parents - else: - ids = ids[1:] - n = len(ids) - left = bintree(ids[:n/2], parent=root) - right = bintree(ids[n/2:], parent=root) - parents.update(left) - parents.update(right) - return parents - -def reverse_bintree(parents): - """construct {parent:[children]} dict from {child:parent} - - keys are the nodes in the tree, and values are the lists of children - of that node in the tree. - - reverse_tree[None] is the root node - - >>> tree = bintree(range(7)) - >>> reverse_bintree(tree) - {None: 0, 0: [1, 4], 4: [5, 6], 1: [2, 3]} - """ - children = {} - for child,parent in parents.iteritems(): - if parent is None: - children[None] = child - continue - elif parent not in children: - children[parent] = [] - children[parent].append(child) - - return children - -def depth(n, tree): - """get depth of an element in the tree""" - d = 0 - parent = tree[n] - while parent is not None: - d += 1 - parent = tree[parent] - return d - -def print_bintree(tree, indent=' '): - """print a binary tree""" - for n in sorted(tree.keys()): - print("%s%s" % (indent * depth(n,tree), n)) - -#---------------------------------------------------------------------------- -# Communicator class for a binary-tree map -#---------------------------------------------------------------------------- - -ip_pat = re.compile(r'^\d+\.\d+\.\d+\.\d+$') - -def disambiguate_dns_url(url, location): - """accept either IP address or dns name, and return IP""" - if not ip_pat.match(location): - location = socket.gethostbyname(location) - return disambiguate_url(url, location) - -class BinaryTreeCommunicator(object): - - id = None - pub = None - sub = None - downstream = None - upstream = None - pub_url = None - tree_url = None - - def __init__(self, id, interface='tcp://*', root=False): - self.id = id - self.root = root - - # create context and sockets - self._ctx = zmq.Context() - if root: - self.pub = self._ctx.socket(zmq.PUB) - else: - self.sub = self._ctx.socket(zmq.SUB) - self.sub.setsockopt(zmq.SUBSCRIBE, b'') - self.downstream = self._ctx.socket(zmq.PULL) - self.upstream = self._ctx.socket(zmq.PUSH) - - # bind to ports - interface_f = interface + ":%i" - if self.root: - pub_port = self.pub.bind_to_random_port(interface) - self.pub_url = interface_f % pub_port - - tree_port = self.downstream.bind_to_random_port(interface) - self.tree_url = interface_f % tree_port - self.downstream_poller = zmq.Poller() - self.downstream_poller.register(self.downstream, zmq.POLLIN) - - # guess first public IP from socket - self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0] - - def __del__(self): - self.downstream.close() - self.upstream.close() - if self.root: - self.pub.close() - else: - self.sub.close() - self._ctx.term() - - @property - def info(self): - """return the connection info for this object's sockets.""" - return (self.tree_url, self.location) - - def connect(self, peers, btree, pub_url, root_id=0): - """connect to peers. `peers` will be a dict of 4-tuples, keyed by name. - {peer : (ident, addr, pub_addr, location)} - where peer is the name, ident is the XREP identity, addr,pub_addr are the - """ - - # count the number of children we have - self.nchildren = btree.values().count(self.id) - - if self.root: - return # root only binds - - root_location = peers[root_id][-1] - self.sub.connect(disambiguate_dns_url(pub_url, root_location)) - - parent = btree[self.id] - - tree_url, location = peers[parent] - self.upstream.connect(disambiguate_dns_url(tree_url, location)) - - def serialize(self, obj): - """serialize objects. - - Must return list of sendable buffers. - - Can be extended for more efficient/noncopying serialization of numpy arrays, etc. - """ - return [pickle.dumps(obj)] - - def unserialize(self, msg): - """inverse of serialize""" - return pickle.loads(msg[0]) - - def publish(self, value): - assert self.root - self.pub.send_multipart(self.serialize(value)) - - def consume(self): - assert not self.root - return self.unserialize(self.sub.recv_multipart()) - - def send_upstream(self, value, flags=0): - assert not self.root - self.upstream.send_multipart(self.serialize(value), flags=flags|zmq.NOBLOCK) - - def recv_downstream(self, flags=0, timeout=2000.): - # wait for a message, so we won't block if there was a bug - self.downstream_poller.poll(timeout) - - msg = self.downstream.recv_multipart(zmq.NOBLOCK|flags) - return self.unserialize(msg) - - def reduce(self, f, value, flat=True, all=False): - """parallel reduce on binary tree - - if flat: - value is an entry in the sequence - else: - value is a list of entries in the sequence - - if all: - broadcast final result to all nodes - else: - only root gets final result - """ - if not flat: - value = reduce(f, value) - - for i in range(self.nchildren): - value = f(value, self.recv_downstream()) - - if not self.root: - self.send_upstream(value) - - if all: - if self.root: - self.publish(value) - else: - value = self.consume() - return value - - def allreduce(self, f, value, flat=True): - """parallel reduce followed by broadcast of the result""" - return self.reduce(f, value, flat=flat, all=True) - diff --git a/examples/Parallel Computing/interengine/bintree_script.py b/examples/Parallel Computing/interengine/bintree_script.py deleted file mode 100755 index 45dfce4..0000000 --- a/examples/Parallel Computing/interengine/bintree_script.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -""" -Script for setting up and using [all]reduce with a binary-tree engine interconnect. - -usage: `python bintree_script.py` - -This spanning tree strategy ensures that a single node node mailbox will never -receive more that 2 messages at once. This is very important to scale to large -clusters (e.g. 1000 nodes) since if you have many incoming messages of a couple -of megabytes you might saturate the network interface of a single node and -potentially its memory buffers if the messages are not consumed in a streamed -manner. - -Note that the AllReduce scheme implemented with the spanning tree strategy -impose the aggregation function to be commutative and distributive. It might -not be the case if you implement the naive gather / reduce / broadcast strategy -where you can reorder the partial data before performing the reduce. -""" -from __future__ import print_function - -from IPython.parallel import Client, Reference - - -# connect client and create views -rc = Client() -rc.block=True -ids = rc.ids - -root_id = ids[0] -root = rc[root_id] - -view = rc[:] - -# run bintree.py script defining bintree functions, etc. -exec(compile(open('bintree.py').read(), 'bintree.py', 'exec')) - -# generate binary tree of parents -btree = bintree(ids) - -print("setting up binary tree interconnect:") -print_bintree(btree) - -view.run('bintree.py') -view.scatter('id', ids, flatten=True) -view['root_id'] = root_id - -# create the Communicator objects on the engines -view.execute('com = BinaryTreeCommunicator(id, root = id==root_id )') -pub_url = root.apply_sync(lambda : com.pub_url) - -# gather the connection information into a dict -ar = view.apply_async(lambda : com.info) -peers = ar.get_dict() -# this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators - -# connect the engines to each other: -def connect(com, peers, tree, pub_url, root_id): - """this function will be called on the engines""" - com.connect(peers, tree, pub_url, root_id) - -view.apply_sync(connect, Reference('com'), peers, btree, pub_url, root_id) - -# functions that can be used for reductions -# max and min builtins can be used as well -def add(a,b): - """cumulative sum reduction""" - return a+b - -def mul(a,b): - """cumulative product reduction""" - return a*b - -view['add'] = add -view['mul'] = mul - -# scatter some data -data = list(range(1000)) -view.scatter('data', data) - -# perform cumulative sum via allreduce -view.execute("data_sum = com.allreduce(add, data, flat=False)") -print("allreduce sum of data on all engines:", view['data_sum']) - -# perform cumulative sum *without* final broadcast -# when not broadcasting with allreduce, the final result resides on the root node: -view.execute("ids_sum = com.reduce(add, id, flat=True)") -print("reduce sum of engine ids (not broadcast):", root['ids_sum']) -print("partial result on each engine:", view['ids_sum']) diff --git a/examples/Parallel Computing/interengine/communicator.py b/examples/Parallel Computing/interengine/communicator.py deleted file mode 100644 index cf17219..0000000 --- a/examples/Parallel Computing/interengine/communicator.py +++ /dev/null @@ -1,77 +0,0 @@ -import socket - -import uuid -import zmq - -from IPython.parallel.util import disambiguate_url - -class EngineCommunicator(object): - - def __init__(self, interface='tcp://*', identity=None): - self._ctx = zmq.Context() - self.socket = self._ctx.socket(zmq.XREP) - self.pub = self._ctx.socket(zmq.PUB) - self.sub = self._ctx.socket(zmq.SUB) - - # configure sockets - self.identity = identity or bytes(uuid.uuid4()) - print(self.identity) - self.socket.setsockopt(zmq.IDENTITY, self.identity) - self.sub.setsockopt(zmq.SUBSCRIBE, b'') - - # bind to ports - port = self.socket.bind_to_random_port(interface) - pub_port = self.pub.bind_to_random_port(interface) - self.url = interface+":%i"%port - self.pub_url = interface+":%i"%pub_port - # guess first public IP from socket - self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0] - self.peers = {} - - def __del__(self): - self.socket.close() - self.pub.close() - self.sub.close() - self._ctx.term() - - @property - def info(self): - """return the connection info for this object's sockets.""" - return (self.identity, self.url, self.pub_url, self.location) - - def connect(self, peers): - """connect to peers. `peers` will be a dict of 4-tuples, keyed by name. - {peer : (ident, addr, pub_addr, location)} - where peer is the name, ident is the XREP identity, addr,pub_addr are the - """ - for peer, (ident, url, pub_url, location) in peers.items(): - self.peers[peer] = ident - if ident != self.identity: - self.sub.connect(disambiguate_url(pub_url, location)) - if ident > self.identity: - # prevent duplicate xrep, by only connecting - # engines to engines with higher IDENTITY - # a doubly-connected pair will crash - self.socket.connect(disambiguate_url(url, location)) - - def send(self, peers, msg, flags=0, copy=True): - if not isinstance(peers, list): - peers = [peers] - if not isinstance(msg, list): - msg = [msg] - for p in peers: - ident = self.peers[p] - self.socket.send_multipart([ident]+msg, flags=flags, copy=copy) - - def recv(self, flags=0, copy=True): - return self.socket.recv_multipart(flags=flags, copy=copy)[1:] - - def publish(self, msg, flags=0, copy=True): - if not isinstance(msg, list): - msg = [msg] - self.pub.send_multipart(msg, copy=copy) - - def consume(self, flags=0, copy=True): - return self.sub.recv_multipart(flags=flags, copy=copy) - - diff --git a/examples/Parallel Computing/interengine/interengine.py b/examples/Parallel Computing/interengine/interengine.py deleted file mode 100644 index 865c802..0000000 --- a/examples/Parallel Computing/interengine/interengine.py +++ /dev/null @@ -1,43 +0,0 @@ -import sys - -from IPython.parallel import Client - - -rc = Client() -rc.block=True -view = rc[:] -view.run('communicator.py') -view.execute('com = EngineCommunicator()') - -# gather the connection information into a dict -ar = view.apply_async(lambda : com.info) -peers = ar.get_dict() -# this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators - -# connect the engines to each other: -view.apply_sync(lambda pdict: com.connect(pdict), peers) - -# now all the engines are connected, and we can communicate between them: - -def broadcast(client, sender, msg_name, dest_name=None, block=None): - """broadcast a message from one engine to all others.""" - dest_name = msg_name if dest_name is None else dest_name - client[sender].execute('com.publish(%s)'%msg_name, block=None) - targets = client.ids - targets.remove(sender) - return client[targets].execute('%s=com.consume()'%dest_name, block=None) - -def send(client, sender, targets, msg_name, dest_name=None, block=None): - """send a message from one to one-or-more engines.""" - dest_name = msg_name if dest_name is None else dest_name - def _send(targets, m_name): - msg = globals()[m_name] - return com.send(targets, msg) - - client[sender].apply_async(_send, targets, msg_name) - - return client[targets].execute('%s=com.recv()'%dest_name, block=None) - - - - diff --git a/examples/Parallel Computing/iopubwatcher.py b/examples/Parallel Computing/iopubwatcher.py deleted file mode 100644 index 5cbf798..0000000 --- a/examples/Parallel Computing/iopubwatcher.py +++ /dev/null @@ -1,77 +0,0 @@ -"""A script for watching all traffic on the IOPub channel (stdout/stderr/pyerr) of engines. - -This connects to the default cluster, or you can pass the path to your ipcontroller-client.json - -Try running this script, and then running a few jobs that print (and call sys.stdout.flush), -and you will see the print statements as they arrive, notably not waiting for the results -to finish. - -You can use the zeromq SUBSCRIBE mechanism to only receive information from specific engines, -and easily filter by message type. - -Authors -------- -* MinRK -""" - -import sys -import json -import zmq - -from IPython.kernel.zmq.session import Session -from IPython.utils.py3compat import str_to_bytes -from jupyter_client.connect import find_connection_file - -def main(connection_file): - """watch iopub channel, and print messages""" - - ctx = zmq.Context.instance() - - with open(connection_file) as f: - cfg = json.loads(f.read()) - - reg_url = cfg['interface'] - iopub_port = cfg['iopub'] - iopub_url = "%s:%s"%(reg_url, iopub_port) - - session = Session(key=str_to_bytes(cfg['key'])) - sub = ctx.socket(zmq.SUB) - - # This will subscribe to all messages: - sub.setsockopt(zmq.SUBSCRIBE, b'') - # replace with b'' with b'engine.1.stdout' to subscribe only to engine 1's stdout - # 0MQ subscriptions are simple 'foo*' matches, so 'engine.1.' subscribes - # to everything from engine 1, but there is no way to subscribe to - # just stdout from everyone. - # multiple calls to subscribe will add subscriptions, e.g. to subscribe to - # engine 1's stderr and engine 2's stdout: - # sub.setsockopt(zmq.SUBSCRIBE, b'engine.1.stderr') - # sub.setsockopt(zmq.SUBSCRIBE, b'engine.2.stdout') - sub.connect(iopub_url) - while True: - try: - idents,msg = session.recv(sub, mode=0) - except KeyboardInterrupt: - return - # ident always length 1 here - topic = idents[0] - if msg['msg_type'] == 'stream': - # stdout/stderr - # stream names are in msg['content']['name'], if you want to handle - # them differently - print("%s: %s" % (topic, msg['content']['text'])) - elif msg['msg_type'] == 'pyerr': - # Python traceback - c = msg['content'] - print(topic + ':') - for line in c['traceback']: - # indent lines - print(' ' + line) - -if __name__ == '__main__': - if len(sys.argv) > 1: - cf = sys.argv[1] - else: - # This gets the security file for the default profile: - cf = find_connection_file('ipcontroller-client.json') - main(cf) diff --git a/examples/Parallel Computing/itermapresult.py b/examples/Parallel Computing/itermapresult.py deleted file mode 100644 index d31a7aa..0000000 --- a/examples/Parallel Computing/itermapresult.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Example of iteration through AsyncMapResults, without waiting for all results - -When you call view.map(func, sequence), you will receive a special AsyncMapResult -object. These objects are used to reconstruct the results of the split call. -One feature AsyncResults provide is that they are iterable *immediately*, so -you can iterate through the actual results as they complete. - -This is useful if you submit a large number of tasks that may take some time, -but want to perform logic on elements in the result, or even abort subsequent -tasks in cases where you are searching for the first affirmative result. - -By default, the results will match the ordering of the submitted sequence, but -if you call `map(...ordered=False)`, then results will be provided to the iterator -on a first come first serve basis. - -Authors -------- -* MinRK -""" -from __future__ import print_function - -import time - -from IPython import parallel - -# create client & view -rc = parallel.Client() -dv = rc[:] -v = rc.load_balanced_view() - -# scatter 'id', so id=0,1,2 on engines 0,1,2 -dv.scatter('id', rc.ids, flatten=True) -print("Engine IDs: ", dv['id']) - -# create a Reference to `id`. This will be a different value on each engine -ref = parallel.Reference('id') -print("sleeping for `id` seconds on each engine") -tic = time.time() -ar = dv.apply(time.sleep, ref) -for i,r in enumerate(ar): - print("%i: %.3f"%(i, time.time()-tic)) - -def sleep_here(t): - import time - time.sleep(t) - return id,t - -# one call per task -print("running with one call per task") -amr = v.map(sleep_here, [.01*t for t in range(100)]) -tic = time.time() -for i,r in enumerate(amr): - print("task %i on engine %i: %.3f" % (i, r[0], time.time()-tic)) - -print("running with four calls per task") -# with chunksize, we can have four calls per task -amr = v.map(sleep_here, [.01*t for t in range(100)], chunksize=4) -tic = time.time() -for i,r in enumerate(amr): - print("task %i on engine %i: %.3f" % (i, r[0], time.time()-tic)) - -print("running with two calls per task, with unordered results") -# We can even iterate through faster results first, with ordered=False -amr = v.map(sleep_here, [.01*t for t in range(100,0,-1)], ordered=False, chunksize=2) -tic = time.time() -for i,r in enumerate(amr): - print("slept %.2fs on engine %i: %.3f" % (r[1], r[0], time.time()-tic)) diff --git a/examples/Parallel Computing/nwmerge.py b/examples/Parallel Computing/nwmerge.py deleted file mode 100644 index 699d9c0..0000000 --- a/examples/Parallel Computing/nwmerge.py +++ /dev/null @@ -1,124 +0,0 @@ -"""Example showing how to merge multiple remote data streams. -""" -# Slightly modified version of: -# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511509 -from __future__ import print_function - -import heapq -from IPython.parallel.error import RemoteError - -def mergesort(list_of_lists, key=None): - """ Perform an N-way merge operation on sorted lists. - - @param list_of_lists: (really iterable of iterable) of sorted elements - (either by naturally or by C{key}) - @param key: specify sort key function (like C{sort()}, C{sorted()}) - - Yields tuples of the form C{(item, iterator)}, where the iterator is the - built-in list iterator or something you pass in, if you pre-generate the - iterators. - - This is a stable merge; complexity O(N lg N) - - Examples:: - - >>> print list(mergesort([[1,2,3,4], - ... [2,3.25,3.75,4.5,6,7], - ... [2.625,3.625,6.625,9]])) - [1, 2, 2, 2.625, 3, 3.25, 3.625, 3.75, 4, 4.5, 6, 6.625, 7, 9] - - # note stability - >>> print list(mergesort([[1,2,3,4], - ... [2,3.25,3.75,4.5,6,7], - ... [2.625,3.625,6.625,9]], - ... key=int)) - [1, 2, 2, 2.625, 3, 3.25, 3.75, 3.625, 4, 4.5, 6, 6.625, 7, 9] - - - >>> print list(mergesort([[4, 3, 2, 1], - ... [7, 6, 4.5, 3.75, 3.25, 2], - ... [9, 6.625, 3.625, 2.625]], - ... key=lambda x: -x)) - [9, 7, 6.625, 6, 4.5, 4, 3.75, 3.625, 3.25, 3, 2.625, 2, 2, 1] - """ - - heap = [] - for i, itr in enumerate(iter(pl) for pl in list_of_lists): - try: - item = itr.next() - if key: - toadd = (key(item), i, item, itr) - else: - toadd = (item, i, itr) - heap.append(toadd) - except StopIteration: - pass - heapq.heapify(heap) - - if key: - while heap: - _, idx, item, itr = heap[0] - yield item - try: - item = itr.next() - heapq.heapreplace(heap, (key(item), idx, item, itr) ) - except StopIteration: - heapq.heappop(heap) - - else: - while heap: - item, idx, itr = heap[0] - yield item - try: - heapq.heapreplace(heap, (itr.next(), idx, itr)) - except StopIteration: - heapq.heappop(heap) - - -def remote_iterator(view,name): - """Return an iterator on an object living on a remote engine. - """ - view.execute('it%s=iter(%s)'%(name,name), block=True) - while True: - try: - result = view.apply_sync(lambda x: x.next(), Reference('it'+name)) - # This causes the StopIteration exception to be raised. - except RemoteError as e: - if e.ename == 'StopIteration': - raise StopIteration - else: - raise e - else: - yield result - -# Main, interactive testing -if __name__ == '__main__': - - from IPython.parallel import Client, Reference - rc = Client() - view = rc[:] - print('Engine IDs:', rc.ids) - - # Make a set of 'sorted datasets' - a0 = range(5,20) - a1 = range(10) - a2 = range(15,25) - - # Now, imagine these had been created in the remote engines by some long - # computation. In this simple example, we just send them over into the - # remote engines. They will all be called 'a' in each engine. - rc[0]['a'] = a0 - rc[1]['a'] = a1 - rc[2]['a'] = a2 - - # And we now make a local object which represents the remote iterator - aa0 = remote_iterator(rc[0],'a') - aa1 = remote_iterator(rc[1],'a') - aa2 = remote_iterator(rc[2],'a') - - # Let's merge them, both locally and remotely: - print('Merge the local datasets:') - print(list(mergesort([a0,a1,a2]))) - - print('Locally merge the remote sets:') - print(list(mergesort([aa0,aa1,aa2]))) diff --git a/examples/Parallel Computing/phistogram.py b/examples/Parallel Computing/phistogram.py deleted file mode 100644 index 4393e74..0000000 --- a/examples/Parallel Computing/phistogram.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Parallel histogram function""" -import numpy -from IPython.parallel import Reference - -def phistogram(view, a, bins=10, rng=None, normed=False): - """Compute the histogram of a remote array a. - - Parameters - ---------- - view - IPython DirectView instance - a : str - String name of the remote array - bins : int - Number of histogram bins - rng : (float, float) - Tuple of min, max of the range to histogram - normed : boolean - Should the histogram counts be normalized to 1 - """ - nengines = len(view.targets) - - # view.push(dict(bins=bins, rng=rng)) - with view.sync_imports(): - import numpy - rets = view.apply_sync(lambda a, b, rng: numpy.histogram(a,b,rng), Reference(a), bins, rng) - hists = [ r[0] for r in rets ] - lower_edges = [ r[1] for r in rets ] - # view.execute('hist, lower_edges = numpy.histogram(%s, bins, rng)' % a) - lower_edges = view.pull('lower_edges', targets=0) - hist_array = numpy.array(hists).reshape(nengines, -1) - # hist_array.shape = (nengines,-1) - total_hist = numpy.sum(hist_array, 0) - if normed: - total_hist = total_hist/numpy.sum(total_hist,dtype=float) - return total_hist, lower_edges - - - - diff --git a/examples/Parallel Computing/pi/parallelpi.py b/examples/Parallel Computing/pi/parallelpi.py deleted file mode 100644 index b85e2cb..0000000 --- a/examples/Parallel Computing/pi/parallelpi.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Calculate statistics on the digits of pi in parallel. - -This program uses the functions in :file:`pidigits.py` to calculate -the frequencies of 2 digit sequences in the digits of pi. The -results are plotted using matplotlib. - -To run, text files from http://www.super-computing.org/ -must be installed in the working directory of the IPython engines. -The actual filenames to be used can be set with the ``filestring`` -variable below. - -The dataset we have been using for this is the 200 million digit one here: -ftp://pi.super-computing.org/.2/pi200m/ - -and the files used will be downloaded if they are not in the working directory -of the IPython engines. -""" -from __future__ import print_function - -from IPython.parallel import Client -from matplotlib import pyplot as plt -import numpy as np -from pidigits import * -from timeit import default_timer as clock - -# Files with digits of pi (10m digits each) -filestring = 'pi200m.ascii.%(i)02dof20' -files = [filestring % {'i':i} for i in range(1,21)] - -# Connect to the IPython cluster -c = Client() -c[:].run('pidigits.py') - -# the number of engines -n = len(c) -id0 = c.ids[0] -v = c[:] -v.block=True -# fetch the pi-files -print("downloading %i files of pi"%n) -v.map(fetch_pi_file, files[:n]) -print("done") - -# Run 10m digits on 1 engine -t1 = clock() -freqs10m = c[id0].apply_sync(compute_two_digit_freqs, files[0]) -t2 = clock() -digits_per_second1 = 10.0e6/(t2-t1) -print("Digits per second (1 core, 10m digits): ", digits_per_second1) - - -# Run n*10m digits on all engines -t1 = clock() -freqs_all = v.map(compute_two_digit_freqs, files[:n]) -freqs150m = reduce_freqs(freqs_all) -t2 = clock() -digits_per_second8 = n*10.0e6/(t2-t1) -print("Digits per second (%i engines, %i0m digits): "%(n,n), digits_per_second8) - -print("Speedup: ", digits_per_second8/digits_per_second1) - -plot_two_digit_freqs(freqs150m) -plt.title("2 digit sequences in %i0m digits of pi"%n) -plt.show() - diff --git a/examples/Parallel Computing/pi/pidigits.py b/examples/Parallel Computing/pi/pidigits.py deleted file mode 100644 index 9e9c0e7..0000000 --- a/examples/Parallel Computing/pi/pidigits.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Compute statistics on the digits of pi. - -This uses precomputed digits of pi from the website -of Professor Yasumasa Kanada at the University of -Tokoyo: http://www.super-computing.org/ - -Currently, there are only functions to read the -.txt (non-compressed, non-binary) files, but adding -support for compression and binary files would be -straightforward. - -This focuses on computing the number of times that -all 1, 2, n digits sequences occur in the digits of pi. -If the digits of pi are truly random, these frequencies -should be equal. -""" - -# Import statements -from __future__ import division, with_statement - -import numpy as np -from matplotlib import pyplot as plt - -try : #python2 - from urllib import urlretrieve -except ImportError : #python3 - from urllib.request import urlretrieve - - # Top-level functions - -def fetch_pi_file(filename): - """This will download a segment of pi from super-computing.org - if the file is not already present. - """ - import os, urllib - ftpdir="ftp://pi.super-computing.org/.2/pi200m/" - if os.path.exists(filename): - # we already have it - return - else: - # download it - urlretrieve(ftpdir+filename,filename) - -def compute_one_digit_freqs(filename): - """ - Read digits of pi from a file and compute the 1 digit frequencies. - """ - d = txt_file_to_digits(filename) - freqs = one_digit_freqs(d) - return freqs - -def compute_two_digit_freqs(filename): - """ - Read digits of pi from a file and compute the 2 digit frequencies. - """ - d = txt_file_to_digits(filename) - freqs = two_digit_freqs(d) - return freqs - -def reduce_freqs(freqlist): - """ - Add up a list of freq counts to get the total counts. - """ - allfreqs = np.zeros_like(freqlist[0]) - for f in freqlist: - allfreqs += f - return allfreqs - -def compute_n_digit_freqs(filename, n): - """ - Read digits of pi from a file and compute the n digit frequencies. - """ - d = txt_file_to_digits(filename) - freqs = n_digit_freqs(d, n) - return freqs - -# Read digits from a txt file - -def txt_file_to_digits(filename, the_type=str): - """ - Yield the digits of pi read from a .txt file. - """ - with open(filename, 'r') as f: - for line in f.readlines(): - for c in line: - if c != '\n' and c!= ' ': - yield the_type(c) - -# Actual counting functions - -def one_digit_freqs(digits, normalize=False): - """ - Consume digits of pi and compute 1 digit freq. counts. - """ - freqs = np.zeros(10, dtype='i4') - for d in digits: - freqs[int(d)] += 1 - if normalize: - freqs = freqs/freqs.sum() - return freqs - -def two_digit_freqs(digits, normalize=False): - """ - Consume digits of pi and compute 2 digits freq. counts. - """ - freqs = np.zeros(100, dtype='i4') - last = next(digits) - this = next(digits) - for d in digits: - index = int(last + this) - freqs[index] += 1 - last = this - this = d - if normalize: - freqs = freqs/freqs.sum() - return freqs - -def n_digit_freqs(digits, n, normalize=False): - """ - Consume digits of pi and compute n digits freq. counts. - - This should only be used for 1-6 digits. - """ - freqs = np.zeros(pow(10,n), dtype='i4') - current = np.zeros(n, dtype=int) - for i in range(n): - current[i] = next(digits) - for d in digits: - index = int(''.join(map(str, current))) - freqs[index] += 1 - current[0:-1] = current[1:] - current[-1] = d - if normalize: - freqs = freqs/freqs.sum() - return freqs - -# Plotting functions - -def plot_two_digit_freqs(f2): - """ - Plot two digits frequency counts using matplotlib. - """ - f2_copy = f2.copy() - f2_copy.shape = (10,10) - ax = plt.matshow(f2_copy) - plt.colorbar() - for i in range(10): - for j in range(10): - plt.text(i-0.2, j+0.2, str(j)+str(i)) - plt.ylabel('First digit') - plt.xlabel('Second digit') - return ax - -def plot_one_digit_freqs(f1): - """ - Plot one digit frequency counts using matplotlib. - """ - ax = plt.plot(f1,'bo-') - plt.title('Single digit counts in pi') - plt.xlabel('Digit') - plt.ylabel('Count') - return ax diff --git a/examples/Parallel Computing/rmt/rmt.ipy b/examples/Parallel Computing/rmt/rmt.ipy deleted file mode 100644 index 1f4c5fe..0000000 --- a/examples/Parallel Computing/rmt/rmt.ipy +++ /dev/null @@ -1,146 +0,0 @@ -# 2 - -# - -# # Eigenvalue distribution of Gaussian orthogonal random matrices - -# - -# The eigenvalues of random matrices obey certain statistical laws. Here we construct random matrices -# from the Gaussian Orthogonal Ensemble (GOE), find their eigenvalues and then investigate the nearest -# neighbor eigenvalue distribution $\rho(s)$. - -# - -from rmtkernel import ensemble_diffs, normalize_diffs, GOE -import numpy as np -from IPython.parallel import Client - -# - -# ## Wigner's nearest neighbor eigenvalue distribution - -# - -# The Wigner distribution gives the theoretical result for the nearest neighbor eigenvalue distribution -# for the GOE: -# -# $$\rho(s) = \frac{\pi s}{2} \exp(-\pi s^2/4)$$ - -# - -def wigner_dist(s): - """Returns (s, rho(s)) for the Wigner GOE distribution.""" - return (np.pi*s/2.0) * np.exp(-np.pi*s**2/4.) - -# - -def generate_wigner_data(): - s = np.linspace(0.0,4.0,400) - rhos = wigner_dist(s) - return s, rhos - -# - -s, rhos = generate_wigner_data() - -# - -plot(s, rhos) -xlabel('Normalized level spacing s') -ylabel('Probability $\rho(s)$') - -# - -# ## Serial calculation of nearest neighbor eigenvalue distribution - -# - -# In this section we numerically construct and diagonalize a large number of GOE random matrices -# and compute the nerest neighbor eigenvalue distribution. This comptation is done on a single core. - -# - -def serial_diffs(num, N): - """Compute the nearest neighbor distribution for num NxX matrices.""" - diffs = ensemble_diffs(num, N) - normalized_diffs = normalize_diffs(diffs) - return normalized_diffs - -# - -serial_nmats = 1000 -serial_matsize = 50 - -# - -%timeit -r1 -n1 serial_diffs(serial_nmats, serial_matsize) - -# - -serial_diffs = serial_diffs(serial_nmats, serial_matsize) - -# - -# The numerical computation agrees with the predictions of Wigner, but it would be nice to get more -# statistics. For that we will do a parallel computation. - -# - -hist_data = hist(serial_diffs, bins=30, normed=True) -plot(s, rhos) -xlabel('Normalized level spacing s') -ylabel('Probability $P(s)$') - -# - -# ## Parallel calculation of nearest neighbor eigenvalue distribution - -# - -# Here we perform a parallel computation, where each process constructs and diagonalizes a subset of -# the overall set of random matrices. - -# - -def parallel_diffs(rc, num, N): - nengines = len(rc.targets) - num_per_engine = num/nengines - print "Running with", num_per_engine, "per engine." - ar = rc.apply_async(ensemble_diffs, num_per_engine, N) - diffs = np.array(ar.get()).flatten() - normalized_diffs = normalize_diffs(diffs) - return normalized_diffs - -# - -client = Client() -view = client[:] -view.run('rmtkernel.py') -view.block = False - -# - -parallel_nmats = 40*serial_nmats -parallel_matsize = 50 - -# - -%timeit -r1 -n1 parallel_diffs(view, parallel_nmats, parallel_matsize) - -# - -pdiffs = parallel_diffs(view, parallel_nmats, parallel_matsize) - -# - -# Again, the agreement with the Wigner distribution is excellent, but now we have better -# statistics. - -# - -hist_data = hist(pdiffs, bins=30, normed=True) -plot(s, rhos) -xlabel('Normalized level spacing s') -ylabel('Probability $P(s)$') - diff --git a/examples/Parallel Computing/rmt/rmt.ipynb b/examples/Parallel Computing/rmt/rmt.ipynb deleted file mode 100644 index abcd453..0000000 --- a/examples/Parallel Computing/rmt/rmt.ipynb +++ /dev/null @@ -1,821 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Eigenvalue distribution of Gaussian orthogonal random matrices" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The eigenvalues of random matrices obey certain statistical laws. Here we construct random matrices \n", - "from the Gaussian Orthogonal Ensemble (GOE), find their eigenvalues and then investigate the nearest\n", - "neighbor eigenvalue distribution $\\rho(s)$." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from rmtkernel import ensemble_diffs, normalize_diffs, GOE\n", - "import numpy as np\n", - "from IPython.parallel import Client" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Wigner's nearest neighbor eigenvalue distribution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Wigner distribution gives the theoretical result for the nearest neighbor eigenvalue distribution\n", - "for the GOE:\n", - "\n", - "$$\\rho(s) = \\frac{\\pi s}{2} \\exp(-\\pi s^2/4)$$" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def wigner_dist(s):\n", - " \"\"\"Returns (s, rho(s)) for the Wigner GOE distribution.\"\"\"\n", - " return (np.pi*s/2.0) * np.exp(-np.pi*s**2/4.)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def generate_wigner_data():\n", - " s = np.linspace(0.0,4.0,400)\n", - " rhos = wigner_dist(s)\n", - " return s, rhos" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "s, rhos = generate_wigner_data()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "<matplotlib.text.Text at 0x3828790>" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "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\n", - "SUVORK5CYII=\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot(s, rhos)\n", - "xlabel('Normalized level spacing s')\n", - "ylabel('Probability $\\rho(s)$')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Serial calculation of nearest neighbor eigenvalue distribution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this section we numerically construct and diagonalize a large number of GOE random matrices\n", - "and compute the nerest neighbor eigenvalue distribution. This comptation is done on a single core." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def serial_diffs(num, N):\n", - " \"\"\"Compute the nearest neighbor distribution for num NxX matrices.\"\"\"\n", - " diffs = ensemble_diffs(num, N)\n", - " normalized_diffs = normalize_diffs(diffs)\n", - " return normalized_diffs" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "serial_nmats = 1000\n", - "serial_matsize = 50" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1 loops, best of 1: 1.19 s per loop" - ] - } - ], - "source": [ - "%timeit -r1 -n1 serial_diffs(serial_nmats, serial_matsize)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "serial_diffs = serial_diffs(serial_nmats, serial_matsize)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The numerical computation agrees with the predictions of Wigner, but it would be nice to get more\n", - "statistics. For that we will do a parallel computation." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "<matplotlib.text.Text at 0x3475bd0>" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": [ - "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\n", - "qJYMKhgTJkwo8vzQoUPodDo6d+5cFZl46aWXePfdd/Uj+fc7JDVv3jz9z/7+/vj7+1do23su7oFE\n", - "BellnckjNBH2Hjz/HTdu36BpvaZapxHCIoSHhxMeHl7xFRkz4BEREaHatWunBgwYoAYMGKDatWun\n", - "IiMjjRo0uXXrlvLy8tI/nzZtmgoNDS3Spk2bNsrZ2Vk5OzurevXqqSZNmqjNmzcXW5eR8Q3y0raX\n", - "FH3MfADYqrMYsI4A1NTQqZX+uxeiuijvd6dR7woMDFQnT57UPz916pQKDAw0eqNeXl4qIiJCXbhw\n", - "QbVv314lJCSU2nbChAkmPUuq/bL2iuYW9OVpdVkMWEdtlOP7jur4n8cr/fcvRHVQ3u9Oo86SSk5O\n", - "pkWLFvrnzZs3Jzk52ei9msWLFxMUFES/fv34+9//jqOjIyEhIYSEhBi9rsp0OeUySRlJcF3TGKIs\n", - "GfBar9eYvXO21kmEqFaMutJ71apVrFu3jmeeeQalFN999x1jxoxh6tSpVZmxVJV9pffnRz5n+7nt\n", - "rH9mPZjrVc9Wn8WwdWTmZOK+0p2Ph3xMX5e+ZbQXQtyryu+HoZTi2rVrXL9+ndDQUHQ6HUOGDMHb\n", - "29vojVaWyi4YYzeN5fE2jzOlyxQs6cvTurIYtg6lFBuPb+SdPe9w6IVDcmc+IYxgkoLRsWNHjh07\n", - "ZvRGqkplFox8lU/zhc2JeT4G50bOWNKXp3VlMbxgKKXo+VlPXujygtyZTwgjVPlcUjqdDl9fX7Zu\n", - "3Wr0RizBsT+PYV/LntYNW2sdRRhIp9OxcMBC3vjlDdKz07WOI4TVM2rQOzo6mqFDh9KsWTO8vb3x\n", - "9vausmsxTG3nuZ30dzHuqnWhve4tu9OzVU8+PPCh1lGEsHpGDXqfO3euxN2Ytm3bVmooQ1XmIalB\n", - "awcxpfMUnnZ72oDpy83r8Ix1ZTFkHXYU3DfjrkbAFGAlcLvgpfr1G5GaavwZfEJUB1U6hpGTk8P2\n", - "7dvZu3cvAQEB+Pn5YWNj1M5JlaisgpGVm4XTB05cfOkijWo3koJhidsZEAy1UmHLx/o2lXlChBDW\n", - "pErHMF5//XVWrVqFk5MTb731FosXLzZ6Q+bsQPwB3JzcaFS7kdZRRHlF/hPab4bmh7VOIoTVMmgP\n", - "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\n", - "AKKjowkMDNS3iY6OZsSIETg4ODBmzBjeeOONUtdXt2bdqg0shMWoYcDhITug6I2Z5s+ff9/lhqzj\n", - "r+rXb8Sdm3dIvJPIn+l/6h830m/wZ/qfnEk+o//5z/Q/uXH7Bjqdjno161HXri51a9bV/1yvZj0u\n", - "nLzAlS1X9M8L/3/2rDlkpt6B7LuRcimYU+6eR93a9hz77Vdq2NQo9WGrs5VDawYwecGIjY0tcnjJ\n", - "3d2dqKioIgUjJiaG5557Tv/cycmJc+fO8fDDxf+FI4QoVDjFyP389dDWvLuP0pYbso7i0tJ02Nna\n", - "0bx+c4MGzJVSZORmkJ6dTnpOOrezb5Oefff/c9L5z57/4NPCR78sNTuVq7evktnkDjw4EmreLnjY\n", - "ZoNNbpFHus0pHvvyMXLycsjNzy3xkafysNXZFikidrZ2pRaYwkNtOgqKjE6nQ4eOa4euseXjLfpl\n", - "hUXor+0Kf9ayXXmY5aC3UqrY8bXSPmTx1w3pjMpoY+w65hvQpjK2U5E28yk9pymzmPIzV2WW+Qa0\n", - "qYztVLTNX3/nlbOdyv4X+6ZVm0pZsqHM98YZcLOcvLv/yyLLuGB/cT30eoXeb85MXjC6du3KP/7x\n", - "D/3z48ePM3DgwCJtfH19OXHiBAEBAQAkJCTg4uJSbF0WfEawEEJYHJNfndOgQQOg4EypuLg4du7c\n", - "ia+vb5E2vr6+bNq0iaSkJNatW4ebm1tJqxJCCGFCmhySWrx4MUFBQeTk5DBjxgwcHR0JCQkBICgo\n", - "CB8fH3r16sWjjz6Kg4MDa9as0SKmEEKIeykzFxERoVxdXVXbtm3V0qVLS2wzZ84c1aZNG9W5c2d1\n", - "8uRJEycsUFbO3bt3K3t7e+Xl5aW8vLzU22+/bfKMEydOVE2aNFEeHh6ltjGHviwrpzn0pVJKXbp0\n", - "Sfn7+yt3d3fl5+en1q5dW2I7rfvUkJxa92lGRoby8fFRnp6eytfXV3344YclttO6Lw3JqXVf3is3\n", - "N1d5eXmpIUOGlLjc2P40+4Lh5eWlIiIiVFxcnGrfvr1KSEgosjw6Olr17NlTJSUlqXXr1qnAwECz\n", - "zLl79241dOhQTbIVioyMVIcPHy71i9hc+rKsnObQl0opde3aNXXkyBGllFIJCQmqTZs2KjU1tUgb\n", - "c+hTQ3KaQ5+mp6crpZTKzMxUHTp0UGfOnCmy3Bz6Uqmyc5pDXxZauHChGjt2bIl5ytOfZj3D2L3X\n", - "bLRu3Vp/zca9/nrNxsmTJ80yJ2g/SN+7d28aNWpU6nJz6EsoOydo35cAzZo1w8vLCwBHR0c6dOjA\n", - "wYMHi7Qxhz41JCdo36d16tQB4Pbt2+Tm5lKrVq0iy82hL6HsnKB9XwLEx8fz008/8fzzz5eYpzz9\n", - "adYFo7RrNu4VExODu7u7/nnhNRumZEhOnU7H/v378fLy4uWXXzZ5RkOYQ18awhz78uzZsxw/fhwf\n", - "H58ir5tbn5aW0xz6ND8/H09PT5o2bcq0adNo2bJlkeXm0pdl5TSHvgSYNWsWH3zwATY2JX/Nl6c/\n", - "zbpgGEIZcc2Gljp37szly5eJjY3F3d2dmTNnah2pGOnL8klLS2PUqFEsWrSIunWLzjxgTn16v5zm\n", - "0Kc2Njb8+uuvnD17lpUrV3LkyJEiy82lL8vKaQ59GRoaSpMmTfD29i51b6c8/WnWBaNr166cOnVK\n", - "//z48eN069atSJvCazYKlXbNRlUyJGf9+vWpU6cOdnZ2TJ48mdjYWLKyKnaBUGUzh740hDn1ZU5O\n", - "DsOHD+e5557jySefLLbcXPq0rJzm1KfOzs4MHjy42GFdc+nLQqXlNIe+3L9/Pz/++CNt2rRhzJgx\n", - "/PLLL4wbN65Im/L0p1kXDEu5ZsOQnDdu3NBX8y1bttCpU6cSj31qyRz60hDm0pdKKSZPnoyHhwcv\n", - "vfRSiW3MoU8Nyal1nyYmJnLr1i0AkpKS2LFjR7HCZg59aUhOrfsSYMGCBVy+fJkLFy7wzTff8Pjj\n", - "j/Of//ynSJvy9KdZTg1yL0u5ZqOsnN9++y2rVq2iRo0adOrUiYULF5o845gxY4iIiCAxMZGWLVsy\n", - "f/58cnJy9BnNpS/LymkOfQmwb98+1qxZQ6dOnfD29gYK/lAvXbqkz2oOfWpITq379Nq1a4wfP568\n", - "vDyaNWtGcHAwzZs3N7u/dUNyat2XJSk81FTR/rToO+4JIYQwHbM+JCWEEMJ8SMEQQghhECkYQggh\n", - "DCIFQwghhEGkYIhKZWNjQ3BwsP75v//977/cArTq+fv7c/jwYQACAwNJTU2t0PrCw8MZOnSowa9X\n", - "xbaq0tWrV3nmmWdMuk1hmaRgiEpVs2ZNvv/+e5KSkgDjr8TNy8urcIZ7t7l161bs7e0rvE5r1qJF\n", - "CzZu3Kh1DGEBpGCISmVnZ8cLL7zAokWLii27evUqM2fOxNPTk1mzZnHjxg0AJkyYwMsvv4yvry+v\n", - "vvoqEydO5JVXXsHHx4f27dtz5MgRXnjhBTp06MC8efP06/v73/9O165d6dGjB6tXry4xj7OzM0lJ\n", - "SXz00Ud4e3vj7e1NmzZtePzxx4GCecDGjRuHr68vc+bM0V+RGxsbS9++ffH29mb79u1lfu6MjAw+\n", - "/PBD/Pz8CAwMJDw8HIDu3bsXuZq2cO8nMzOzxPaluXz5MoMGDcLLywtPT0/OnTtHXFwc7u7uTJ48\n", - "GTc3N+bPn6/P//bbb+Pj40PXrl1ZsGBBkfW88soreHt706VLFy5cuEBcXBwdO3YE4IsvvmD06NEM\n", - "HjwYDw8Pli5dqn/vtm3b6N69Oz4+Prz00ktMnz69WM6jR4/St29fvLy86Ny5M7dv3y6z74QFqcjU\n", - "uUL8Vb169VRqaqpydnZWKSkp6t///reaN2+eUkqpWbNmqffff18ppdSCBQvU7NmzlVJKjR8/Xvn5\n", - "+emn3J4wYYIaNGiQysrKUl988YWqV6+eCg8PV1lZWcrNzU0/dXxycrJSSqmsrCzl6+urbt++rZRS\n", - "yt/fXx06dEgppZSzs7NKSkrS58vJyVG9e/dWoaGh+ra3bt1SSik1e/Zs9c033yillOrUqZOKjo5W\n", - "t2/fVgMHDixxeujdu3fr7zPw+eefqyVLliillLp+/bry8fFRSim1aNEiNXfuXKWUUlevXlXt27e/\n", - "b/t713mvuXPnqk8++UT/GTIyMtSFCxeUTqdT3333ncrMzFRPP/20+vbbb4v0TW5urho6dKg6deqU\n", - "vq9XrFih77c7d+6oCxcu6KeS//zzz1WTJk3U1atXVWpqqnrooYdUdna2ysnJUc7OzurChQsqKSlJ\n", - "de7cWU2fPr1YzvHjx6uwsDClVME04Lm5ucXaCMslexii0tWvX59x48YV+dcpwM8//8ykSZMAmDx5\n", - "Mlu2bAEKDiGNGDGC+vXr69uOGDGCmjVr0r17dxo2bIifnx81a9bE29tbPxPwzp07CQwMxNvbm/Pn\n", - "z/PLL7+UmW3GjBn07duXwMBADh06xLFjx/D398fb25vQ0FAiIyO5cuUKSil8fHyoW7cuo0aNKnO6\n", - "6k2bNrF69Wq8vb0ZOHAgN27c4MKFC4wcOZJvv/0WgA0bNujHCkpqf/78+VLX37VrVxYvXsx7771H\n", - "cnIyDzzwAFAwLc2wYcOoVasWY8aMYdu2bQAcPHiQ4cOH06lTJw4fPsyOHTvIzs5m9+7dTJkyBSg4\n", - "fFi7du1i2xowYADNmzenfv36uLu7c/jwYaKioujYsSPOzs44ODjwxBNPlNgn3bt3Z86cOSxfvpzc\n", - "3FxsbW3L/J0Iy2H2U4MIy/TSSy/RuXNnJk6cWOT10r54mzdvXuR54fxcNWvWpGHDhvrXa9asSXZ2\n", - "NmlpacyZM4c9e/bw4IMPMmzYMG7evHnfTF988QWXL19m5cqVQME01R4eHuzevbtIu/j4eMM+5D3y\n", - "8/NZsWIFffr0KbascePG/P7772zYsEE/NUNp7Qun6/irwMBAunTpwpo1a+jZsycbN24s0i+FCsdv\n", - "pk+fzrfffouHhwezZs3i5s2b6HS6Emco/au/9ndmZiY1atQoMjZU2jqCgoLo37+/fiqS6OhomjZt\n", - "et/tCcshexiiSjRq1IiRI0fy6aef6r9oBg8ezJdffkl+fj6fffYZTzzxRLnWrZTi1q1b2NnZ0axZ\n", - "M06fPs2uXbvu+55Dhw6xcOFCvvrqK/1rXbt25caNG/o9lvT0dM6cOcNDDz2Era0tsbGxpKens2HD\n", - "hjIzjR07lpCQENLS0gCKTHk9atQo3nvvPVJTU/Hw8CizfUkuXLign7uob9+++nGRlJQUfvjhB7Ky\n", - "sli/fj0DBw4kMzOTtLQ0nJ2duXLlCps3bwYKxpcee+wxVq9ejVKKrKwsMjIyyvxsOp2Obt268fvv\n", - "vxMXF0dycjKhoaElntBw7tw5XFxc+N///V9cXV3N4l4lovJIwRCV6t4vkVdeeYXExET98+DgYC5d\n", - "uoS3tzc3btzg5ZdfLvF9f31e0rKWLVsyfPhwPDw8mDZtWqmnohb+q3rFihXcvHmTxx57DG9vb154\n", - "4QUAvvrqK1atWkWnTp3o0aMHf/zxBwAff/wxr732Gr169cLT07PEL0edTqd/fcSIEfj4+BAQEICH\n", - "hwdz587VtxsxYgTr169n5MiRRV4rqf2967zXhg0b8PDwoGvXrty5c0e/LldXV3788Ue8vLzw8PAg\n", - "MDCQBx54gDlz5uDj48OoUaMYPHiwfj3vvPMOZ8+exdPTk549e+pPPCjcZmnbt7W1Zfny5YwaNYqB\n", - "AwfSsWNH2rRpU6zdkiVL6NixIz4+Pri6utKjR48Sfy/CMsnkg0JYqLi4OIYOHcrvv/9uku2lp6dT\n", - "t25dUlJSGDJkCJ988gnt27c3ybaFeZAxDCEsmCnvODdv3jzCwsKws7Pj2WeflWJRDckehhBCCIPI\n", - "GIYQQgiDSMEQQghhECkYQgghDCIFQwghhEGkYAghhDCIFAwhhBAG+X8LAxP1Be5fBAAAAABJRU5E\n", - "rkJggg==\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "hist_data = hist(serial_diffs, bins=30, normed=True)\n", - "plot(s, rhos)\n", - "xlabel('Normalized level spacing s')\n", - "ylabel('Probability $P(s)$')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Parallel calculation of nearest neighbor eigenvalue distribution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we perform a parallel computation, where each process constructs and diagonalizes a subset of\n", - "the overall set of random matrices." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def parallel_diffs(rc, num, N):\n", - " nengines = len(rc.targets)\n", - " num_per_engine = num/nengines\n", - " print \"Running with\", num_per_engine, \"per engine.\"\n", - " ar = rc.apply_async(ensemble_diffs, num_per_engine, N)\n", - " diffs = np.array(ar.get()).flatten()\n", - " normalized_diffs = normalize_diffs(diffs)\n", - " return normalized_diffs" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "client = Client()\n", - "view = client[:]\n", - "view.run('rmtkernel.py')\n", - "view.block = False" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "parallel_nmats = 40*serial_nmats\n", - "parallel_matsize = 50" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with 10000 per engine.\n", - "1 loops, best of 1: 14 s per loop" - ] - } - ], - "source": [ - "%timeit -r1 -n1 parallel_diffs(view, parallel_nmats, parallel_matsize)" - ] - } - ], - "metadata": { - "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.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/examples/Parallel Computing/rmt/rmtkernel.py b/examples/Parallel Computing/rmt/rmtkernel.py deleted file mode 100644 index a44b2b5..0000000 --- a/examples/Parallel Computing/rmt/rmtkernel.py +++ /dev/null @@ -1,42 +0,0 @@ -#------------------------------------------------------------------------------- -# Core routines for computing properties of symmetric random matrices. -#------------------------------------------------------------------------------- - -import numpy as np -ra = np.random -la = np.linalg - -def GOE(N): - """Creates an NxN element of the Gaussian Orthogonal Ensemble""" - m = ra.standard_normal((N,N)) - m += m.T - return m/2 - - -def center_eigenvalue_diff(mat): - """Compute the eigvals of mat and then find the center eigval difference.""" - N = len(mat) - evals = np.sort(la.eigvals(mat)) - diff = np.abs(evals[N/2] - evals[N/2-1]) - return diff - - -def ensemble_diffs(num, N): - """Return num eigenvalue diffs for the NxN GOE ensemble.""" - diffs = np.empty(num) - for i in xrange(num): - mat = GOE(N) - diffs[i] = center_eigenvalue_diff(mat) - return diffs - - -def normalize_diffs(diffs): - """Normalize an array of eigenvalue diffs.""" - return diffs/diffs.mean() - - -def normalized_ensemble_diffs(num, N): - """Return num *normalized* eigenvalue diffs for the NxN GOE ensemble.""" - diffs = ensemble_diffs(num, N) - return normalize_diffs(diffs) - diff --git a/examples/Parallel Computing/task_profiler.py b/examples/Parallel Computing/task_profiler.py deleted file mode 100644 index bef8351..0000000 --- a/examples/Parallel Computing/task_profiler.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -"""Test the performance of the task farming system. - -This script submits a set of tasks via a LoadBalancedView. The tasks -are basically just a time.sleep(t), where t is a random number between -two limits that can be configured at the command line. To run -the script there must first be an IPython controller and engines running:: - - ipcluster start -n 16 - -A good test to run with 16 engines is:: - - python task_profiler.py -n 128 -t 0.01 -T 1.0 - -This should show a speedup of 13-14x. The limitation here is that the -overhead of a single task is about 0.001-0.01 seconds. -""" -import random, sys -from optparse import OptionParser - -from IPython.utils.timing import time -from IPython.parallel import Client - -def main(): - parser = OptionParser() - parser.set_defaults(n=100) - parser.set_defaults(tmin=1e-3) - parser.set_defaults(tmax=1) - parser.set_defaults(profile='default') - - parser.add_option("-n", type='int', dest='n', - help='the number of tasks to run') - parser.add_option("-t", type='float', dest='tmin', - help='the minimum task length in seconds') - parser.add_option("-T", type='float', dest='tmax', - help='the maximum task length in seconds') - parser.add_option("-p", '--profile', type='str', dest='profile', - help="the cluster profile [default: 'default']") - - (opts, args) = parser.parse_args() - assert opts.tmax >= opts.tmin, "tmax must not be smaller than tmin" - - rc = Client() - view = rc.load_balanced_view() - print(view) - rc.block=True - nengines = len(rc.ids) - with rc[:].sync_imports(): - from IPython.utils.timing import time - - # the jobs should take a random time within a range - times = [random.random()*(opts.tmax-opts.tmin)+opts.tmin for i in range(opts.n)] - stime = sum(times) - - print("executing %i tasks, totalling %.1f secs on %i engines"%(opts.n, stime, nengines)) - time.sleep(1) - start = time.time() - amr = view.map(time.sleep, times) - amr.get() - stop = time.time() - - ptime = stop-start - scale = stime/ptime - - print("executed %.1f secs in %.1f secs"%(stime, ptime)) - print("%.3fx parallel performance on %i engines"%(scale, nengines)) - print("%.1f%% of theoretical max"%(100*scale/nengines)) - - -if __name__ == '__main__': - main() diff --git a/examples/Parallel Computing/throughput.py b/examples/Parallel Computing/throughput.py deleted file mode 100644 index 8c3dd68..0000000 --- a/examples/Parallel Computing/throughput.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import print_function - -import time -import numpy as np -from IPython import parallel - -nlist = map(int, np.logspace(2,9,16,base=2)) -nlist2 = map(int, np.logspace(2,8,15,base=2)) -tlist = map(int, np.logspace(7,22,16,base=2)) -nt = 16 -def wait(t=0): - import time - time.sleep(t) - -def echo(s=''): - return s - -def time_throughput(nmessages, t=0, f=wait): - client = parallel.Client() - view = client.load_balanced_view() - # do one ping before starting timing - if f is echo: - t = np.random.random(t/8) - view.apply_sync(echo, '') - client.spin() - tic = time.time() - for i in xrange(nmessages): - view.apply(f, t) - lap = time.time() - client.wait() - toc = time.time() - return lap-tic, toc-tic - - -def do_runs(nlist,t=0,f=wait, trials=2, runner=time_throughput): - A = np.zeros((len(nlist),2)) - for i,n in enumerate(nlist): - t1 = t2 = 0 - for _ in range(trials): - time.sleep(.25) - ts = runner(n,t,f) - t1 += ts[0] - t2 += ts[1] - t1 /= trials - t2 /= trials - A[i] = (t1,t2) - A[i] = n/A[i] - print(n,A[i]) - return A - -def do_echo(n,tlist=[0],f=echo, trials=2, runner=time_throughput): - A = np.zeros((len(tlist),2)) - for i,t in enumerate(tlist): - t1 = t2 = 0 - for _ in range(trials): - time.sleep(.25) - ts = runner(n,t,f) - t1 += ts[0] - t2 += ts[1] - t1 /= trials - t2 /= trials - A[i] = (t1,t2) - A[i] = n/A[i] - print(t,A[i]) - return A - diff --git a/examples/Parallel Computing/wave2D/RectPartitioner.py b/examples/Parallel Computing/wave2D/RectPartitioner.py deleted file mode 100755 index 920e570..0000000 --- a/examples/Parallel Computing/wave2D/RectPartitioner.py +++ /dev/null @@ -1,492 +0,0 @@ -#!/usr/bin/env python -"""A rectangular domain partitioner and associated communication -functionality for solving PDEs in (1D,2D) using FDM -written in the python language - -The global solution domain is assumed to be of rectangular shape, -where the number of cells in each direction is stored in nx, ny, nz - -The numerical scheme is fully explicit - -Authors -------- - - * Xing Cai - * Min Ragan-Kelley - -""" -from __future__ import print_function -import time - -from numpy import zeros, ascontiguousarray, frombuffer -try: - from mpi4py import MPI -except ImportError: - pass -else: - mpi = MPI.COMM_WORLD - -class RectPartitioner: - """ - Responsible for a rectangular partitioning of a global domain, - which is expressed as the numbers of cells in the different - spatial directions. The partitioning info is expressed as an - array of integers, each indicating the number of subdomains in - one spatial direction. - """ - - def __init__(self, my_id=-1, num_procs=-1, \ - global_num_cells=[], num_parts=[]): - self.nsd = 0 - self.my_id = my_id - self.num_procs = num_procs - self.redim (global_num_cells, num_parts) - - def redim (self, global_num_cells, num_parts): - nsd_ = len(global_num_cells) -# print("Inside the redim function, nsd=%d" %nsd_) - - if nsd_<1 | nsd_>3 | nsd_!=len(num_parts): - print('The input global_num_cells is not ok!') - return - - self.nsd = nsd_ - self.global_num_cells = global_num_cells - self.num_parts = num_parts - - def prepare_communication (self): - """ - Find the subdomain rank (tuple) for each processor and - determine the neighbor info. - """ - - nsd_ = self.nsd - if nsd_<1: - print('Number of space dimensions is %d, nothing to do' %nsd_) - return - - self.subd_rank = [-1,-1,-1] - self.subd_lo_ix = [-1,-1,-1] - self.subd_hi_ix = [-1,-1,-1] - self.lower_neighbors = [-1,-1,-1] - self.upper_neighbors = [-1,-1,-1] - - num_procs = self.num_procs - my_id = self.my_id - - num_subds = 1 - for i in range(nsd_): - num_subds = num_subds*self.num_parts[i] - if my_id==0: - print("# subds=", num_subds) - # should check num_subds againt num_procs - - offsets = [1, 0, 0] - - # find the subdomain rank - self.subd_rank[0] = my_id%self.num_parts[0] - if nsd_>=2: - offsets[1] = self.num_parts[0] - self.subd_rank[1] = my_id/offsets[1] - if nsd_==3: - offsets[1] = self.num_parts[0] - offsets[2] = self.num_parts[0]*self.num_parts[1] - self.subd_rank[1] = (my_id%offsets[2])/self.num_parts[0] - self.subd_rank[2] = my_id/offsets[2] - - print("my_id=%d, subd_rank: "%my_id, self.subd_rank) - if my_id==0: - print("offsets=", offsets) - - # find the neighbor ids - for i in range(nsd_): - rank = self.subd_rank[i] - if rank>0: - self.lower_neighbors[i] = my_id-offsets[i] - if rank=(self.num_parts[i]-m): - ix = ix+1 # load balancing - if rank=0: - self.in_lower_buffers = [zeros(1, float)] - self.out_lower_buffers = [zeros(1, float)] - if self.upper_neighbors[0]>=0: - self.in_upper_buffers = [zeros(1, float)] - self.out_upper_buffers = [zeros(1, float)] - - def get_num_loc_cells(self): - return [self.subd_hi_ix[0]-self.subd_lo_ix[0]] - - -class RectPartitioner2D(RectPartitioner): - """ - Subclass of RectPartitioner, for 2D problems - """ - def prepare_communication (self): - """ - Prepare the buffers to be used for later communications - """ - - RectPartitioner.prepare_communication (self) - - self.in_lower_buffers = [[], []] - self.out_lower_buffers = [[], []] - self.in_upper_buffers = [[], []] - self.out_upper_buffers = [[], []] - - size1 = self.subd_hi_ix[1]-self.subd_lo_ix[1]+1 - if self.lower_neighbors[0]>=0: - self.in_lower_buffers[0] = zeros(size1, float) - self.out_lower_buffers[0] = zeros(size1, float) - if self.upper_neighbors[0]>=0: - self.in_upper_buffers[0] = zeros(size1, float) - self.out_upper_buffers[0] = zeros(size1, float) - - size0 = self.subd_hi_ix[0]-self.subd_lo_ix[0]+1 - if self.lower_neighbors[1]>=0: - self.in_lower_buffers[1] = zeros(size0, float) - self.out_lower_buffers[1] = zeros(size0, float) - if self.upper_neighbors[1]>=0: - self.in_upper_buffers[1] = zeros(size0, float) - self.out_upper_buffers[1] = zeros(size0, float) - - def get_num_loc_cells(self): - return [self.subd_hi_ix[0]-self.subd_lo_ix[0],\ - self.subd_hi_ix[1]-self.subd_lo_ix[1]] - - -class MPIRectPartitioner2D(RectPartitioner2D): - """ - Subclass of RectPartitioner2D, which uses MPI via mpi4py for communication - """ - - def __init__(self, my_id=-1, num_procs=-1, - global_num_cells=[], num_parts=[], - slice_copy=True): - RectPartitioner.__init__(self, my_id, num_procs, - global_num_cells, num_parts) - self.slice_copy = slice_copy - - def update_internal_boundary (self, solution_array): - nsd_ = self.nsd - if nsd_!=len(self.in_lower_buffers) | nsd_!=len(self.out_lower_buffers): - print("Buffers for communicating with lower neighbors not ready") - return - if nsd_!=len(self.in_upper_buffers) | nsd_!=len(self.out_upper_buffers): - print("Buffers for communicating with upper neighbors not ready") - return - - loc_nx = self.subd_hi_ix[0]-self.subd_lo_ix[0] - loc_ny = self.subd_hi_ix[1]-self.subd_lo_ix[1] - - lower_x_neigh = self.lower_neighbors[0] - upper_x_neigh = self.upper_neighbors[0] - lower_y_neigh = self.lower_neighbors[1] - upper_y_neigh = self.upper_neighbors[1] - - # communicate in the x-direction first - if lower_x_neigh>-1: - if self.slice_copy: - self.out_lower_buffers[0] = ascontiguousarray(solution_array[1,:]) - else: - for i in xrange(0,loc_ny+1): - self.out_lower_buffers[0][i] = solution_array[1,i] - mpi.Isend(self.out_lower_buffers[0], lower_x_neigh) - - if upper_x_neigh>-1: - mpi.Recv(self.in_upper_buffers[0], upper_x_neigh) - if self.slice_copy: - solution_array[loc_nx,:] = self.in_upper_buffers[0] - self.out_upper_buffers[0] = ascontiguousarray(solution_array[loc_nx-1,:]) - else: - for i in xrange(0,loc_ny+1): - solution_array[loc_nx,i] = self.in_upper_buffers[0][i] - self.out_upper_buffers[0][i] = solution_array[loc_nx-1,i] - mpi.Isend(self.out_upper_buffers[0], upper_x_neigh) - - if lower_x_neigh>-1: - mpi.Recv(self.in_lower_buffers[0], lower_x_neigh) - if self.slice_copy: - solution_array[0,:] = self.in_lower_buffers[0] - else: - for i in xrange(0,loc_ny+1): - solution_array[0,i] = self.in_lower_buffers[0][i] - - # communicate in the y-direction afterwards - if lower_y_neigh>-1: - if self.slice_copy: - self.out_lower_buffers[1] = ascontiguousarray(solution_array[:,1]) - else: - for i in xrange(0,loc_nx+1): - self.out_lower_buffers[1][i] = solution_array[i,1] - mpi.Isend(self.out_lower_buffers[1], lower_y_neigh) - - if upper_y_neigh>-1: - mpi.Recv(self.in_upper_buffers[1], upper_y_neigh) - if self.slice_copy: - solution_array[:,loc_ny] = self.in_upper_buffers[1] - self.out_upper_buffers[1] = ascontiguousarray(solution_array[:,loc_ny-1]) - else: - for i in xrange(0,loc_nx+1): - solution_array[i,loc_ny] = self.in_upper_buffers[1][i] - self.out_upper_buffers[1][i] = solution_array[i,loc_ny-1] - mpi.Isend(self.out_upper_buffers[1], upper_y_neigh) - - if lower_y_neigh>-1: - mpi.Recv(self.in_lower_buffers[1], lower_y_neigh) - if self.slice_copy: - solution_array[:,0] = self.in_lower_buffers[1] - else: - for i in xrange(0,loc_nx+1): - solution_array[i,0] = self.in_lower_buffers[1][i] - -class ZMQRectPartitioner2D(RectPartitioner2D): - """ - Subclass of RectPartitioner2D, which uses 0MQ via pyzmq for communication - The first two arguments must be `comm`, an EngineCommunicator object, - and `addrs`, a dict of connection information for other EngineCommunicator - objects. - """ - - def __init__(self, comm, addrs, my_id=-1, num_procs=-1, - global_num_cells=[], num_parts=[], - slice_copy=True): - RectPartitioner.__init__(self, my_id, num_procs, - global_num_cells, num_parts) - self.slice_copy = slice_copy - self.comm = comm # an Engine - self.addrs = addrs - - def prepare_communication(self): - RectPartitioner2D.prepare_communication(self) - # connect west/south to east/north - west_id,south_id = self.lower_neighbors[:2] - west = self.addrs.get(west_id, None) - south = self.addrs.get(south_id, None) - self.comm.connect(south, west) - - def update_internal_boundary_x_y (self, solution_array): - """update the inner boundary with the same send/recv pattern as the MPIPartitioner""" - nsd_ = self.nsd - dtype = solution_array.dtype - if nsd_!=len(self.in_lower_buffers) | nsd_!=len(self.out_lower_buffers): - print("Buffers for communicating with lower neighbors not ready") - return - if nsd_!=len(self.in_upper_buffers) | nsd_!=len(self.out_upper_buffers): - print("Buffers for communicating with upper neighbors not ready") - return - - loc_nx = self.subd_hi_ix[0]-self.subd_lo_ix[0] - loc_ny = self.subd_hi_ix[1]-self.subd_lo_ix[1] - - lower_x_neigh = self.lower_neighbors[0] - upper_x_neigh = self.upper_neighbors[0] - lower_y_neigh = self.lower_neighbors[1] - upper_y_neigh = self.upper_neighbors[1] - trackers = [] - flags = dict(copy=False, track=False) - # communicate in the x-direction first - if lower_x_neigh>-1: - if self.slice_copy: - self.out_lower_buffers[0] = ascontiguousarray(solution_array[1,:]) - else: - for i in xrange(0,loc_ny+1): - self.out_lower_buffers[0][i] = solution_array[1,i] - t = self.comm.west.send(self.out_lower_buffers[0], **flags) - trackers.append(t) - - if upper_x_neigh>-1: - msg = self.comm.east.recv(copy=False) - self.in_upper_buffers[0] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[loc_nx,:] = self.in_upper_buffers[0] - self.out_upper_buffers[0] = ascontiguousarray(solution_array[loc_nx-1,:]) - else: - for i in xrange(0,loc_ny+1): - solution_array[loc_nx,i] = self.in_upper_buffers[0][i] - self.out_upper_buffers[0][i] = solution_array[loc_nx-1,i] - t = self.comm.east.send(self.out_upper_buffers[0], **flags) - trackers.append(t) - - - if lower_x_neigh>-1: - msg = self.comm.west.recv(copy=False) - self.in_lower_buffers[0] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[0,:] = self.in_lower_buffers[0] - else: - for i in xrange(0,loc_ny+1): - solution_array[0,i] = self.in_lower_buffers[0][i] - - # communicate in the y-direction afterwards - if lower_y_neigh>-1: - if self.slice_copy: - self.out_lower_buffers[1] = ascontiguousarray(solution_array[:,1]) - else: - for i in xrange(0,loc_nx+1): - self.out_lower_buffers[1][i] = solution_array[i,1] - t = self.comm.south.send(self.out_lower_buffers[1], **flags) - trackers.append(t) - - - if upper_y_neigh>-1: - msg = self.comm.north.recv(copy=False) - self.in_upper_buffers[1] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[:,loc_ny] = self.in_upper_buffers[1] - self.out_upper_buffers[1] = ascontiguousarray(solution_array[:,loc_ny-1]) - else: - for i in xrange(0,loc_nx+1): - solution_array[i,loc_ny] = self.in_upper_buffers[1][i] - self.out_upper_buffers[1][i] = solution_array[i,loc_ny-1] - t = self.comm.north.send(self.out_upper_buffers[1], **flags) - trackers.append(t) - - if lower_y_neigh>-1: - msg = self.comm.south.recv(copy=False) - self.in_lower_buffers[1] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[:,0] = self.in_lower_buffers[1] - else: - for i in xrange(0,loc_nx+1): - solution_array[i,0] = self.in_lower_buffers[1][i] - - # wait for sends to complete: - if flags['track']: - for t in trackers: - t.wait() - - def update_internal_boundary_send_recv (self, solution_array): - """update the inner boundary, sending first, then recving""" - nsd_ = self.nsd - dtype = solution_array.dtype - if nsd_!=len(self.in_lower_buffers) | nsd_!=len(self.out_lower_buffers): - print("Buffers for communicating with lower neighbors not ready") - return - if nsd_!=len(self.in_upper_buffers) | nsd_!=len(self.out_upper_buffers): - print("Buffers for communicating with upper neighbors not ready") - return - - loc_nx = self.subd_hi_ix[0]-self.subd_lo_ix[0] - loc_ny = self.subd_hi_ix[1]-self.subd_lo_ix[1] - - lower_x_neigh = self.lower_neighbors[0] - upper_x_neigh = self.upper_neighbors[0] - lower_y_neigh = self.lower_neighbors[1] - upper_y_neigh = self.upper_neighbors[1] - trackers = [] - flags = dict(copy=False, track=False) - - # send in all directions first - if lower_x_neigh>-1: - if self.slice_copy: - self.out_lower_buffers[0] = ascontiguousarray(solution_array[1,:]) - else: - for i in xrange(0,loc_ny+1): - self.out_lower_buffers[0][i] = solution_array[1,i] - t = self.comm.west.send(self.out_lower_buffers[0], **flags) - trackers.append(t) - - if lower_y_neigh>-1: - if self.slice_copy: - self.out_lower_buffers[1] = ascontiguousarray(solution_array[:,1]) - else: - for i in xrange(0,loc_nx+1): - self.out_lower_buffers[1][i] = solution_array[i,1] - t = self.comm.south.send(self.out_lower_buffers[1], **flags) - trackers.append(t) - - if upper_x_neigh>-1: - if self.slice_copy: - self.out_upper_buffers[0] = ascontiguousarray(solution_array[loc_nx-1,:]) - else: - for i in xrange(0,loc_ny+1): - self.out_upper_buffers[0][i] = solution_array[loc_nx-1,i] - t = self.comm.east.send(self.out_upper_buffers[0], **flags) - trackers.append(t) - - if upper_y_neigh>-1: - if self.slice_copy: - self.out_upper_buffers[1] = ascontiguousarray(solution_array[:,loc_ny-1]) - else: - for i in xrange(0,loc_nx+1): - self.out_upper_buffers[1][i] = solution_array[i,loc_ny-1] - t = self.comm.north.send(self.out_upper_buffers[1], **flags) - trackers.append(t) - - - # now start receiving - if upper_x_neigh>-1: - msg = self.comm.east.recv(copy=False) - self.in_upper_buffers[0] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[loc_nx,:] = self.in_upper_buffers[0] - else: - for i in xrange(0,loc_ny+1): - solution_array[loc_nx,i] = self.in_upper_buffers[0][i] - - if lower_x_neigh>-1: - msg = self.comm.west.recv(copy=False) - self.in_lower_buffers[0] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[0,:] = self.in_lower_buffers[0] - else: - for i in xrange(0,loc_ny+1): - solution_array[0,i] = self.in_lower_buffers[0][i] - - if upper_y_neigh>-1: - msg = self.comm.north.recv(copy=False) - self.in_upper_buffers[1] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[:,loc_ny] = self.in_upper_buffers[1] - else: - for i in xrange(0,loc_nx+1): - solution_array[i,loc_ny] = self.in_upper_buffers[1][i] - - if lower_y_neigh>-1: - msg = self.comm.south.recv(copy=False) - self.in_lower_buffers[1] = frombuffer(msg, dtype=dtype) - if self.slice_copy: - solution_array[:,0] = self.in_lower_buffers[1] - else: - for i in xrange(0,loc_nx+1): - solution_array[i,0] = self.in_lower_buffers[1][i] - - # wait for sends to complete: - if flags['track']: - for t in trackers: - t.wait() - - # use send/recv pattern instead of x/y sweeps - update_internal_boundary = update_internal_boundary_send_recv - diff --git a/examples/Parallel Computing/wave2D/communicator.py b/examples/Parallel Computing/wave2D/communicator.py deleted file mode 100644 index 4abb2eb..0000000 --- a/examples/Parallel Computing/wave2D/communicator.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -"""A simple Communicator class that has N,E,S,W neighbors connected via 0MQ PEER sockets""" - -import socket - -import zmq - -from IPython.parallel.util import disambiguate_url - -class EngineCommunicator(object): - """An object that connects Engines to each other. - north and east sockets listen, while south and west sockets connect. - - This class is useful in cases where there is a set of nodes that - must communicate only with their nearest neighbors. - """ - - def __init__(self, interface='tcp://*', identity=None): - self._ctx = zmq.Context() - self.north = self._ctx.socket(zmq.PAIR) - self.west = self._ctx.socket(zmq.PAIR) - self.south = self._ctx.socket(zmq.PAIR) - self.east = self._ctx.socket(zmq.PAIR) - - # bind to ports - northport = self.north.bind_to_random_port(interface) - eastport = self.east.bind_to_random_port(interface) - - self.north_url = interface+":%i"%northport - self.east_url = interface+":%i"%eastport - - # guess first public IP from socket - self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0] - - def __del__(self): - self.north.close() - self.south.close() - self.east.close() - self.west.close() - self._ctx.term() - - @property - def info(self): - """return the connection info for this object's sockets.""" - return (self.location, self.north_url, self.east_url) - - def connect(self, south_peer=None, west_peer=None): - """connect to peers. `peers` will be a 3-tuples, of the form: - (location, north_addr, east_addr) - as produced by - """ - if south_peer is not None: - location, url, _ = south_peer - self.south.connect(disambiguate_url(url, location)) - if west_peer is not None: - location, _, url = west_peer - self.west.connect(disambiguate_url(url, location)) - - diff --git a/examples/Parallel Computing/wave2D/parallelwave-mpi.py b/examples/Parallel Computing/wave2D/parallelwave-mpi.py deleted file mode 100755 index 18f805a..0000000 --- a/examples/Parallel Computing/wave2D/parallelwave-mpi.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python -""" -A simple python program of solving a 2D wave equation in parallel. -Domain partitioning and inter-processor communication -are done by an object of class MPIRectPartitioner2D -(which is a subclass of RectPartitioner2D and uses MPI via mpi4py) - -An example of running the program is (8 processors, 4x2 partition, -400x100 grid cells):: - - $ ipcluster start --engines=MPIExec -n 8 # start 8 engines with mpiexec - $ python parallelwave-mpi.py --grid 400 100 --partition 4 2 - -See also parallelwave-mpi, which runs the same program, but uses MPI -(via mpi4py) for the inter-engine communication. - -Authors -------- - - * Xing Cai - * Min Ragan-Kelley - -""" - -import sys -import time - -from numpy import exp, zeros, newaxis, sqrt - -from IPython.external import argparse -from IPython.parallel import Client, Reference - -def setup_partitioner(index, num_procs, gnum_cells, parts): - """create a partitioner in the engine namespace""" - global partitioner - p = MPIRectPartitioner2D(my_id=index, num_procs=num_procs) - p.redim(global_num_cells=gnum_cells, num_parts=parts) - p.prepare_communication() - # put the partitioner into the global namespace: - partitioner=p - -def setup_solver(*args, **kwargs): - """create a WaveSolver in the engine namespace""" - global solver - solver = WaveSolver(*args, **kwargs) - -def wave_saver(u, x, y, t): - """save the wave log""" - global u_hist - global t_hist - t_hist.append(t) - u_hist.append(1.0*u) - - -# main program: -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - paa = parser.add_argument - paa('--grid', '-g', - type=int, nargs=2, default=[100,100], dest='grid', - help="Cells in the grid, e.g. --grid 100 200") - paa('--partition', '-p', - type=int, nargs=2, default=None, - help="Process partition grid, e.g. --partition 4 2 for 4x2") - paa('-c', - type=float, default=1., - help="Wave speed (I think)") - paa('-Ly', - type=float, default=1., - help="system size (in y)") - paa('-Lx', - type=float, default=1., - help="system size (in x)") - paa('-t', '--tstop', - type=float, default=1., - help="Time units to run") - paa('--profile', - type=unicode, default=u'default', - help="Specify the ipcluster profile for the client to connect to.") - paa('--save', - action='store_true', - help="Add this flag to save the time/wave history during the run.") - paa('--scalar', - action='store_true', - help="Also run with scalar interior implementation, to see vector speedup.") - - ns = parser.parse_args() - # set up arguments - grid = ns.grid - partition = ns.partition - Lx = ns.Lx - Ly = ns.Ly - c = ns.c - tstop = ns.tstop - if ns.save: - user_action = wave_saver - else: - user_action = None - - num_cells = 1.0*(grid[0]-1)*(grid[1]-1) - final_test = True - - # create the Client - rc = Client(profile=ns.profile) - num_procs = len(rc.ids) - - if partition is None: - partition = [1,num_procs] - - assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs) - - view = rc[:] - print("Running %s system on %s processes until %f" % (grid, partition, tstop)) - - # functions defining initial/boundary/source conditions - def I(x,y): - from numpy import exp - return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2)) - def f(x,y,t): - return 0.0 - # from numpy import exp,sin - # return 10*exp(-(x - sin(100*t))**2) - def bc(x,y,t): - return 0.0 - - # initial imports, setup rank - view.execute('\n'.join([ - "from mpi4py import MPI", - "import numpy", - "mpi = MPI.COMM_WORLD", - "my_id = MPI.COMM_WORLD.Get_rank()"]), block=True) - - # initialize t_hist/u_hist for saving the state at each step (optional) - view['t_hist'] = [] - view['u_hist'] = [] - - # set vector/scalar implementation details - impl = {} - impl['ic'] = 'vectorized' - impl['inner'] = 'scalar' - impl['bc'] = 'vectorized' - - # execute some files so that the classes we need will be defined on the engines: - view.run('RectPartitioner.py') - view.run('wavesolver.py') - - # setup remote partitioner - # note that Reference means that the argument passed to setup_partitioner will be the - # object named 'my_id' in the engine's namespace - view.apply_sync(setup_partitioner, Reference('my_id'), num_procs, grid, partition) - # wait for initial communication to complete - view.execute('mpi.barrier()') - # setup remote solvers - view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl) - - # lambda for calling solver.solve: - _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs) - - if ns.scalar: - impl['inner'] = 'scalar' - # run first with element-wise Python operations for each cell - t0 = time.time() - ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action) - if final_test: - # this sum is performed element-wise as results finish - s = sum(ar) - # the L2 norm (RMS) of the result: - norm = sqrt(s/num_cells) - else: - norm = -1 - t1 = time.time() - print('scalar inner-version, Wtime=%g, norm=%g' % (t1-t0, norm)) - - impl['inner'] = 'vectorized' - # setup new solvers - view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl) - view.execute('mpi.barrier()') - - # run again with numpy vectorized inner-implementation - t0 = time.time() - ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action) - if final_test: - # this sum is performed element-wise as results finish - s = sum(ar) - # the L2 norm (RMS) of the result: - norm = sqrt(s/num_cells) - else: - norm = -1 - t1 = time.time() - print('vector inner-version, Wtime=%g, norm=%g' % (t1-t0, norm)) - - # if ns.save is True, then u_hist stores the history of u as a list - # If the partion scheme is Nx1, then u can be reconstructed via 'gather': - if ns.save and partition[-1] == 1: - import matplotlib.pyplot as plt - view.execute('u_last=u_hist[-1]') - # map mpi IDs to IPython IDs, which may not match - ranks = view['my_id'] - targets = range(len(ranks)) - for idx in range(len(ranks)): - targets[idx] = ranks.index(idx) - u_last = rc[targets].gather('u_last', block=True) - plt.pcolor(u_last) - plt.show() diff --git a/examples/Parallel Computing/wave2D/parallelwave.py b/examples/Parallel Computing/wave2D/parallelwave.py deleted file mode 100755 index 966ea73..0000000 --- a/examples/Parallel Computing/wave2D/parallelwave.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env python -""" -A simple python program of solving a 2D wave equation in parallel. -Domain partitioning and inter-processor communication -are done by an object of class ZMQRectPartitioner2D -(which is a subclass of RectPartitioner2D and uses 0MQ via pyzmq) - -An example of running the program is (8 processors, 4x2 partition, -200x200 grid cells):: - - $ ipcluster start -n 8 # start 8 engines - $ python parallelwave.py --grid 200 200 --partition 4 2 - -See also parallelwave-mpi, which runs the same program, but uses MPI -(via mpi4py) for the inter-engine communication. - -Authors -------- - - * Xing Cai - * Min Ragan-Kelley - -""" -# -import sys -import time - -from numpy import exp, zeros, newaxis, sqrt - -from IPython.external import argparse -from IPython.parallel import Client, Reference - -def setup_partitioner(comm, addrs, index, num_procs, gnum_cells, parts): - """create a partitioner in the engine namespace""" - global partitioner - p = ZMQRectPartitioner2D(comm, addrs, my_id=index, num_procs=num_procs) - p.redim(global_num_cells=gnum_cells, num_parts=parts) - p.prepare_communication() - # put the partitioner into the global namespace: - partitioner=p - -def setup_solver(*args, **kwargs): - """create a WaveSolver in the engine namespace.""" - global solver - solver = WaveSolver(*args, **kwargs) - -def wave_saver(u, x, y, t): - """save the wave state for each timestep.""" - global u_hist - global t_hist - t_hist.append(t) - u_hist.append(1.0*u) - - -# main program: -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - paa = parser.add_argument - paa('--grid', '-g', - type=int, nargs=2, default=[100,100], dest='grid', - help="Cells in the grid, e.g. --grid 100 200") - paa('--partition', '-p', - type=int, nargs=2, default=None, - help="Process partition grid, e.g. --partition 4 2 for 4x2") - paa('-c', - type=float, default=1., - help="Wave speed (I think)") - paa('-Ly', - type=float, default=1., - help="system size (in y)") - paa('-Lx', - type=float, default=1., - help="system size (in x)") - paa('-t', '--tstop', - type=float, default=1., - help="Time units to run") - paa('--profile', - type=unicode, default=u'default', - help="Specify the ipcluster profile for the client to connect to.") - paa('--save', - action='store_true', - help="Add this flag to save the time/wave history during the run.") - paa('--scalar', - action='store_true', - help="Also run with scalar interior implementation, to see vector speedup.") - - ns = parser.parse_args() - # set up arguments - grid = ns.grid - partition = ns.partition - Lx = ns.Lx - Ly = ns.Ly - c = ns.c - tstop = ns.tstop - if ns.save: - user_action = wave_saver - else: - user_action = None - - num_cells = 1.0*(grid[0]-1)*(grid[1]-1) - final_test = True - - # create the Client - rc = Client(profile=ns.profile) - num_procs = len(rc.ids) - - if partition is None: - partition = [num_procs,1] - else: - num_procs = min(num_procs, partition[0]*partition[1]) - - assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs) - - # construct the View: - view = rc[:num_procs] - print("Running %s system on %s processes until %f"%(grid, partition, tstop)) - - # functions defining initial/boundary/source conditions - def I(x,y): - from numpy import exp - return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2)) - def f(x,y,t): - return 0.0 - # from numpy import exp,sin - # return 10*exp(-(x - sin(100*t))**2) - def bc(x,y,t): - return 0.0 - - # initialize t_hist/u_hist for saving the state at each step (optional) - view['t_hist'] = [] - view['u_hist'] = [] - - # set vector/scalar implementation details - impl = {} - impl['ic'] = 'vectorized' - impl['inner'] = 'scalar' - impl['bc'] = 'vectorized' - - # execute some files so that the classes we need will be defined on the engines: - view.execute('import numpy') - view.run('communicator.py') - view.run('RectPartitioner.py') - view.run('wavesolver.py') - - # scatter engine IDs - view.scatter('my_id', range(num_procs), flatten=True) - - # create the engine connectors - view.execute('com = EngineCommunicator()') - - # gather the connection information into a single dict - ar = view.apply_async(lambda : com.info) - peers = ar.get_dict() - # print peers - # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators - - # setup remote partitioner - # note that Reference means that the argument passed to setup_partitioner will be the - # object named 'com' in the engine's namespace - view.apply_sync(setup_partitioner, Reference('com'), peers, Reference('my_id'), num_procs, grid, partition) - time.sleep(1) - # convenience lambda to call solver.solve: - _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs) - - if ns.scalar: - impl['inner'] = 'scalar' - # setup remote solvers - view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly, partitioner=Reference('partitioner'), dt=0,implementation=impl) - - # run first with element-wise Python operations for each cell - t0 = time.time() - ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action) - if final_test: - # this sum is performed element-wise as results finish - s = sum(ar) - # the L2 norm (RMS) of the result: - norm = sqrt(s/num_cells) - else: - norm = -1 - t1 = time.time() - print('scalar inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)) - - # run again with faster numpy-vectorized inner implementation: - impl['inner'] = 'vectorized' - # setup remote solvers - view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl) - - t0 = time.time() - - ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action) - if final_test: - # this sum is performed element-wise as results finish - s = sum(ar) - # the L2 norm (RMS) of the result: - norm = sqrt(s/num_cells) - else: - norm = -1 - t1 = time.time() - print('vector inner-version, Wtime=%g, norm=%g'%(t1-t0, norm)) - - # if ns.save is True, then u_hist stores the history of u as a list - # If the partion scheme is Nx1, then u can be reconstructed via 'gather': - if ns.save and partition[-1] == 1: - import matplotlib.pyplot as plt - view.execute('u_last=u_hist[-1]') - u_last = view.gather('u_last', block=True) - plt.pcolor(u_last) - plt.show() diff --git a/examples/Parallel Computing/wave2D/wavesolver.py b/examples/Parallel Computing/wave2D/wavesolver.py deleted file mode 100755 index 440d966..0000000 --- a/examples/Parallel Computing/wave2D/wavesolver.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python -""" -A simple WaveSolver class for evolving the wave equation in 2D. -This works in parallel by using a RectPartitioner object. - -Authors -------- - - * Xing Cai - * Min Ragan-Kelley - -""" -import time - -from numpy import exp, zeros, newaxis, sqrt, arange - -def iseq(start=0, stop=None, inc=1): - """ - Generate integers from start to (and including!) stop, - with increment of inc. Alternative to range/xrange. - """ - if stop is None: # allow isequence(3) to be 0, 1, 2, 3 - # take 1st arg as stop, start as 0, and inc=1 - stop = start; start = 0; inc = 1 - return arange(start, stop+inc, inc) - -class WaveSolver(object): - """ - Solve the 2D wave equation u_tt = u_xx + u_yy + f(x,y,t) with - u = bc(x,y,t) on the boundary and initial condition du/dt = 0. - - Parallelization by using a RectPartitioner object 'partitioner' - - nx and ny are the total number of global grid cells in the x and y - directions. The global grid points are numbered as (0,0), (1,0), (2,0), - ..., (nx,0), (0,1), (1,1), ..., (nx, ny). - - dt is the time step. If dt<=0, an optimal time step is used. - - tstop is the stop time for the simulation. - - I, f are functions: I(x,y), f(x,y,t) - - user_action: function of (u, x, y, t) called at each time - level (x and y are one-dimensional coordinate vectors). - This function allows the calling code to plot the solution, - compute errors, etc. - - implementation: a dictionary specifying how the initial - condition ('ic'), the scheme over inner points ('inner'), - and the boundary conditions ('bc') are to be implemented. - Normally, values are legal: 'scalar' or 'vectorized'. - 'scalar' means straight loops over grid points, while - 'vectorized' means special NumPy vectorized operations. - - If a key in the implementation dictionary is missing, it - defaults in this function to 'scalar' (the safest strategy). - Note that if 'vectorized' is specified, the functions I, f, - and bc must work in vectorized mode. It is always recommended - to first run the 'scalar' mode and then compare 'vectorized' - results with the 'scalar' results to check that I, f, and bc - work. - - verbose: true if a message at each time step is written, - false implies no output during the simulation. - - final_test: true means the discrete L2-norm of the final solution is - to be computed. - """ - - def __init__(self, I, f, c, bc, Lx, Ly, partitioner=None, dt=-1, - user_action=None, - implementation={'ic': 'vectorized', # or 'scalar' - 'inner': 'vectorized', - 'bc': 'vectorized'}): - - nx = partitioner.global_num_cells[0] # number of global cells in x dir - ny = partitioner.global_num_cells[1] # number of global cells in y dir - dx = Lx/float(nx) - dy = Ly/float(ny) - loc_nx, loc_ny = partitioner.get_num_loc_cells() - nx = loc_nx; ny = loc_ny # now use loc_nx and loc_ny instead - lo_ix0 = partitioner.subd_lo_ix[0] - lo_ix1 = partitioner.subd_lo_ix[1] - hi_ix0 = partitioner.subd_hi_ix[0] - hi_ix1 = partitioner.subd_hi_ix[1] - x = iseq(dx*lo_ix0, dx*hi_ix0, dx) # local grid points in x dir - y = iseq(dy*lo_ix1, dy*hi_ix1, dy) # local grid points in y dir - self.x = x - self.y = y - xv = x[:,newaxis] # for vectorized expressions with f(xv,yv) - yv = y[newaxis,:] # -- " -- - if dt <= 0: - dt = (1/float(c))*(1/sqrt(1/dx**2 + 1/dy**2)) # max time step - Cx2 = (c*dt/dx)**2; Cy2 = (c*dt/dy)**2; dt2 = dt**2 # help variables - - u = zeros((nx+1,ny+1)) # solution array - u_1 = u.copy() # solution at t-dt - u_2 = u.copy() # solution at t-2*dt - - # preserve for self.solve - implementation=dict(implementation) # copy - - if 'ic' not in implementation: - implementation['ic'] = 'scalar' - if 'bc' not in implementation: - implementation['bc'] = 'scalar' - if 'inner' not in implementation: - implementation['inner'] = 'scalar' - - self.implementation = implementation - self.Lx = Lx - self.Ly = Ly - self.I=I - self.f=f - self.c=c - self.bc=bc - self.user_action = user_action - self.partitioner=partitioner - - # set initial condition (pointwise - allows straight if-tests in I(x,y)): - t=0.0 - if implementation['ic'] == 'scalar': - for i in xrange(0,nx+1): - for j in xrange(0,ny+1): - u_1[i,j] = I(x[i], y[j]) - - for i in xrange(1,nx): - for j in xrange(1,ny): - u_2[i,j] = u_1[i,j] + \ - 0.5*Cx2*(u_1[i-1,j] - 2*u_1[i,j] + u_1[i+1,j]) + \ - 0.5*Cy2*(u_1[i,j-1] - 2*u_1[i,j] + u_1[i,j+1]) + \ - dt2*f(x[i], y[j], 0.0) - - # boundary values of u_2 (equals u(t=dt) due to du/dt=0) - i = 0 - for j in xrange(0,ny+1): - u_2[i,j] = bc(x[i], y[j], t+dt) - j = 0 - for i in xrange(0,nx+1): - u_2[i,j] = bc(x[i], y[j], t+dt) - i = nx - for j in xrange(0,ny+1): - u_2[i,j] = bc(x[i], y[j], t+dt) - j = ny - for i in xrange(0,nx+1): - u_2[i,j] = bc(x[i], y[j], t+dt) - - elif implementation['ic'] == 'vectorized': - u_1 = I(xv,yv) - u_2[1:nx,1:ny] = u_1[1:nx,1:ny] + \ - 0.5*Cx2*(u_1[0:nx-1,1:ny] - 2*u_1[1:nx,1:ny] + u_1[2:nx+1,1:ny]) + \ - 0.5*Cy2*(u_1[1:nx,0:ny-1] - 2*u_1[1:nx,1:ny] + u_1[1:nx,2:ny+1]) + \ - dt2*(f(xv[1:nx,1:ny], yv[1:nx,1:ny], 0.0)) - # boundary values (t=dt): - i = 0; u_2[i,:] = bc(x[i], y, t+dt) - j = 0; u_2[:,j] = bc(x, y[j], t+dt) - i = nx; u_2[i,:] = bc(x[i], y, t+dt) - j = ny; u_2[:,j] = bc(x, y[j], t+dt) - - if user_action is not None: - user_action(u_1, x, y, t) # allow user to plot etc. - # print(list(self.us[2][2])) - self.us = (u,u_1,u_2) - - - def solve(self, tstop, dt=-1, user_action=None, verbose=False, final_test=False): - t0=time.time() - f=self.f - c=self.c - bc=self.bc - partitioner = self.partitioner - implementation = self.implementation - nx = partitioner.global_num_cells[0] # number of global cells in x dir - ny = partitioner.global_num_cells[1] # number of global cells in y dir - dx = self.Lx/float(nx) - dy = self.Ly/float(ny) - loc_nx, loc_ny = partitioner.get_num_loc_cells() - nx = loc_nx; ny = loc_ny # now use loc_nx and loc_ny instead - x = self.x - y = self.y - xv = x[:,newaxis] # for vectorized expressions with f(xv,yv) - yv = y[newaxis,:] # -- " -- - if dt <= 0: - dt = (1/float(c))*(1/sqrt(1/dx**2 + 1/dy**2)) # max time step - Cx2 = (c*dt/dx)**2; Cy2 = (c*dt/dy)**2; dt2 = dt**2 # help variables - # id for the four possible neighbor subdomains - lower_x_neigh = partitioner.lower_neighbors[0] - upper_x_neigh = partitioner.upper_neighbors[0] - lower_y_neigh = partitioner.lower_neighbors[1] - upper_y_neigh = partitioner.upper_neighbors[1] - u,u_1,u_2 = self.us - # u_1 = self.u_1 - - t = 0.0 - while t <= tstop: - t_old = t; t += dt - if verbose: - print('solving (%s version) at t=%g' % \ - (implementation['inner'], t)) - # update all inner points: - if implementation['inner'] == 'scalar': - for i in xrange(1, nx): - for j in xrange(1, ny): - u[i,j] = - u_2[i,j] + 2*u_1[i,j] + \ - Cx2*(u_1[i-1,j] - 2*u_1[i,j] + u_1[i+1,j]) + \ - Cy2*(u_1[i,j-1] - 2*u_1[i,j] + u_1[i,j+1]) + \ - dt2*f(x[i], y[j], t_old) - elif implementation['inner'] == 'vectorized': - u[1:nx,1:ny] = - u_2[1:nx,1:ny] + 2*u_1[1:nx,1:ny] + \ - Cx2*(u_1[0:nx-1,1:ny] - 2*u_1[1:nx,1:ny] + u_1[2:nx+1,1:ny]) + \ - Cy2*(u_1[1:nx,0:ny-1] - 2*u_1[1:nx,1:ny] + u_1[1:nx,2:ny+1]) + \ - dt2*f(xv[1:nx,1:ny], yv[1:nx,1:ny], t_old) - - # insert boundary conditions (if there's no neighbor): - if lower_x_neigh < 0: - if implementation['bc'] == 'scalar': - i = 0 - for j in xrange(0, ny+1): - u[i,j] = bc(x[i], y[j], t) - elif implementation['bc'] == 'vectorized': - u[0,:] = bc(x[0], y, t) - if upper_x_neigh < 0: - if implementation['bc'] == 'scalar': - i = nx - for j in xrange(0, ny+1): - u[i,j] = bc(x[i], y[j], t) - elif implementation['bc'] == 'vectorized': - u[nx,:] = bc(x[nx], y, t) - if lower_y_neigh < 0: - if implementation['bc'] == 'scalar': - j = 0 - for i in xrange(0, nx+1): - u[i,j] = bc(x[i], y[j], t) - elif implementation['bc'] == 'vectorized': - u[:,0] = bc(x, y[0], t) - if upper_y_neigh < 0: - if implementation['bc'] == 'scalar': - j = ny - for i in xrange(0, nx+1): - u[i,j] = bc(x[i], y[j], t) - elif implementation['bc'] == 'vectorized': - u[:,ny] = bc(x, y[ny], t) - - # communication - partitioner.update_internal_boundary (u) - - if user_action is not None: - user_action(u, x, y, t) - # update data structures for next step - u_2, u_1, u = u_1, u, u_2 - - t1 = time.time() - print('my_id=%2d, dt=%g, %s version, slice_copy=%s, net Wtime=%g'\ - %(partitioner.my_id,dt,implementation['inner'],\ - partitioner.slice_copy,t1-t0)) - # save the us - self.us = u,u_1,u_2 - # check final results; compute discrete L2-norm of the solution - if final_test: - loc_res = 0.0 - for i in iseq(start=1, stop=nx-1): - for j in iseq(start=1, stop=ny-1): - loc_res += u_1[i,j]**2 - return loc_res - return dt - diff --git a/ipython_parallel/__init__.py b/ipython_parallel/__init__.py deleted file mode 100644 index 3efff79..0000000 --- a/ipython_parallel/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -"""The IPython ZMQ-based parallel computing interface.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -import os -import warnings - -import zmq - -from IPython.config.configurable import MultipleInstanceError - -from ipython_kernel.pickleutil import Reference - -from .client.asyncresult import * -from .client.client import Client -from .client.remotefunction import * -from .client.view import * -from .controller.dependency import * -from .error import * -from .util import interactive - -#----------------------------------------------------------------------------- -# Functions -#----------------------------------------------------------------------------- - - -def bind_kernel(**kwargs): - """Bind an Engine's Kernel to be used as a full IPython kernel. - - This allows a running Engine to be used simultaneously as a full IPython kernel - with the QtConsole or other frontends. - - This function returns immediately. - """ - from IPython.kernel.zmq.kernelapp import IPKernelApp - from ipython_parallel.apps.ipengineapp import IPEngineApp - - # first check for IPKernelApp, in which case this should be a no-op - # because there is already a bound kernel - if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp): - return - - if IPEngineApp.initialized(): - try: - app = IPEngineApp.instance() - except MultipleInstanceError: - pass - else: - return app.bind_kernel(**kwargs) - - raise RuntimeError("bind_kernel be called from an IPEngineApp instance") - - - diff --git a/ipython_parallel/apps/__init__.py b/ipython_parallel/apps/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/ipython_parallel/apps/__init__.py +++ /dev/null diff --git a/ipython_parallel/apps/baseapp.py b/ipython_parallel/apps/baseapp.py deleted file mode 100644 index 15aa84e..0000000 --- a/ipython_parallel/apps/baseapp.py +++ /dev/null @@ -1,242 +0,0 @@ -# encoding: utf-8 -""" -The Base Application class for ipython_parallel apps -""" - - -import os -import logging -import re -import sys - -from IPython.config.application import catch_config_error, LevelFormatter -from IPython.core import release -from IPython.core.crashhandler import CrashHandler -from IPython.core.application import ( - BaseIPythonApplication, - base_aliases as base_ip_aliases, - base_flags as base_ip_flags -) -from IPython.utils.path import expand_path -from IPython.utils.process import check_pid -from IPython.utils import py3compat -from IPython.utils.py3compat import unicode_type - -from IPython.utils.traitlets import Unicode, Bool, Instance, Dict - -#----------------------------------------------------------------------------- -# Module errors -#----------------------------------------------------------------------------- - -class PIDFileError(Exception): - pass - - -#----------------------------------------------------------------------------- -# Crash handler for this application -#----------------------------------------------------------------------------- - -class ParallelCrashHandler(CrashHandler): - """sys.excepthook for IPython itself, leaves a detailed report on disk.""" - - def __init__(self, app): - contact_name = release.authors['Min'][0] - contact_email = release.author_email - bug_tracker = 'https://github.com/ipython/ipython/issues' - super(ParallelCrashHandler,self).__init__( - app, contact_name, contact_email, bug_tracker - ) - - -#----------------------------------------------------------------------------- -# Main application -#----------------------------------------------------------------------------- -base_aliases = {} -base_aliases.update(base_ip_aliases) -base_aliases.update({ - 'work-dir' : 'BaseParallelApplication.work_dir', - 'log-to-file' : 'BaseParallelApplication.log_to_file', - 'clean-logs' : 'BaseParallelApplication.clean_logs', - 'log-url' : 'BaseParallelApplication.log_url', - 'cluster-id' : 'BaseParallelApplication.cluster_id', -}) - -base_flags = { - 'log-to-file' : ( - {'BaseParallelApplication' : {'log_to_file' : True}}, - "send log output to a file" - ) -} -base_flags.update(base_ip_flags) - -class BaseParallelApplication(BaseIPythonApplication): - """The base Application for ipython_parallel apps - - Principle extensions to BaseIPyythonApplication: - - * work_dir - * remote logging via pyzmq - * IOLoop instance - """ - - crash_handler_class = ParallelCrashHandler - - def _log_level_default(self): - # temporarily override default_log_level to INFO - return logging.INFO - - def _log_format_default(self): - """override default log format to include time""" - return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s" - - work_dir = Unicode(py3compat.getcwd(), config=True, - help='Set the working dir for the process.' - ) - def _work_dir_changed(self, name, old, new): - self.work_dir = unicode_type(expand_path(new)) - - log_to_file = Bool(config=True, - help="whether to log to a file") - - clean_logs = Bool(False, config=True, - help="whether to cleanup old logfiles before starting") - - log_url = Unicode('', config=True, - help="The ZMQ URL of the iplogger to aggregate logging.") - - cluster_id = Unicode('', config=True, - help="""String id to add to runtime files, to prevent name collisions when - using multiple clusters with a single profile simultaneously. - - When set, files will be named like: 'ipcontroller--engine.json' - - Since this is text inserted into filenames, typical recommendations apply: - Simple character strings are ideal, and spaces are not recommended (but should - generally work). - """ - ) - def _cluster_id_changed(self, name, old, new): - self.name = self.__class__.name - if new: - self.name += '-%s'%new - - def _config_files_default(self): - return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py'] - - loop = Instance('zmq.eventloop.ioloop.IOLoop') - def _loop_default(self): - from zmq.eventloop.ioloop import IOLoop - return IOLoop.instance() - - aliases = Dict(base_aliases) - flags = Dict(base_flags) - - @catch_config_error - def initialize(self, argv=None): - """initialize the app""" - super(BaseParallelApplication, self).initialize(argv) - self.to_work_dir() - self.reinit_logging() - - def to_work_dir(self): - wd = self.work_dir - if unicode_type(wd) != py3compat.getcwd(): - os.chdir(wd) - self.log.info("Changing to working dir: %s" % wd) - # This is the working dir by now. - sys.path.insert(0, '') - - def reinit_logging(self): - # Remove old log files - log_dir = self.profile_dir.log_dir - if self.clean_logs: - for f in os.listdir(log_dir): - if re.match(r'%s-\d+\.(log|err|out)' % self.name, f): - try: - os.remove(os.path.join(log_dir, f)) - except (OSError, IOError): - # probably just conflict from sibling process - # already removing it - pass - if self.log_to_file: - # Start logging to the new log file - log_filename = self.name + u'-' + str(os.getpid()) + u'.log' - logfile = os.path.join(log_dir, log_filename) - open_log_file = open(logfile, 'w') - else: - open_log_file = None - if open_log_file is not None: - while self.log.handlers: - self.log.removeHandler(self.log.handlers[0]) - self._log_handler = logging.StreamHandler(open_log_file) - self.log.addHandler(self._log_handler) - else: - self._log_handler = self.log.handlers[0] - # Add timestamps to log format: - self._log_formatter = LevelFormatter(self.log_format, - datefmt=self.log_datefmt) - self._log_handler.setFormatter(self._log_formatter) - # do not propagate log messages to root logger - # ipcluster app will sometimes print duplicate messages during shutdown - # if this is 1 (default): - self.log.propagate = False - - def write_pid_file(self, overwrite=False): - """Create a .pid file in the pid_dir with my pid. - - This must be called after pre_construct, which sets `self.pid_dir`. - This raises :exc:`PIDFileError` if the pid file exists already. - """ - pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') - if os.path.isfile(pid_file): - pid = self.get_pid_from_file() - if not overwrite: - raise PIDFileError( - 'The pid file [%s] already exists. \nThis could mean that this ' - 'server is already running with [pid=%s].' % (pid_file, pid) - ) - with open(pid_file, 'w') as f: - self.log.info("Creating pid file: %s" % pid_file) - f.write(repr(os.getpid())+'\n') - - def remove_pid_file(self): - """Remove the pid file. - - This should be called at shutdown by registering a callback with - :func:`reactor.addSystemEventTrigger`. This needs to return - ``None``. - """ - pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') - if os.path.isfile(pid_file): - try: - self.log.info("Removing pid file: %s" % pid_file) - os.remove(pid_file) - except: - self.log.warn("Error removing the pid file: %s" % pid_file) - - def get_pid_from_file(self): - """Get the pid from the pid file. - - If the pid file doesn't exist a :exc:`PIDFileError` is raised. - """ - pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid') - if os.path.isfile(pid_file): - with open(pid_file, 'r') as f: - s = f.read().strip() - try: - pid = int(s) - except: - raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s)) - return pid - else: - raise PIDFileError('pid file not found: %s' % pid_file) - - def check_pid(self, pid): - try: - return check_pid(pid) - except Exception: - self.log.warn( - "Could not determine whether pid %i is running. " - " Making the likely assumption that it is."%pid - ) - return True diff --git a/ipython_parallel/apps/daemonize.py b/ipython_parallel/apps/daemonize.py deleted file mode 100644 index 6b0ee38..0000000 --- a/ipython_parallel/apps/daemonize.py +++ /dev/null @@ -1,26 +0,0 @@ -"""daemonize function from twisted.scripts._twistd_unix.""" - -#----------------------------------------------------------------------------- -# Copyright (c) Twisted Matrix Laboratories. -# See Twisted's LICENSE for details. -# http://twistedmatrix.com/ -#----------------------------------------------------------------------------- - -import os, errno - -def daemonize(): - # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16 - if os.fork(): # launch child and... - os._exit(0) # kill off parent - os.setsid() - if os.fork(): # launch child and... - os._exit(0) # kill off parent again. - null = os.open('/dev/null', os.O_RDWR) - for i in range(3): - try: - os.dup2(null, i) - except OSError as e: - if e.errno != errno.EBADF: - raise - os.close(null) - diff --git a/ipython_parallel/apps/ipclusterapp.py b/ipython_parallel/apps/ipclusterapp.py deleted file mode 100755 index 54f8fb7..0000000 --- a/ipython_parallel/apps/ipclusterapp.py +++ /dev/null @@ -1,596 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -"""The ipcluster application.""" -from __future__ import print_function - -import errno -import logging -import os -import re -import signal - -from subprocess import check_call, CalledProcessError, PIPE -import zmq - -from IPython.config.application import catch_config_error -from IPython.config.loader import Config -from IPython.core.application import BaseIPythonApplication -from IPython.core.profiledir import ProfileDir -from IPython.utils.importstring import import_item -from IPython.utils.py3compat import string_types -from IPython.utils.sysinfo import num_cpus -from IPython.utils.traitlets import (Integer, Unicode, Bool, CFloat, Dict, List, Any, - DottedObjectName) - -from .baseapp import ( - BaseParallelApplication, - PIDFileError, - base_flags, base_aliases -) -from .daemonize import daemonize - - -#----------------------------------------------------------------------------- -# Module level variables -#----------------------------------------------------------------------------- - - -_description = """Start an IPython cluster for parallel computing. - -An IPython cluster consists of 1 controller and 1 or more engines. -This command automates the startup of these processes using a wide range of -startup methods (SSH, local processes, PBS, mpiexec, SGE, LSF, HTCondor, -Windows HPC Server 2008). To start a cluster with 4 engines on your -local host simply do 'ipcluster start --n=4'. For more complex usage -you will typically do 'ipython profile create mycluster --parallel', then edit -configuration files, followed by 'ipcluster start --profile=mycluster --n=4'. -""" - -_main_examples = """ -ipcluster start --n=4 # start a 4 node cluster on localhost -ipcluster start -h # show the help string for the start subcmd - -ipcluster stop -h # show the help string for the stop subcmd -ipcluster engines -h # show the help string for the engines subcmd -""" - -_start_examples = """ -ipython profile create mycluster --parallel # create mycluster profile -ipcluster start --profile=mycluster --n=4 # start mycluster with 4 nodes -""" - -_stop_examples = """ -ipcluster stop --profile=mycluster # stop a running cluster by profile name -""" - -_engines_examples = """ -ipcluster engines --profile=mycluster --n=4 # start 4 engines only -""" - - -# Exit codes for ipcluster - -# This will be the exit code if the ipcluster appears to be running because -# a .pid file exists -ALREADY_STARTED = 10 - - -# This will be the exit code if ipcluster stop is run, but there is not .pid -# file to be found. -ALREADY_STOPPED = 11 - -# This will be the exit code if ipcluster engines is run, but there is not .pid -# file to be found. -NO_CLUSTER = 12 - - -#----------------------------------------------------------------------------- -# Utilities -#----------------------------------------------------------------------------- - -def find_launcher_class(clsname, kind): - """Return a launcher for a given clsname and kind. - - Parameters - ========== - clsname : str - The full name of the launcher class, either with or without the - module path, or an abbreviation (MPI, SSH, SGE, PBS, LSF, HTCondor - WindowsHPC). - kind : str - Either 'EngineSet' or 'Controller'. - """ - if '.' not in clsname: - # not a module, presume it's the raw name in apps.launcher - if kind and kind not in clsname: - # doesn't match necessary full class name, assume it's - # just 'PBS' or 'MPI' etc prefix: - clsname = clsname + kind + 'Launcher' - clsname = 'ipython_parallel.apps.launcher.'+clsname - klass = import_item(clsname) - return klass - -#----------------------------------------------------------------------------- -# Main application -#----------------------------------------------------------------------------- - -start_help = """Start an IPython cluster for parallel computing - -Start an ipython cluster by its profile name or cluster -directory. Cluster directories contain configuration, log and -security related files and are named using the convention -'profile_' and should be creating using the 'start' -subcommand of 'ipcluster'. If your cluster directory is in -the cwd or the ipython directory, you can simply refer to it -using its profile name, 'ipcluster start --n=4 --profile=`, -otherwise use the 'profile-dir' option. -""" -stop_help = """Stop a running IPython cluster - -Stop a running ipython cluster by its profile name or cluster -directory. Cluster directories are named using the convention -'profile_'. If your cluster directory is in -the cwd or the ipython directory, you can simply refer to it -using its profile name, 'ipcluster stop --profile=`, otherwise -use the '--profile-dir' option. -""" -engines_help = """Start engines connected to an existing IPython cluster - -Start one or more engines to connect to an existing Cluster -by profile name or cluster directory. -Cluster directories contain configuration, log and -security related files and are named using the convention -'profile_' and should be creating using the 'start' -subcommand of 'ipcluster'. If your cluster directory is in -the cwd or the ipython directory, you can simply refer to it -using its profile name, 'ipcluster engines --n=4 --profile=`, -otherwise use the 'profile-dir' option. -""" -stop_aliases = dict( - signal='IPClusterStop.signal', -) -stop_aliases.update(base_aliases) - -class IPClusterStop(BaseParallelApplication): - name = u'ipcluster' - description = stop_help - examples = _stop_examples - - signal = Integer(signal.SIGINT, config=True, - help="signal to use for stopping processes.") - - aliases = Dict(stop_aliases) - - def start(self): - """Start the app for the stop subcommand.""" - try: - pid = self.get_pid_from_file() - except PIDFileError: - self.log.critical( - 'Could not read pid file, cluster is probably not running.' - ) - # Here I exit with a unusual exit status that other processes - # can watch for to learn how I existed. - self.remove_pid_file() - self.exit(ALREADY_STOPPED) - - if not self.check_pid(pid): - self.log.critical( - 'Cluster [pid=%r] is not running.' % pid - ) - self.remove_pid_file() - # Here I exit with a unusual exit status that other processes - # can watch for to learn how I existed. - self.exit(ALREADY_STOPPED) - - elif os.name=='posix': - sig = self.signal - self.log.info( - "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig) - ) - try: - os.kill(pid, sig) - except OSError: - self.log.error("Stopping cluster failed, assuming already dead.", - exc_info=True) - self.remove_pid_file() - elif os.name=='nt': - try: - # kill the whole tree - p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE) - except (CalledProcessError, OSError): - self.log.error("Stopping cluster failed, assuming already dead.", - exc_info=True) - self.remove_pid_file() - -engine_aliases = {} -engine_aliases.update(base_aliases) -engine_aliases.update(dict( - n='IPClusterEngines.n', - engines = 'IPClusterEngines.engine_launcher_class', - daemonize = 'IPClusterEngines.daemonize', -)) -engine_flags = {} -engine_flags.update(base_flags) - -engine_flags.update(dict( - daemonize=( - {'IPClusterEngines' : {'daemonize' : True}}, - """run the cluster into the background (not available on Windows)""", - ) -)) -class IPClusterEngines(BaseParallelApplication): - - name = u'ipcluster' - description = engines_help - examples = _engines_examples - usage = None - default_log_level = logging.INFO - classes = List() - def _classes_default(self): - from ipython_parallel.apps import launcher - launchers = launcher.all_launchers - eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__] - return [ProfileDir]+eslaunchers - - n = Integer(num_cpus(), config=True, - help="""The number of engines to start. The default is to use one for each - CPU on your machine""") - - engine_launcher = Any(config=True, help="Deprecated, use engine_launcher_class") - def _engine_launcher_changed(self, name, old, new): - if isinstance(new, string_types): - self.log.warn("WARNING: %s.engine_launcher is deprecated as of 0.12," - " use engine_launcher_class" % self.__class__.__name__) - self.engine_launcher_class = new - engine_launcher_class = DottedObjectName('LocalEngineSetLauncher', - config=True, - help="""The class for launching a set of Engines. Change this value - to use various batch systems to launch your engines, such as PBS,SGE,MPI,etc. - Each launcher class has its own set of configuration options, for making sure - it will work in your environment. - - You can also write your own launcher, and specify it's absolute import path, - as in 'mymodule.launcher.FTLEnginesLauncher`. - - IPython's bundled examples include: - - Local : start engines locally as subprocesses [default] - MPI : use mpiexec to launch engines in an MPI environment - PBS : use PBS (qsub) to submit engines to a batch queue - SGE : use SGE (qsub) to submit engines to a batch queue - LSF : use LSF (bsub) to submit engines to a batch queue - SSH : use SSH to start the controller - Note that SSH does *not* move the connection files - around, so you will likely have to do this manually - unless the machines are on a shared file system. - HTCondor : use HTCondor to submit engines to a batch queue - WindowsHPC : use Windows HPC - - If you are using one of IPython's builtin launchers, you can specify just the - prefix, e.g: - - c.IPClusterEngines.engine_launcher_class = 'SSH' - - or: - - ipcluster start --engines=MPI - - """ - ) - daemonize = Bool(False, config=True, - help="""Daemonize the ipcluster program. This implies --log-to-file. - Not available on Windows. - """) - - def _daemonize_changed(self, name, old, new): - if new: - self.log_to_file = True - - early_shutdown = Integer(30, config=True, help="The timeout (in seconds)") - _stopping = False - - aliases = Dict(engine_aliases) - flags = Dict(engine_flags) - - @catch_config_error - def initialize(self, argv=None): - super(IPClusterEngines, self).initialize(argv) - self.init_signal() - self.init_launchers() - - def init_launchers(self): - self.engine_launcher = self.build_launcher(self.engine_launcher_class, 'EngineSet') - - def init_signal(self): - # Setup signals - signal.signal(signal.SIGINT, self.sigint_handler) - - def build_launcher(self, clsname, kind=None): - """import and instantiate a Launcher based on importstring""" - try: - klass = find_launcher_class(clsname, kind) - except (ImportError, KeyError): - self.log.fatal("Could not import launcher class: %r"%clsname) - self.exit(1) - - launcher = klass( - work_dir=u'.', parent=self, log=self.log, - profile_dir=self.profile_dir.location, cluster_id=self.cluster_id, - ) - return launcher - - def engines_started_ok(self): - self.log.info("Engines appear to have started successfully") - self.early_shutdown = 0 - - def start_engines(self): - # Some EngineSetLaunchers ignore `n` and use their own engine count, such as SSH: - n = getattr(self.engine_launcher, 'engine_count', self.n) - self.log.info("Starting %s Engines with %s", n, self.engine_launcher_class) - try: - self.engine_launcher.start(self.n) - except: - self.log.exception("Engine start failed") - raise - self.engine_launcher.on_stop(self.engines_stopped_early) - if self.early_shutdown: - self.loop.add_timeout(self.loop.time() + self.early_shutdown, self.engines_started_ok) - - def engines_stopped_early(self, r): - if self.early_shutdown and not self._stopping: - self.log.error(""" - Engines shutdown early, they probably failed to connect. - - Check the engine log files for output. - - If your controller and engines are not on the same machine, you probably - have to instruct the controller to listen on an interface other than localhost. - - You can set this by adding "--ip='*'" to your ControllerLauncher.controller_args. - - Be sure to read our security docs before instructing your controller to listen on - a public interface. - """) - self.stop_launchers() - - return self.engines_stopped(r) - - def engines_stopped(self, r): - return self.loop.stop() - - def stop_engines(self): - if self.engine_launcher.running: - self.log.info("Stopping Engines...") - d = self.engine_launcher.stop() - return d - else: - return None - - def stop_launchers(self, r=None): - if not self._stopping: - self._stopping = True - self.log.error("IPython cluster: stopping") - self.stop_engines() - # Wait a few seconds to let things shut down. - self.loop.add_timeout(self.loop.time() + 3, self.loop.stop) - - def sigint_handler(self, signum, frame): - self.log.debug("SIGINT received, stopping launchers...") - self.stop_launchers() - - def start_logging(self): - # Remove old log files of the controller and engine - if self.clean_logs: - log_dir = self.profile_dir.log_dir - for f in os.listdir(log_dir): - if re.match(r'ip(engine|controller)-.+\.(log|err|out)',f): - os.remove(os.path.join(log_dir, f)) - - def start(self): - """Start the app for the engines subcommand.""" - self.log.info("IPython cluster: started") - # First see if the cluster is already running - - # Now log and daemonize - self.log.info( - 'Starting engines with [daemon=%r]' % self.daemonize - ) - # TODO: Get daemonize working on Windows or as a Windows Server. - if self.daemonize: - if os.name=='posix': - daemonize() - - self.loop.add_callback(self.start_engines) - # Now write the new pid file AFTER our new forked pid is active. - # self.write_pid_file() - try: - self.loop.start() - except KeyboardInterrupt: - pass - except zmq.ZMQError as e: - if e.errno == errno.EINTR: - pass - else: - raise - -start_aliases = {} -start_aliases.update(engine_aliases) -start_aliases.update(dict( - delay='IPClusterStart.delay', - controller = 'IPClusterStart.controller_launcher_class', -)) -start_aliases['clean-logs'] = 'IPClusterStart.clean_logs' - -class IPClusterStart(IPClusterEngines): - - name = u'ipcluster' - description = start_help - examples = _start_examples - default_log_level = logging.INFO - auto_create = Bool(True, config=True, - help="whether to create the profile_dir if it doesn't exist") - classes = List() - def _classes_default(self,): - from ipython_parallel.apps import launcher - return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers - - clean_logs = Bool(True, config=True, - help="whether to cleanup old logs before starting") - - delay = CFloat(1., config=True, - help="delay (in s) between starting the controller and the engines") - - controller_launcher = Any(config=True, help="Deprecated, use controller_launcher_class") - def _controller_launcher_changed(self, name, old, new): - if isinstance(new, string_types): - # old 0.11-style config - self.log.warn("WARNING: %s.controller_launcher is deprecated as of 0.12," - " use controller_launcher_class" % self.__class__.__name__) - self.controller_launcher_class = new - controller_launcher_class = DottedObjectName('LocalControllerLauncher', - config=True, - help="""The class for launching a Controller. Change this value if you want - your controller to also be launched by a batch system, such as PBS,SGE,MPI,etc. - - Each launcher class has its own set of configuration options, for making sure - it will work in your environment. - - Note that using a batch launcher for the controller *does not* put it - in the same batch job as the engines, so they will still start separately. - - IPython's bundled examples include: - - Local : start engines locally as subprocesses - MPI : use mpiexec to launch the controller in an MPI universe - PBS : use PBS (qsub) to submit the controller to a batch queue - SGE : use SGE (qsub) to submit the controller to a batch queue - LSF : use LSF (bsub) to submit the controller to a batch queue - HTCondor : use HTCondor to submit the controller to a batch queue - SSH : use SSH to start the controller - WindowsHPC : use Windows HPC - - If you are using one of IPython's builtin launchers, you can specify just the - prefix, e.g: - - c.IPClusterStart.controller_launcher_class = 'SSH' - - or: - - ipcluster start --controller=MPI - - """ - ) - reset = Bool(False, config=True, - help="Whether to reset config files as part of '--create'." - ) - - # flags = Dict(flags) - aliases = Dict(start_aliases) - - def init_launchers(self): - self.controller_launcher = self.build_launcher(self.controller_launcher_class, 'Controller') - self.engine_launcher = self.build_launcher(self.engine_launcher_class, 'EngineSet') - - def engines_stopped(self, r): - """prevent parent.engines_stopped from stopping everything on engine shutdown""" - pass - - def start_controller(self): - self.log.info("Starting Controller with %s", self.controller_launcher_class) - self.controller_launcher.on_stop(self.stop_launchers) - try: - self.controller_launcher.start() - except: - self.log.exception("Controller start failed") - raise - - def stop_controller(self): - # self.log.info("In stop_controller") - if self.controller_launcher and self.controller_launcher.running: - return self.controller_launcher.stop() - - def stop_launchers(self, r=None): - if not self._stopping: - self.stop_controller() - super(IPClusterStart, self).stop_launchers() - - def start(self): - """Start the app for the start subcommand.""" - # First see if the cluster is already running - try: - pid = self.get_pid_from_file() - except PIDFileError: - pass - else: - if self.check_pid(pid): - self.log.critical( - 'Cluster is already running with [pid=%s]. ' - 'use "ipcluster stop" to stop the cluster.' % pid - ) - # Here I exit with a unusual exit status that other processes - # can watch for to learn how I existed. - self.exit(ALREADY_STARTED) - else: - self.remove_pid_file() - - - # Now log and daemonize - self.log.info( - 'Starting ipcluster with [daemon=%r]' % self.daemonize - ) - # TODO: Get daemonize working on Windows or as a Windows Server. - if self.daemonize: - if os.name=='posix': - daemonize() - - def start(): - self.start_controller() - self.loop.add_timeout(self.loop.time() + self.delay, self.start_engines) - self.loop.add_callback(start) - # Now write the new pid file AFTER our new forked pid is active. - self.write_pid_file() - try: - self.loop.start() - except KeyboardInterrupt: - pass - except zmq.ZMQError as e: - if e.errno == errno.EINTR: - pass - else: - raise - finally: - self.remove_pid_file() - -base='ipython_parallel.apps.ipclusterapp.IPCluster' - -class IPClusterApp(BaseIPythonApplication): - name = u'ipcluster' - description = _description - examples = _main_examples - - subcommands = { - 'start' : (base+'Start', start_help), - 'stop' : (base+'Stop', stop_help), - 'engines' : (base+'Engines', engines_help), - } - - # no aliases or flags for parent App - aliases = Dict() - flags = Dict() - - def start(self): - if self.subapp is None: - print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())) - print() - self.print_description() - self.print_subcommands() - self.exit(1) - else: - return self.subapp.start() - -launch_new_instance = IPClusterApp.launch_instance - -if __name__ == '__main__': - launch_new_instance() - diff --git a/ipython_parallel/apps/ipcontrollerapp.py b/ipython_parallel/apps/ipcontrollerapp.py deleted file mode 100755 index c99e5eb..0000000 --- a/ipython_parallel/apps/ipcontrollerapp.py +++ /dev/null @@ -1,548 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -""" -The IPython controller application. - -Authors: - -* Brian Granger -* MinRK - -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -from __future__ import with_statement - -import json -import os -import stat -import sys - -from multiprocessing import Process -from signal import signal, SIGINT, SIGABRT, SIGTERM - -import zmq -from zmq.devices import ProcessMonitoredQueue -from zmq.log.handlers import PUBHandler - -from IPython.core.profiledir import ProfileDir - -from ipython_parallel.apps.baseapp import ( - BaseParallelApplication, - base_aliases, - base_flags, - catch_config_error, -) -from IPython.utils.importstring import import_item -from IPython.utils.localinterfaces import localhost, public_ips -from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict, TraitError - -from IPython.kernel.zmq.session import ( - Session, session_aliases, session_flags, -) - -from ipython_parallel.controller.heartmonitor import HeartMonitor -from ipython_parallel.controller.hub import HubFactory -from ipython_parallel.controller.scheduler import TaskScheduler,launch_scheduler -from ipython_parallel.controller.dictdb import DictDB - -from ipython_parallel.util import split_url, disambiguate_url, set_hwm - -# conditional import of SQLiteDB / MongoDB backend class -real_dbs = [] - -try: - from ipython_parallel.controller.sqlitedb import SQLiteDB -except ImportError: - pass -else: - real_dbs.append(SQLiteDB) - -try: - from ipython_parallel.controller.mongodb import MongoDB -except ImportError: - pass -else: - real_dbs.append(MongoDB) - - - -#----------------------------------------------------------------------------- -# Module level variables -#----------------------------------------------------------------------------- - - -_description = """Start the IPython controller for parallel computing. - -The IPython controller provides a gateway between the IPython engines and -clients. The controller needs to be started before the engines and can be -configured using command line options or using a cluster directory. Cluster -directories contain config, log and security files and are usually located in -your ipython directory and named as "profile_name". See the `profile` -and `profile-dir` options for details. -""" - -_examples = """ -ipcontroller --ip=192.168.0.1 --port=1000 # listen on ip, port for engines -ipcontroller --scheme=pure # use the pure zeromq scheduler -""" - - -#----------------------------------------------------------------------------- -# The main application -#----------------------------------------------------------------------------- -flags = {} -flags.update(base_flags) -flags.update({ - 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}}, - 'Use threads instead of processes for the schedulers'), - 'sqlitedb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.sqlitedb.SQLiteDB'}}, - 'use the SQLiteDB backend'), - 'mongodb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.mongodb.MongoDB'}}, - 'use the MongoDB backend'), - 'dictdb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.dictdb.DictDB'}}, - 'use the in-memory DictDB backend'), - 'nodb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.dictdb.NoDB'}}, - """use dummy DB backend, which doesn't store any information. - - This is the default as of IPython 0.13. - - To enable delayed or repeated retrieval of results from the Hub, - select one of the true db backends. - """), - 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}}, - 'reuse existing json connection files'), - 'restore' : ({'IPControllerApp' : {'restore_engines' : True, 'reuse_files' : True}}, - 'Attempt to restore engines from a JSON file. ' - 'For use when resuming a crashed controller'), -}) - -flags.update(session_flags) - -aliases = dict( - ssh = 'IPControllerApp.ssh_server', - enginessh = 'IPControllerApp.engine_ssh_server', - location = 'IPControllerApp.location', - - url = 'HubFactory.url', - ip = 'HubFactory.ip', - transport = 'HubFactory.transport', - port = 'HubFactory.regport', - - ping = 'HeartMonitor.period', - - scheme = 'TaskScheduler.scheme_name', - hwm = 'TaskScheduler.hwm', -) -aliases.update(base_aliases) -aliases.update(session_aliases) - -class IPControllerApp(BaseParallelApplication): - - name = u'ipcontroller' - description = _description - examples = _examples - classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, DictDB] + real_dbs - - # change default to True - auto_create = Bool(True, config=True, - help="""Whether to create profile dir if it doesn't exist.""") - - reuse_files = Bool(False, config=True, - help="""Whether to reuse existing json connection files. - If False, connection files will be removed on a clean exit. - """ - ) - restore_engines = Bool(False, config=True, - help="""Reload engine state from JSON file - """ - ) - ssh_server = Unicode(u'', config=True, - help="""ssh url for clients to use when connecting to the Controller - processes. It should be of the form: [user@]server[:port]. The - Controller's listening addresses must be accessible from the ssh server""", - ) - engine_ssh_server = Unicode(u'', config=True, - help="""ssh url for engines to use when connecting to the Controller - processes. It should be of the form: [user@]server[:port]. The - Controller's listening addresses must be accessible from the ssh server""", - ) - location = Unicode(u'', config=True, - help="""The external IP or domain name of the Controller, used for disambiguating - engine and client connections.""", - ) - import_statements = List([], config=True, - help="import statements to be run at startup. Necessary in some environments" - ) - - use_threads = Bool(False, config=True, - help='Use threads instead of processes for the schedulers', - ) - - engine_json_file = Unicode('ipcontroller-engine.json', config=True, - help="JSON filename where engine connection info will be stored.") - client_json_file = Unicode('ipcontroller-client.json', config=True, - help="JSON filename where client connection info will be stored.") - - def _cluster_id_changed(self, name, old, new): - super(IPControllerApp, self)._cluster_id_changed(name, old, new) - self.engine_json_file = "%s-engine.json" % self.name - self.client_json_file = "%s-client.json" % self.name - - - # internal - children = List() - mq_class = Unicode('zmq.devices.ProcessMonitoredQueue') - - def _use_threads_changed(self, name, old, new): - self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process') - - write_connection_files = Bool(True, - help="""Whether to write connection files to disk. - True in all cases other than runs with `reuse_files=True` *after the first* - """ - ) - - aliases = Dict(aliases) - flags = Dict(flags) - - - def save_connection_dict(self, fname, cdict): - """save a connection dict to json file.""" - c = self.config - url = cdict['registration'] - location = cdict['location'] - - if not location: - if public_ips(): - location = public_ips()[-1] - else: - self.log.warn("Could not identify this machine's IP, assuming %s." - " You may need to specify '--location=' to help" - " IPython decide when to connect via loopback." % localhost() ) - location = localhost() - cdict['location'] = location - fname = os.path.join(self.profile_dir.security_dir, fname) - self.log.info("writing connection info to %s", fname) - with open(fname, 'w') as f: - f.write(json.dumps(cdict, indent=2)) - os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR) - - def load_config_from_json(self): - """load config from existing json connector files.""" - c = self.config - self.log.debug("loading config from JSON") - - # load engine config - - fname = os.path.join(self.profile_dir.security_dir, self.engine_json_file) - self.log.info("loading connection info from %s", fname) - with open(fname) as f: - ecfg = json.loads(f.read()) - - # json gives unicode, Session.key wants bytes - c.Session.key = ecfg['key'].encode('ascii') - - xport,ip = ecfg['interface'].split('://') - - c.HubFactory.engine_ip = ip - c.HubFactory.engine_transport = xport - - self.location = ecfg['location'] - if not self.engine_ssh_server: - self.engine_ssh_server = ecfg['ssh'] - - # load client config - - fname = os.path.join(self.profile_dir.security_dir, self.client_json_file) - self.log.info("loading connection info from %s", fname) - with open(fname) as f: - ccfg = json.loads(f.read()) - - for key in ('key', 'registration', 'pack', 'unpack', 'signature_scheme'): - assert ccfg[key] == ecfg[key], "mismatch between engine and client info: %r" % key - - xport,addr = ccfg['interface'].split('://') - - c.HubFactory.client_transport = xport - c.HubFactory.client_ip = ip - if not self.ssh_server: - self.ssh_server = ccfg['ssh'] - - # load port config: - c.HubFactory.regport = ecfg['registration'] - c.HubFactory.hb = (ecfg['hb_ping'], ecfg['hb_pong']) - c.HubFactory.control = (ccfg['control'], ecfg['control']) - c.HubFactory.mux = (ccfg['mux'], ecfg['mux']) - c.HubFactory.task = (ccfg['task'], ecfg['task']) - c.HubFactory.iopub = (ccfg['iopub'], ecfg['iopub']) - c.HubFactory.notifier_port = ccfg['notification'] - - def cleanup_connection_files(self): - if self.reuse_files: - self.log.debug("leaving JSON connection files for reuse") - return - self.log.debug("cleaning up JSON connection files") - for f in (self.client_json_file, self.engine_json_file): - f = os.path.join(self.profile_dir.security_dir, f) - try: - os.remove(f) - except Exception as e: - self.log.error("Failed to cleanup connection file: %s", e) - else: - self.log.debug(u"removed %s", f) - - def load_secondary_config(self): - """secondary config, loading from JSON and setting defaults""" - if self.reuse_files: - try: - self.load_config_from_json() - except (AssertionError,IOError) as e: - self.log.error("Could not load config from JSON: %s" % e) - else: - # successfully loaded config from JSON, and reuse=True - # no need to wite back the same file - self.write_connection_files = False - - self.log.debug("Config changed") - self.log.debug(repr(self.config)) - - def init_hub(self): - c = self.config - - self.do_import_statements() - - try: - self.factory = HubFactory(config=c, log=self.log) - # self.start_logging() - self.factory.init_hub() - except TraitError: - raise - except Exception: - self.log.error("Couldn't construct the Controller", exc_info=True) - self.exit(1) - - if self.write_connection_files: - # save to new json config files - f = self.factory - base = { - 'key' : f.session.key.decode('ascii'), - 'location' : self.location, - 'pack' : f.session.packer, - 'unpack' : f.session.unpacker, - 'signature_scheme' : f.session.signature_scheme, - } - - cdict = {'ssh' : self.ssh_server} - cdict.update(f.client_info) - cdict.update(base) - self.save_connection_dict(self.client_json_file, cdict) - - edict = {'ssh' : self.engine_ssh_server} - edict.update(f.engine_info) - edict.update(base) - self.save_connection_dict(self.engine_json_file, edict) - - fname = "engines%s.json" % self.cluster_id - self.factory.hub.engine_state_file = os.path.join(self.profile_dir.log_dir, fname) - if self.restore_engines: - self.factory.hub._load_engine_state() - # load key into config so other sessions in this process (TaskScheduler) - # have the same value - self.config.Session.key = self.factory.session.key - - def init_schedulers(self): - children = self.children - mq = import_item(str(self.mq_class)) - - f = self.factory - ident = f.session.bsession - # disambiguate url, in case of * - monitor_url = disambiguate_url(f.monitor_url) - # maybe_inproc = 'inproc://monitor' if self.use_threads else monitor_url - # IOPub relay (in a Process) - q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub') - q.bind_in(f.client_url('iopub')) - q.setsockopt_in(zmq.IDENTITY, ident + b"_iopub") - q.bind_out(f.engine_url('iopub')) - q.setsockopt_out(zmq.SUBSCRIBE, b'') - q.connect_mon(monitor_url) - q.daemon=True - children.append(q) - - # Multiplexer Queue (in a Process) - q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'in', b'out') - - q.bind_in(f.client_url('mux')) - q.setsockopt_in(zmq.IDENTITY, b'mux_in') - q.bind_out(f.engine_url('mux')) - q.setsockopt_out(zmq.IDENTITY, b'mux_out') - q.connect_mon(monitor_url) - q.daemon=True - children.append(q) - - # Control Queue (in a Process) - q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'incontrol', b'outcontrol') - q.bind_in(f.client_url('control')) - q.setsockopt_in(zmq.IDENTITY, b'control_in') - q.bind_out(f.engine_url('control')) - q.setsockopt_out(zmq.IDENTITY, b'control_out') - q.connect_mon(monitor_url) - q.daemon=True - children.append(q) - if 'TaskScheduler.scheme_name' in self.config: - scheme = self.config.TaskScheduler.scheme_name - else: - scheme = TaskScheduler.scheme_name.get_default_value() - # Task Queue (in a Process) - if scheme == 'pure': - self.log.warn("task::using pure DEALER Task scheduler") - q = mq(zmq.ROUTER, zmq.DEALER, zmq.PUB, b'intask', b'outtask') - # q.setsockopt_out(zmq.HWM, hub.hwm) - q.bind_in(f.client_url('task')) - q.setsockopt_in(zmq.IDENTITY, b'task_in') - q.bind_out(f.engine_url('task')) - q.setsockopt_out(zmq.IDENTITY, b'task_out') - q.connect_mon(monitor_url) - q.daemon=True - children.append(q) - elif scheme == 'none': - self.log.warn("task::using no Task scheduler") - - else: - self.log.info("task::using Python %s Task scheduler"%scheme) - sargs = (f.client_url('task'), f.engine_url('task'), - monitor_url, disambiguate_url(f.client_url('notification')), - disambiguate_url(f.client_url('registration')), - ) - kwargs = dict(logname='scheduler', loglevel=self.log_level, - log_url = self.log_url, config=dict(self.config)) - if 'Process' in self.mq_class: - # run the Python scheduler in a Process - q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs) - q.daemon=True - children.append(q) - else: - # single-threaded Controller - kwargs['in_thread'] = True - launch_scheduler(*sargs, **kwargs) - - # set unlimited HWM for all relay devices - if hasattr(zmq, 'SNDHWM'): - q = children[0] - q.setsockopt_in(zmq.RCVHWM, 0) - q.setsockopt_out(zmq.SNDHWM, 0) - - for q in children[1:]: - if not hasattr(q, 'setsockopt_in'): - continue - q.setsockopt_in(zmq.SNDHWM, 0) - q.setsockopt_in(zmq.RCVHWM, 0) - q.setsockopt_out(zmq.SNDHWM, 0) - q.setsockopt_out(zmq.RCVHWM, 0) - q.setsockopt_mon(zmq.SNDHWM, 0) - - - def terminate_children(self): - child_procs = [] - for child in self.children: - if isinstance(child, ProcessMonitoredQueue): - child_procs.append(child.launcher) - elif isinstance(child, Process): - child_procs.append(child) - if child_procs: - self.log.critical("terminating children...") - for child in child_procs: - try: - child.terminate() - except OSError: - # already dead - pass - - def handle_signal(self, sig, frame): - self.log.critical("Received signal %i, shutting down", sig) - self.terminate_children() - self.loop.stop() - - def init_signal(self): - for sig in (SIGINT, SIGABRT, SIGTERM): - signal(sig, self.handle_signal) - - def do_import_statements(self): - statements = self.import_statements - for s in statements: - try: - self.log.msg("Executing statement: '%s'" % s) - exec(s, globals(), locals()) - except: - self.log.msg("Error running statement: %s" % s) - - def forward_logging(self): - if self.log_url: - self.log.info("Forwarding logging to %s"%self.log_url) - context = zmq.Context.instance() - lsock = context.socket(zmq.PUB) - lsock.connect(self.log_url) - handler = PUBHandler(lsock) - handler.root_topic = 'controller' - handler.setLevel(self.log_level) - self.log.addHandler(handler) - - @catch_config_error - def initialize(self, argv=None): - super(IPControllerApp, self).initialize(argv) - self.forward_logging() - self.load_secondary_config() - self.init_hub() - self.init_schedulers() - - def start(self): - # Start the subprocesses: - self.factory.start() - # children must be started before signals are setup, - # otherwise signal-handling will fire multiple times - for child in self.children: - child.start() - self.init_signal() - - self.write_pid_file(overwrite=True) - - try: - self.factory.loop.start() - except KeyboardInterrupt: - self.log.critical("Interrupted, Exiting...\n") - finally: - self.cleanup_connection_files() - - -def launch_new_instance(*args, **kwargs): - """Create and run the IPython controller""" - if sys.platform == 'win32': - # make sure we don't get called from a multiprocessing subprocess - # this can result in infinite Controllers being started on Windows - # which doesn't have a proper fork, so multiprocessing is wonky - - # this only comes up when IPython has been installed using vanilla - # setuptools, and *not* distribute. - import multiprocessing - p = multiprocessing.current_process() - # the main process has name 'MainProcess' - # subprocesses will have names like 'Process-1' - if p.name != 'MainProcess': - # we are a subprocess, don't start another Controller! - return - return IPControllerApp.launch_instance(*args, **kwargs) - - -if __name__ == '__main__': - launch_new_instance() diff --git a/ipython_parallel/apps/ipengineapp.py b/ipython_parallel/apps/ipengineapp.py deleted file mode 100755 index 3a9ff48..0000000 --- a/ipython_parallel/apps/ipengineapp.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -""" -The IPython engine application - -Authors: - -* Brian Granger -* MinRK - -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import json -import os -import sys -import time - -import zmq -from zmq.eventloop import ioloop - -from IPython.core.profiledir import ProfileDir -from ipython_parallel.apps.baseapp import ( - BaseParallelApplication, - base_aliases, - base_flags, - catch_config_error, -) -from IPython.kernel.zmq.log import EnginePUBHandler -from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel -from IPython.kernel.zmq.kernelapp import IPKernelApp -from IPython.kernel.zmq.session import ( - Session, session_aliases, session_flags -) -from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell - -from IPython.config.configurable import Configurable - -from ipython_parallel.engine.engine import EngineFactory -from ipython_parallel.util import disambiguate_ip_address - -from IPython.utils.importstring import import_item -from IPython.utils.py3compat import cast_bytes -from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float, Instance - - -#----------------------------------------------------------------------------- -# Module level variables -#----------------------------------------------------------------------------- - -_description = """Start an IPython engine for parallel computing. - -IPython engines run in parallel and perform computations on behalf of a client -and controller. A controller needs to be started before the engines. The -engine can be configured using command line options or using a cluster -directory. Cluster directories contain config, log and security files and are -usually located in your ipython directory and named as "profile_name". -See the `profile` and `profile-dir` options for details. -""" - -_examples = """ -ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port -ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity -""" - -#----------------------------------------------------------------------------- -# MPI configuration -#----------------------------------------------------------------------------- - -mpi4py_init = """from mpi4py import MPI as mpi -mpi.size = mpi.COMM_WORLD.Get_size() -mpi.rank = mpi.COMM_WORLD.Get_rank() -""" - - -pytrilinos_init = """from PyTrilinos import Epetra -class SimpleStruct: -pass -mpi = SimpleStruct() -mpi.rank = 0 -mpi.size = 0 -""" - -class MPI(Configurable): - """Configurable for MPI initialization""" - use = Unicode('', config=True, - help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).' - ) - - def _use_changed(self, name, old, new): - # load default init script if it's not set - if not self.init_script: - self.init_script = self.default_inits.get(new, '') - - init_script = Unicode('', config=True, - help="Initialization code for MPI") - - default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init}, - config=True) - - -#----------------------------------------------------------------------------- -# Main application -#----------------------------------------------------------------------------- -aliases = dict( - file = 'IPEngineApp.url_file', - c = 'IPEngineApp.startup_command', - s = 'IPEngineApp.startup_script', - - url = 'EngineFactory.url', - ssh = 'EngineFactory.sshserver', - sshkey = 'EngineFactory.sshkey', - ip = 'EngineFactory.ip', - transport = 'EngineFactory.transport', - port = 'EngineFactory.regport', - location = 'EngineFactory.location', - - timeout = 'EngineFactory.timeout', - - mpi = 'MPI.use', - -) -aliases.update(base_aliases) -aliases.update(session_aliases) -flags = {} -flags.update(base_flags) -flags.update(session_flags) - -class IPEngineApp(BaseParallelApplication): - - name = 'ipengine' - description = _description - examples = _examples - classes = List([ZMQInteractiveShell, ProfileDir, Session, EngineFactory, Kernel, MPI]) - - startup_script = Unicode(u'', config=True, - help='specify a script to be run at startup') - startup_command = Unicode('', config=True, - help='specify a command to be run at startup') - - url_file = Unicode(u'', config=True, - help="""The full location of the file containing the connection information for - the controller. If this is not given, the file must be in the - security directory of the cluster directory. This location is - resolved using the `profile` or `profile_dir` options.""", - ) - wait_for_url_file = Float(5, config=True, - help="""The maximum number of seconds to wait for url_file to exist. - This is useful for batch-systems and shared-filesystems where the - controller and engine are started at the same time and it - may take a moment for the controller to write the connector files.""") - - url_file_name = Unicode(u'ipcontroller-engine.json', config=True) - - def _cluster_id_changed(self, name, old, new): - if new: - base = 'ipcontroller-%s' % new - else: - base = 'ipcontroller' - self.url_file_name = "%s-engine.json" % base - - log_url = Unicode('', config=True, - help="""The URL for the iploggerapp instance, for forwarding - logging to a central location.""") - - # an IPKernelApp instance, used to setup listening for shell frontends - kernel_app = Instance(IPKernelApp, allow_none=True) - - aliases = Dict(aliases) - flags = Dict(flags) - - @property - def kernel(self): - """allow access to the Kernel object, so I look like IPKernelApp""" - return self.engine.kernel - - def find_url_file(self): - """Set the url file. - - Here we don't try to actually see if it exists for is valid as that - is hadled by the connection logic. - """ - config = self.config - # Find the actual controller key file - if not self.url_file: - self.url_file = os.path.join( - self.profile_dir.security_dir, - self.url_file_name - ) - - def load_connector_file(self): - """load config from a JSON connector file, - at a *lower* priority than command-line/config files. - """ - - self.log.info("Loading url_file %r", self.url_file) - config = self.config - - with open(self.url_file) as f: - num_tries = 0 - max_tries = 5 - d = "" - while not d: - try: - d = json.loads(f.read()) - except ValueError: - if num_tries > max_tries: - raise - num_tries += 1 - time.sleep(0.5) - - # allow hand-override of location for disambiguation - # and ssh-server - if 'EngineFactory.location' not in config: - config.EngineFactory.location = d['location'] - if 'EngineFactory.sshserver' not in config: - config.EngineFactory.sshserver = d.get('ssh') - - location = config.EngineFactory.location - - proto, ip = d['interface'].split('://') - ip = disambiguate_ip_address(ip, location) - d['interface'] = '%s://%s' % (proto, ip) - - # DO NOT allow override of basic URLs, serialization, or key - # JSON file takes top priority there - config.Session.key = cast_bytes(d['key']) - config.Session.signature_scheme = d['signature_scheme'] - - config.EngineFactory.url = d['interface'] + ':%i' % d['registration'] - - config.Session.packer = d['pack'] - config.Session.unpacker = d['unpack'] - - self.log.debug("Config changed:") - self.log.debug("%r", config) - self.connection_info = d - - def bind_kernel(self, **kwargs): - """Promote engine to listening kernel, accessible to frontends.""" - if self.kernel_app is not None: - return - - self.log.info("Opening ports for direct connections as an IPython kernel") - - kernel = self.kernel - - kwargs.setdefault('config', self.config) - kwargs.setdefault('log', self.log) - kwargs.setdefault('profile_dir', self.profile_dir) - kwargs.setdefault('session', self.engine.session) - - app = self.kernel_app = IPKernelApp(**kwargs) - - # allow IPKernelApp.instance(): - IPKernelApp._instance = app - - app.init_connection_file() - # relevant contents of init_sockets: - - app.shell_port = app._bind_socket(kernel.shell_streams[0], app.shell_port) - app.log.debug("shell ROUTER Channel on port: %i", app.shell_port) - - app.iopub_port = app._bind_socket(kernel.iopub_socket, app.iopub_port) - app.log.debug("iopub PUB Channel on port: %i", app.iopub_port) - - kernel.stdin_socket = self.engine.context.socket(zmq.ROUTER) - app.stdin_port = app._bind_socket(kernel.stdin_socket, app.stdin_port) - app.log.debug("stdin ROUTER Channel on port: %i", app.stdin_port) - - # start the heartbeat, and log connection info: - - app.init_heartbeat() - - app.log_connection_info() - app.write_connection_file() - - - def init_engine(self): - # This is the working dir by now. - sys.path.insert(0, '') - config = self.config - # print config - self.find_url_file() - - # was the url manually specified? - keys = set(self.config.EngineFactory.keys()) - keys = keys.union(set(self.config.RegistrationFactory.keys())) - - if keys.intersection(set(['ip', 'url', 'port'])): - # Connection info was specified, don't wait for the file - url_specified = True - self.wait_for_url_file = 0 - else: - url_specified = False - - if self.wait_for_url_file and not os.path.exists(self.url_file): - self.log.warn("url_file %r not found", self.url_file) - self.log.warn("Waiting up to %.1f seconds for it to arrive.", self.wait_for_url_file) - tic = time.time() - while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file): - # wait for url_file to exist, or until time limit - time.sleep(0.1) - - if os.path.exists(self.url_file): - self.load_connector_file() - elif not url_specified: - self.log.fatal("Fatal: url file never arrived: %s", self.url_file) - self.exit(1) - - exec_lines = [] - for app in ('IPKernelApp', 'InteractiveShellApp'): - if '%s.exec_lines' % app in config: - exec_lines = config[app].exec_lines - break - - exec_files = [] - for app in ('IPKernelApp', 'InteractiveShellApp'): - if '%s.exec_files' % app in config: - exec_files = config[app].exec_files - break - - config.IPKernelApp.exec_lines = exec_lines - config.IPKernelApp.exec_files = exec_files - - if self.startup_script: - exec_files.append(self.startup_script) - if self.startup_command: - exec_lines.append(self.startup_command) - - # Create the underlying shell class and Engine - # shell_class = import_item(self.master_config.Global.shell_class) - # print self.config - try: - self.engine = EngineFactory(config=config, log=self.log, - connection_info=self.connection_info, - ) - except: - self.log.error("Couldn't start the Engine", exc_info=True) - self.exit(1) - - def forward_logging(self): - if self.log_url: - self.log.info("Forwarding logging to %s", self.log_url) - context = self.engine.context - lsock = context.socket(zmq.PUB) - lsock.connect(self.log_url) - handler = EnginePUBHandler(self.engine, lsock) - handler.setLevel(self.log_level) - self.log.addHandler(handler) - - def init_mpi(self): - global mpi - self.mpi = MPI(parent=self) - - mpi_import_statement = self.mpi.init_script - if mpi_import_statement: - try: - self.log.info("Initializing MPI:") - self.log.info(mpi_import_statement) - exec(mpi_import_statement, globals()) - except: - mpi = None - else: - mpi = None - - @catch_config_error - def initialize(self, argv=None): - super(IPEngineApp, self).initialize(argv) - self.init_mpi() - self.init_engine() - self.forward_logging() - - def start(self): - self.engine.start() - try: - self.engine.loop.start() - except KeyboardInterrupt: - self.log.critical("Engine Interrupted, shutting down...\n") - - -launch_new_instance = IPEngineApp.launch_instance - - -if __name__ == '__main__': - launch_new_instance() - diff --git a/ipython_parallel/apps/iploggerapp.py b/ipython_parallel/apps/iploggerapp.py deleted file mode 100755 index 411896d..0000000 --- a/ipython_parallel/apps/iploggerapp.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -""" -A simple IPython logger application - -Authors: - -* MinRK - -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import sys - -import zmq - -from IPython.core.profiledir import ProfileDir -from IPython.utils.traitlets import Bool, Dict, Unicode - -from ipython_parallel.apps.baseapp import ( - BaseParallelApplication, - base_aliases, - catch_config_error, -) -from ipython_parallel.apps.logwatcher import LogWatcher - -#----------------------------------------------------------------------------- -# Module level variables -#----------------------------------------------------------------------------- - -#: The default config file name for this application -_description = """Start an IPython logger for parallel computing. - -IPython controllers and engines (and your own processes) can broadcast log messages -by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The -logger can be configured using command line options or using a cluster -directory. Cluster directories contain config, log and security files and are -usually located in your ipython directory and named as "profile_name". -See the `profile` and `profile-dir` options for details. -""" - - -#----------------------------------------------------------------------------- -# Main application -#----------------------------------------------------------------------------- -aliases = {} -aliases.update(base_aliases) -aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics')) - -class IPLoggerApp(BaseParallelApplication): - - name = u'iplogger' - description = _description - classes = [LogWatcher, ProfileDir] - aliases = Dict(aliases) - - @catch_config_error - def initialize(self, argv=None): - super(IPLoggerApp, self).initialize(argv) - self.init_watcher() - - def init_watcher(self): - try: - self.watcher = LogWatcher(parent=self, log=self.log) - except: - self.log.error("Couldn't start the LogWatcher", exc_info=True) - self.exit(1) - self.log.info("Listening for log messages on %r"%self.watcher.url) - - - def start(self): - self.watcher.start() - try: - self.watcher.loop.start() - except KeyboardInterrupt: - self.log.critical("Logging Interrupted, shutting down...\n") - - -launch_new_instance = IPLoggerApp.launch_instance - - -if __name__ == '__main__': - launch_new_instance() - diff --git a/ipython_parallel/apps/launcher.py b/ipython_parallel/apps/launcher.py deleted file mode 100644 index 982eeb6..0000000 --- a/ipython_parallel/apps/launcher.py +++ /dev/null @@ -1,1445 +0,0 @@ -# encoding: utf-8 -"""Facilities for launching IPython processes asynchronously.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import copy -import logging -import os -import pipes -import stat -import sys -import time - -# signal imports, handling various platforms, versions - -from signal import SIGINT, SIGTERM -try: - from signal import SIGKILL -except ImportError: - # Windows - SIGKILL=SIGTERM - -try: - # Windows >= 2.7, 3.2 - from signal import CTRL_C_EVENT as SIGINT -except ImportError: - pass - -from subprocess import Popen, PIPE, STDOUT -try: - from subprocess import check_output -except ImportError: - # pre-2.7, define check_output with Popen - def check_output(*args, **kwargs): - kwargs.update(dict(stdout=PIPE)) - p = Popen(*args, **kwargs) - out,err = p.communicate() - return out - -from zmq.eventloop import ioloop - -from IPython.config.application import Application -from IPython.config.configurable import LoggingConfigurable -from IPython.utils.text import EvalFormatter -from IPython.utils.traitlets import ( - Any, Integer, CFloat, List, Unicode, Dict, Instance, HasTraits, CRegExp -) -from IPython.utils.encoding import DEFAULT_ENCODING -from IPython.utils.path import get_home_dir, ensure_dir_exists -from IPython.utils.process import find_cmd, FindCmdError -from IPython.utils.py3compat import iteritems, itervalues - -from .win32support import forward_read_events - -from .winhpcjob import IPControllerTask, IPEngineTask, IPControllerJob, IPEngineSetJob - -WINDOWS = os.name == 'nt' - -#----------------------------------------------------------------------------- -# Paths to the kernel apps -#----------------------------------------------------------------------------- - -ipcluster_cmd_argv = [sys.executable, "-m", "ipython_parallel.cluster"] - -ipengine_cmd_argv = [sys.executable, "-m", "ipython_parallel.engine"] - -ipcontroller_cmd_argv = [sys.executable, "-m", "ipython_parallel.controller"] - -if WINDOWS and sys.version_info < (3,): - # `python -m package` doesn't work on Windows Python 2 - # due to weird multiprocessing bugs - # and python -m module puts classes in the `__main__` module, - # so instance checks get confused - ipengine_cmd_argv = [sys.executable, "-c", "from ipython_parallel.engine.__main__ import main; main()"] - ipcontroller_cmd_argv = [sys.executable, "-c", "from ipython_parallel.controller.__main__ import main; main()"] - -#----------------------------------------------------------------------------- -# Base launchers and errors -#----------------------------------------------------------------------------- - -class LauncherError(Exception): - pass - - -class ProcessStateError(LauncherError): - pass - - -class UnknownStatus(LauncherError): - pass - - -class BaseLauncher(LoggingConfigurable): - """An asbtraction for starting, stopping and signaling a process.""" - - # In all of the launchers, the work_dir is where child processes will be - # run. This will usually be the profile_dir, but may not be. any work_dir - # passed into the __init__ method will override the config value. - # This should not be used to set the work_dir for the actual engine - # and controller. Instead, use their own config files or the - # controller_args, engine_args attributes of the launchers to add - # the work_dir option. - work_dir = Unicode(u'.') - loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=True) - - start_data = Any() - stop_data = Any() - - def _loop_default(self): - return ioloop.IOLoop.instance() - - def __init__(self, work_dir=u'.', config=None, **kwargs): - super(BaseLauncher, self).__init__(work_dir=work_dir, config=config, **kwargs) - self.state = 'before' # can be before, running, after - self.stop_callbacks = [] - - @property - def args(self): - """A list of cmd and args that will be used to start the process. - - This is what is passed to :func:`spawnProcess` and the first element - will be the process name. - """ - return self.find_args() - - def find_args(self): - """The ``.args`` property calls this to find the args list. - - Subcommand should implement this to construct the cmd and args. - """ - raise NotImplementedError('find_args must be implemented in a subclass') - - @property - def arg_str(self): - """The string form of the program arguments.""" - return ' '.join(self.args) - - @property - def running(self): - """Am I running.""" - if self.state == 'running': - return True - else: - return False - - def start(self): - """Start the process.""" - raise NotImplementedError('start must be implemented in a subclass') - - def stop(self): - """Stop the process and notify observers of stopping. - - This method will return None immediately. - To observe the actual process stopping, see :meth:`on_stop`. - """ - raise NotImplementedError('stop must be implemented in a subclass') - - def on_stop(self, f): - """Register a callback to be called with this Launcher's stop_data - when the process actually finishes. - """ - if self.state=='after': - return f(self.stop_data) - else: - self.stop_callbacks.append(f) - - def notify_start(self, data): - """Call this to trigger startup actions. - - This logs the process startup and sets the state to 'running'. It is - a pass-through so it can be used as a callback. - """ - - self.log.debug('Process %r started: %r', self.args[0], data) - self.start_data = data - self.state = 'running' - return data - - def notify_stop(self, data): - """Call this to trigger process stop actions. - - This logs the process stopping and sets the state to 'after'. Call - this to trigger callbacks registered via :meth:`on_stop`.""" - - self.log.debug('Process %r stopped: %r', self.args[0], data) - self.stop_data = data - self.state = 'after' - for i in range(len(self.stop_callbacks)): - d = self.stop_callbacks.pop() - d(data) - return data - - def signal(self, sig): - """Signal the process. - - Parameters - ---------- - sig : str or int - 'KILL', 'INT', etc., or any signal number - """ - raise NotImplementedError('signal must be implemented in a subclass') - -class ClusterAppMixin(HasTraits): - """MixIn for cluster args as traits""" - profile_dir=Unicode('') - cluster_id=Unicode('') - - @property - def cluster_args(self): - return ['--profile-dir', self.profile_dir, '--cluster-id', self.cluster_id] - -class ControllerMixin(ClusterAppMixin): - controller_cmd = List(ipcontroller_cmd_argv, config=True, - help="""Popen command to launch ipcontroller.""") - # Command line arguments to ipcontroller. - controller_args = List(['--log-to-file','--log-level=%i' % logging.INFO], config=True, - help="""command-line args to pass to ipcontroller""") - -class EngineMixin(ClusterAppMixin): - engine_cmd = List(ipengine_cmd_argv, config=True, - help="""command to launch the Engine.""") - # Command line arguments for ipengine. - engine_args = List(['--log-to-file','--log-level=%i' % logging.INFO], config=True, - help="command-line arguments to pass to ipengine" - ) - - -#----------------------------------------------------------------------------- -# Local process launchers -#----------------------------------------------------------------------------- - - -class LocalProcessLauncher(BaseLauncher): - """Start and stop an external process in an asynchronous manner. - - This will launch the external process with a working directory of - ``self.work_dir``. - """ - - # This is used to to construct self.args, which is passed to - # spawnProcess. - cmd_and_args = List([]) - poll_frequency = Integer(100) # in ms - - def __init__(self, work_dir=u'.', config=None, **kwargs): - super(LocalProcessLauncher, self).__init__( - work_dir=work_dir, config=config, **kwargs - ) - self.process = None - self.poller = None - - def find_args(self): - return self.cmd_and_args - - def start(self): - self.log.debug("Starting %s: %r", self.__class__.__name__, self.args) - if self.state == 'before': - self.process = Popen(self.args, - stdout=PIPE,stderr=PIPE,stdin=PIPE, - env=os.environ, - cwd=self.work_dir - ) - if WINDOWS: - self.stdout = forward_read_events(self.process.stdout) - self.stderr = forward_read_events(self.process.stderr) - else: - self.stdout = self.process.stdout.fileno() - self.stderr = self.process.stderr.fileno() - self.loop.add_handler(self.stdout, self.handle_stdout, self.loop.READ) - self.loop.add_handler(self.stderr, self.handle_stderr, self.loop.READ) - self.poller = ioloop.PeriodicCallback(self.poll, self.poll_frequency, self.loop) - self.poller.start() - self.notify_start(self.process.pid) - else: - s = 'The process was already started and has state: %r' % self.state - raise ProcessStateError(s) - - def stop(self): - return self.interrupt_then_kill() - - def signal(self, sig): - if self.state == 'running': - if WINDOWS and sig != SIGINT: - # use Windows tree-kill for better child cleanup - check_output(['taskkill', '-pid', str(self.process.pid), '-t', '-f']) - else: - self.process.send_signal(sig) - - def interrupt_then_kill(self, delay=2.0): - """Send INT, wait a delay and then send KILL.""" - try: - self.signal(SIGINT) - except Exception: - self.log.debug("interrupt failed") - pass - self.killer = self.loop.add_timeout(self.loop.time() + delay, lambda : self.signal(SIGKILL)) - - # callbacks, etc: - - def handle_stdout(self, fd, events): - if WINDOWS: - line = self.stdout.recv() - else: - line = self.process.stdout.readline() - # a stopped process will be readable but return empty strings - if line: - self.log.debug(line[:-1]) - else: - self.poll() - - def handle_stderr(self, fd, events): - if WINDOWS: - line = self.stderr.recv() - else: - line = self.process.stderr.readline() - # a stopped process will be readable but return empty strings - if line: - self.log.debug(line[:-1]) - else: - self.poll() - - def poll(self): - status = self.process.poll() - if status is not None: - self.poller.stop() - self.loop.remove_handler(self.stdout) - self.loop.remove_handler(self.stderr) - self.notify_stop(dict(exit_code=status, pid=self.process.pid)) - return status - -class LocalControllerLauncher(LocalProcessLauncher, ControllerMixin): - """Launch a controller as a regular external process.""" - - def find_args(self): - return self.controller_cmd + self.cluster_args + self.controller_args - - def start(self): - """Start the controller by profile_dir.""" - return super(LocalControllerLauncher, self).start() - - -class LocalEngineLauncher(LocalProcessLauncher, EngineMixin): - """Launch a single engine as a regular externall process.""" - - def find_args(self): - return self.engine_cmd + self.cluster_args + self.engine_args - - -class LocalEngineSetLauncher(LocalEngineLauncher): - """Launch a set of engines as regular external processes.""" - - delay = CFloat(0.1, config=True, - help="""delay (in seconds) between starting each engine after the first. - This can help force the engines to get their ids in order, or limit - process flood when starting many engines.""" - ) - - # launcher class - launcher_class = LocalEngineLauncher - - launchers = Dict() - stop_data = Dict() - - def __init__(self, work_dir=u'.', config=None, **kwargs): - super(LocalEngineSetLauncher, self).__init__( - work_dir=work_dir, config=config, **kwargs - ) - - def start(self, n): - """Start n engines by profile or profile_dir.""" - dlist = [] - for i in range(n): - if i > 0: - time.sleep(self.delay) - el = self.launcher_class(work_dir=self.work_dir, parent=self, log=self.log, - profile_dir=self.profile_dir, cluster_id=self.cluster_id, - ) - - # Copy the engine args over to each engine launcher. - el.engine_cmd = copy.deepcopy(self.engine_cmd) - el.engine_args = copy.deepcopy(self.engine_args) - el.on_stop(self._notice_engine_stopped) - d = el.start() - self.launchers[i] = el - dlist.append(d) - self.notify_start(dlist) - return dlist - - def find_args(self): - return ['engine set'] - - def signal(self, sig): - dlist = [] - for el in itervalues(self.launchers): - d = el.signal(sig) - dlist.append(d) - return dlist - - def interrupt_then_kill(self, delay=1.0): - dlist = [] - for el in itervalues(self.launchers): - d = el.interrupt_then_kill(delay) - dlist.append(d) - return dlist - - def stop(self): - return self.interrupt_then_kill() - - def _notice_engine_stopped(self, data): - pid = data['pid'] - for idx,el in iteritems(self.launchers): - if el.process.pid == pid: - break - self.launchers.pop(idx) - self.stop_data[idx] = data - if not self.launchers: - self.notify_stop(self.stop_data) - - -#----------------------------------------------------------------------------- -# MPI launchers -#----------------------------------------------------------------------------- - - -class MPILauncher(LocalProcessLauncher): - """Launch an external process using mpiexec.""" - - mpi_cmd = List(['mpiexec'], config=True, - help="The mpiexec command to use in starting the process." - ) - mpi_args = List([], config=True, - help="The command line arguments to pass to mpiexec." - ) - program = List(['date'], - help="The program to start via mpiexec.") - program_args = List([], - help="The command line argument to the program." - ) - n = Integer(1) - - def __init__(self, *args, **kwargs): - # deprecation for old MPIExec names: - config = kwargs.get('config', {}) - for oldname in ('MPIExecLauncher', 'MPIExecControllerLauncher', 'MPIExecEngineSetLauncher'): - deprecated = config.get(oldname) - if deprecated: - newname = oldname.replace('MPIExec', 'MPI') - config[newname].update(deprecated) - self.log.warn("WARNING: %s name has been deprecated, use %s", oldname, newname) - - super(MPILauncher, self).__init__(*args, **kwargs) - - def find_args(self): - """Build self.args using all the fields.""" - return self.mpi_cmd + ['-n', str(self.n)] + self.mpi_args + \ - self.program + self.program_args - - def start(self, n): - """Start n instances of the program using mpiexec.""" - self.n = n - return super(MPILauncher, self).start() - - -class MPIControllerLauncher(MPILauncher, ControllerMixin): - """Launch a controller using mpiexec.""" - - # alias back to *non-configurable* program[_args] for use in find_args() - # this way all Controller/EngineSetLaunchers have the same form, rather - # than *some* having `program_args` and others `controller_args` - @property - def program(self): - return self.controller_cmd - - @property - def program_args(self): - return self.cluster_args + self.controller_args - - def start(self): - """Start the controller by profile_dir.""" - return super(MPIControllerLauncher, self).start(1) - - -class MPIEngineSetLauncher(MPILauncher, EngineMixin): - """Launch engines using mpiexec""" - - # alias back to *non-configurable* program[_args] for use in find_args() - # this way all Controller/EngineSetLaunchers have the same form, rather - # than *some* having `program_args` and others `controller_args` - @property - def program(self): - return self.engine_cmd - - @property - def program_args(self): - return self.cluster_args + self.engine_args - - def start(self, n): - """Start n engines by profile or profile_dir.""" - self.n = n - return super(MPIEngineSetLauncher, self).start(n) - -# deprecated MPIExec names -class DeprecatedMPILauncher(object): - def warn(self): - oldname = self.__class__.__name__ - newname = oldname.replace('MPIExec', 'MPI') - self.log.warn("WARNING: %s name is deprecated, use %s", oldname, newname) - -class MPIExecLauncher(MPILauncher, DeprecatedMPILauncher): - """Deprecated, use MPILauncher""" - def __init__(self, *args, **kwargs): - super(MPIExecLauncher, self).__init__(*args, **kwargs) - self.warn() - -class MPIExecControllerLauncher(MPIControllerLauncher, DeprecatedMPILauncher): - """Deprecated, use MPIControllerLauncher""" - def __init__(self, *args, **kwargs): - super(MPIExecControllerLauncher, self).__init__(*args, **kwargs) - self.warn() - -class MPIExecEngineSetLauncher(MPIEngineSetLauncher, DeprecatedMPILauncher): - """Deprecated, use MPIEngineSetLauncher""" - def __init__(self, *args, **kwargs): - super(MPIExecEngineSetLauncher, self).__init__(*args, **kwargs) - self.warn() - - -#----------------------------------------------------------------------------- -# SSH launchers -#----------------------------------------------------------------------------- - -# TODO: Get SSH Launcher back to level of sshx in 0.10.2 - -class SSHLauncher(LocalProcessLauncher): - """A minimal launcher for ssh. - - To be useful this will probably have to be extended to use the ``sshx`` - idea for environment variables. There could be other things this needs - as well. - """ - - ssh_cmd = List(['ssh'], config=True, - help="command for starting ssh") - ssh_args = List(['-tt'], config=True, - help="args to pass to ssh") - scp_cmd = List(['scp'], config=True, - help="command for sending files") - program = List(['date'], - help="Program to launch via ssh") - program_args = List([], - help="args to pass to remote program") - hostname = Unicode('', config=True, - help="hostname on which to launch the program") - user = Unicode('', config=True, - help="username for ssh") - location = Unicode('', config=True, - help="user@hostname location for ssh in one setting") - to_fetch = List([], config=True, - help="List of (remote, local) files to fetch after starting") - to_send = List([], config=True, - help="List of (local, remote) files to send before starting") - - def _hostname_changed(self, name, old, new): - if self.user: - self.location = u'%s@%s' % (self.user, new) - else: - self.location = new - - def _user_changed(self, name, old, new): - self.location = u'%s@%s' % (new, self.hostname) - - def find_args(self): - return self.ssh_cmd + self.ssh_args + [self.location] + \ - list(map(pipes.quote, self.program + self.program_args)) - - def _send_file(self, local, remote): - """send a single file""" - full_remote = "%s:%s" % (self.location, remote) - for i in range(10): - if not os.path.exists(local): - self.log.debug("waiting for %s" % local) - time.sleep(1) - else: - break - remote_dir = os.path.dirname(remote) - self.log.info("ensuring remote %s:%s/ exists", self.location, remote_dir) - check_output(self.ssh_cmd + self.ssh_args + \ - [self.location, 'mkdir', '-p', '--', remote_dir] - ) - self.log.info("sending %s to %s", local, full_remote) - check_output(self.scp_cmd + [local, full_remote]) - - def send_files(self): - """send our files (called before start)""" - if not self.to_send: - return - for local_file, remote_file in self.to_send: - self._send_file(local_file, remote_file) - - def _fetch_file(self, remote, local): - """fetch a single file""" - full_remote = "%s:%s" % (self.location, remote) - self.log.info("fetching %s from %s", local, full_remote) - for i in range(10): - # wait up to 10s for remote file to exist - check = check_output(self.ssh_cmd + self.ssh_args + \ - [self.location, 'test -e', remote, "&& echo 'yes' || echo 'no'"]) - check = check.decode(DEFAULT_ENCODING, 'replace').strip() - if check == u'no': - time.sleep(1) - elif check == u'yes': - break - local_dir = os.path.dirname(local) - ensure_dir_exists(local_dir, 775) - check_output(self.scp_cmd + [full_remote, local]) - - def fetch_files(self): - """fetch remote files (called after start)""" - if not self.to_fetch: - return - for remote_file, local_file in self.to_fetch: - self._fetch_file(remote_file, local_file) - - def start(self, hostname=None, user=None): - if hostname is not None: - self.hostname = hostname - if user is not None: - self.user = user - - self.send_files() - super(SSHLauncher, self).start() - self.fetch_files() - - def signal(self, sig): - if self.state == 'running': - # send escaped ssh connection-closer - self.process.stdin.write('~.') - self.process.stdin.flush() - -class SSHClusterLauncher(SSHLauncher, ClusterAppMixin): - - remote_profile_dir = Unicode('', config=True, - help="""The remote profile_dir to use. - - If not specified, use calling profile, stripping out possible leading homedir. - """) - - def _profile_dir_changed(self, name, old, new): - if not self.remote_profile_dir: - # trigger remote_profile_dir_default logic again, - # in case it was already triggered before profile_dir was set - self.remote_profile_dir = self._strip_home(new) - - @staticmethod - def _strip_home(path): - """turns /home/you/.ipython/profile_foo into .ipython/profile_foo""" - home = get_home_dir() - if not home.endswith('/'): - home = home+'/' - - if path.startswith(home): - return path[len(home):] - else: - return path - - def _remote_profile_dir_default(self): - return self._strip_home(self.profile_dir) - - def _cluster_id_changed(self, name, old, new): - if new: - raise ValueError("cluster id not supported by SSH launchers") - - @property - def cluster_args(self): - return ['--profile-dir', self.remote_profile_dir] - -class SSHControllerLauncher(SSHClusterLauncher, ControllerMixin): - - # alias back to *non-configurable* program[_args] for use in find_args() - # this way all Controller/EngineSetLaunchers have the same form, rather - # than *some* having `program_args` and others `controller_args` - - def _controller_cmd_default(self): - return ['ipcontroller'] - - @property - def program(self): - return self.controller_cmd - - @property - def program_args(self): - return self.cluster_args + self.controller_args - - def _to_fetch_default(self): - return [ - (os.path.join(self.remote_profile_dir, 'security', cf), - os.path.join(self.profile_dir, 'security', cf),) - for cf in ('ipcontroller-client.json', 'ipcontroller-engine.json') - ] - -class SSHEngineLauncher(SSHClusterLauncher, EngineMixin): - - # alias back to *non-configurable* program[_args] for use in find_args() - # this way all Controller/EngineSetLaunchers have the same form, rather - # than *some* having `program_args` and others `controller_args` - - def _engine_cmd_default(self): - return ['ipengine'] - - @property - def program(self): - return self.engine_cmd - - @property - def program_args(self): - return self.cluster_args + self.engine_args - - def _to_send_default(self): - return [ - (os.path.join(self.profile_dir, 'security', cf), - os.path.join(self.remote_profile_dir, 'security', cf)) - for cf in ('ipcontroller-client.json', 'ipcontroller-engine.json') - ] - - -class SSHEngineSetLauncher(LocalEngineSetLauncher): - launcher_class = SSHEngineLauncher - engines = Dict(config=True, - help="""dict of engines to launch. This is a dict by hostname of ints, - corresponding to the number of engines to start on that host.""") - - def _engine_cmd_default(self): - return ['ipengine'] - - @property - def engine_count(self): - """determine engine count from `engines` dict""" - count = 0 - for n in itervalues(self.engines): - if isinstance(n, (tuple,list)): - n,args = n - count += n - return count - - def start(self, n): - """Start engines by profile or profile_dir. - `n` is ignored, and the `engines` config property is used instead. - """ - - dlist = [] - for host, n in iteritems(self.engines): - if isinstance(n, (tuple, list)): - n, args = n - else: - args = copy.deepcopy(self.engine_args) - - if '@' in host: - user,host = host.split('@',1) - else: - user=None - for i in range(n): - if i > 0: - time.sleep(self.delay) - el = self.launcher_class(work_dir=self.work_dir, parent=self, log=self.log, - profile_dir=self.profile_dir, cluster_id=self.cluster_id, - ) - if i > 0: - # only send files for the first engine on each host - el.to_send = [] - - # Copy the engine args over to each engine launcher. - el.engine_cmd = self.engine_cmd - el.engine_args = args - el.on_stop(self._notice_engine_stopped) - d = el.start(user=user, hostname=host) - self.launchers[ "%s/%i" % (host,i) ] = el - dlist.append(d) - self.notify_start(dlist) - return dlist - - -class SSHProxyEngineSetLauncher(SSHClusterLauncher): - """Launcher for calling - `ipcluster engines` on a remote machine. - - Requires that remote profile is already configured. - """ - - n = Integer() - ipcluster_cmd = List(['ipcluster'], config=True) - - @property - def program(self): - return self.ipcluster_cmd + ['engines'] - - @property - def program_args(self): - return ['-n', str(self.n), '--profile-dir', self.remote_profile_dir] - - def _to_send_default(self): - return [ - (os.path.join(self.profile_dir, 'security', cf), - os.path.join(self.remote_profile_dir, 'security', cf)) - for cf in ('ipcontroller-client.json', 'ipcontroller-engine.json') - ] - - def start(self, n): - self.n = n - super(SSHProxyEngineSetLauncher, self).start() - - -#----------------------------------------------------------------------------- -# Windows HPC Server 2008 scheduler launchers -#----------------------------------------------------------------------------- - - -# This is only used on Windows. -def find_job_cmd(): - if WINDOWS: - try: - return find_cmd('job') - except (FindCmdError, ImportError): - # ImportError will be raised if win32api is not installed - return 'job' - else: - return 'job' - - -class WindowsHPCLauncher(BaseLauncher): - - job_id_regexp = CRegExp(r'\d+', config=True, - help="""A regular expression used to get the job id from the output of the - submit_command. """ - ) - job_file_name = Unicode(u'ipython_job.xml', config=True, - help="The filename of the instantiated job script.") - # The full path to the instantiated job script. This gets made dynamically - # by combining the work_dir with the job_file_name. - job_file = Unicode(u'') - scheduler = Unicode('', config=True, - help="The hostname of the scheduler to submit the job to.") - job_cmd = Unicode(find_job_cmd(), config=True, - help="The command for submitting jobs.") - - def __init__(self, work_dir=u'.', config=None, **kwargs): - super(WindowsHPCLauncher, self).__init__( - work_dir=work_dir, config=config, **kwargs - ) - - @property - def job_file(self): - return os.path.join(self.work_dir, self.job_file_name) - - def write_job_file(self, n): - raise NotImplementedError("Implement write_job_file in a subclass.") - - def find_args(self): - return [u'job.exe'] - - def parse_job_id(self, output): - """Take the output of the submit command and return the job id.""" - m = self.job_id_regexp.search(output) - if m is not None: - job_id = m.group() - else: - raise LauncherError("Job id couldn't be determined: %s" % output) - self.job_id = job_id - self.log.info('Job started with id: %r', job_id) - return job_id - - def start(self, n): - """Start n copies of the process using the Win HPC job scheduler.""" - self.write_job_file(n) - args = [ - 'submit', - '/jobfile:%s' % self.job_file, - '/scheduler:%s' % self.scheduler - ] - self.log.debug("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),)) - - output = check_output([self.job_cmd]+args, - env=os.environ, - cwd=self.work_dir, - stderr=STDOUT - ) - output = output.decode(DEFAULT_ENCODING, 'replace') - job_id = self.parse_job_id(output) - self.notify_start(job_id) - return job_id - - def stop(self): - args = [ - 'cancel', - self.job_id, - '/scheduler:%s' % self.scheduler - ] - self.log.info("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),)) - try: - output = check_output([self.job_cmd]+args, - env=os.environ, - cwd=self.work_dir, - stderr=STDOUT - ) - output = output.decode(DEFAULT_ENCODING, 'replace') - except: - output = u'The job already appears to be stopped: %r' % self.job_id - self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd - return output - - -class WindowsHPCControllerLauncher(WindowsHPCLauncher, ClusterAppMixin): - - job_file_name = Unicode(u'ipcontroller_job.xml', config=True, - help="WinHPC xml job file.") - controller_args = List([], config=False, - help="extra args to pass to ipcontroller") - - def write_job_file(self, n): - job = IPControllerJob(parent=self) - - t = IPControllerTask(parent=self) - # The tasks work directory is *not* the actual work directory of - # the controller. It is used as the base path for the stdout/stderr - # files that the scheduler redirects to. - t.work_directory = self.profile_dir - # Add the profile_dir and from self.start(). - t.controller_args.extend(self.cluster_args) - t.controller_args.extend(self.controller_args) - job.add_task(t) - - self.log.debug("Writing job description file: %s", self.job_file) - job.write(self.job_file) - - @property - def job_file(self): - return os.path.join(self.profile_dir, self.job_file_name) - - def start(self): - """Start the controller by profile_dir.""" - return super(WindowsHPCControllerLauncher, self).start(1) - - -class WindowsHPCEngineSetLauncher(WindowsHPCLauncher, ClusterAppMixin): - - job_file_name = Unicode(u'ipengineset_job.xml', config=True, - help="jobfile for ipengines job") - engine_args = List([], config=False, - help="extra args to pas to ipengine") - - def write_job_file(self, n): - job = IPEngineSetJob(parent=self) - - for i in range(n): - t = IPEngineTask(parent=self) - # The tasks work directory is *not* the actual work directory of - # the engine. It is used as the base path for the stdout/stderr - # files that the scheduler redirects to. - t.work_directory = self.profile_dir - # Add the profile_dir and from self.start(). - t.engine_args.extend(self.cluster_args) - t.engine_args.extend(self.engine_args) - job.add_task(t) - - self.log.debug("Writing job description file: %s", self.job_file) - job.write(self.job_file) - - @property - def job_file(self): - return os.path.join(self.profile_dir, self.job_file_name) - - def start(self, n): - """Start the controller by profile_dir.""" - return super(WindowsHPCEngineSetLauncher, self).start(n) - - -#----------------------------------------------------------------------------- -# Batch (PBS) system launchers -#----------------------------------------------------------------------------- - -class BatchClusterAppMixin(ClusterAppMixin): - """ClusterApp mixin that updates the self.context dict, rather than cl-args.""" - def _profile_dir_changed(self, name, old, new): - self.context[name] = new - _cluster_id_changed = _profile_dir_changed - - def _profile_dir_default(self): - self.context['profile_dir'] = '' - return '' - def _cluster_id_default(self): - self.context['cluster_id'] = '' - return '' - - -class BatchSystemLauncher(BaseLauncher): - """Launch an external process using a batch system. - - This class is designed to work with UNIX batch systems like PBS, LSF, - GridEngine, etc. The overall model is that there are different commands - like qsub, qdel, etc. that handle the starting and stopping of the process. - - This class also has the notion of a batch script. The ``batch_template`` - attribute can be set to a string that is a template for the batch script. - This template is instantiated using string formatting. Thus the template can - use {n} fot the number of instances. Subclasses can add additional variables - to the template dict. - """ - - # Subclasses must fill these in. See PBSEngineSet - submit_command = List([''], config=True, - help="The name of the command line program used to submit jobs.") - delete_command = List([''], config=True, - help="The name of the command line program used to delete jobs.") - job_id_regexp = CRegExp('', config=True, - help="""A regular expression used to get the job id from the output of the - submit_command.""") - job_id_regexp_group = Integer(0, config=True, - help="""The group we wish to match in job_id_regexp (0 to match all)""") - batch_template = Unicode('', config=True, - help="The string that is the batch script template itself.") - batch_template_file = Unicode(u'', config=True, - help="The file that contains the batch template.") - batch_file_name = Unicode(u'batch_script', config=True, - help="The filename of the instantiated batch script.") - queue = Unicode(u'', config=True, - help="The PBS Queue.") - - def _queue_changed(self, name, old, new): - self.context[name] = new - - n = Integer(1) - _n_changed = _queue_changed - - # not configurable, override in subclasses - # PBS Job Array regex - job_array_regexp = CRegExp('') - job_array_template = Unicode('') - # PBS Queue regex - queue_regexp = CRegExp('') - queue_template = Unicode('') - # The default batch template, override in subclasses - default_template = Unicode('') - # The full path to the instantiated batch script. - batch_file = Unicode(u'') - # the format dict used with batch_template: - context = Dict() - - def _context_default(self): - """load the default context with the default values for the basic keys - - because the _trait_changed methods only load the context if they - are set to something other than the default value. - """ - return dict(n=1, queue=u'', profile_dir=u'', cluster_id=u'') - - # the Formatter instance for rendering the templates: - formatter = Instance(EvalFormatter, (), {}) - - def find_args(self): - return self.submit_command + [self.batch_file] - - def __init__(self, work_dir=u'.', config=None, **kwargs): - super(BatchSystemLauncher, self).__init__( - work_dir=work_dir, config=config, **kwargs - ) - self.batch_file = os.path.join(self.work_dir, self.batch_file_name) - - def parse_job_id(self, output): - """Take the output of the submit command and return the job id.""" - m = self.job_id_regexp.search(output) - if m is not None: - job_id = m.group(self.job_id_regexp_group) - else: - raise LauncherError("Job id couldn't be determined: %s" % output) - self.job_id = job_id - self.log.info('Job submitted with job id: %r', job_id) - return job_id - - def write_batch_script(self, n): - """Instantiate and write the batch script to the work_dir.""" - self.n = n - # first priority is batch_template if set - if self.batch_template_file and not self.batch_template: - # second priority is batch_template_file - with open(self.batch_template_file) as f: - self.batch_template = f.read() - if not self.batch_template: - # third (last) priority is default_template - self.batch_template = self.default_template - # add jobarray or queue lines to user-specified template - # note that this is *only* when user did not specify a template. - self._insert_queue_in_script() - self._insert_job_array_in_script() - script_as_string = self.formatter.format(self.batch_template, **self.context) - self.log.debug('Writing batch script: %s', self.batch_file) - with open(self.batch_file, 'w') as f: - f.write(script_as_string) - os.chmod(self.batch_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - - def _insert_queue_in_script(self): - """Inserts a queue if required into the batch script. - """ - if self.queue and not self.queue_regexp.search(self.batch_template): - self.log.debug("adding PBS queue settings to batch script") - firstline, rest = self.batch_template.split('\n',1) - self.batch_template = u'\n'.join([firstline, self.queue_template, rest]) - - def _insert_job_array_in_script(self): - """Inserts a job array if required into the batch script. - """ - if not self.job_array_regexp.search(self.batch_template): - self.log.debug("adding job array settings to batch script") - firstline, rest = self.batch_template.split('\n',1) - self.batch_template = u'\n'.join([firstline, self.job_array_template, rest]) - - def start(self, n): - """Start n copies of the process using a batch system.""" - self.log.debug("Starting %s: %r", self.__class__.__name__, self.args) - # Here we save profile_dir in the context so they - # can be used in the batch script template as {profile_dir} - self.write_batch_script(n) - output = check_output(self.args, env=os.environ) - output = output.decode(DEFAULT_ENCODING, 'replace') - - job_id = self.parse_job_id(output) - self.notify_start(job_id) - return job_id - - def stop(self): - try: - p = Popen(self.delete_command+[self.job_id], env=os.environ, - stdout=PIPE, stderr=PIPE) - out, err = p.communicate() - output = out + err - except: - self.log.exception("Problem stopping cluster with command: %s" % - (self.delete_command + [self.job_id])) - output = "" - output = output.decode(DEFAULT_ENCODING, 'replace') - self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd - return output - - -class PBSLauncher(BatchSystemLauncher): - """A BatchSystemLauncher subclass for PBS.""" - - submit_command = List(['qsub'], config=True, - help="The PBS submit command ['qsub']") - delete_command = List(['qdel'], config=True, - help="The PBS delete command ['qsub']") - job_id_regexp = CRegExp(r'\d+', config=True, - help="Regular expresion for identifying the job ID [r'\d+']") - - batch_file = Unicode(u'') - job_array_regexp = CRegExp('#PBS\W+-t\W+[\w\d\-\$]+') - job_array_template = Unicode('#PBS -t 1-{n}') - queue_regexp = CRegExp('#PBS\W+-q\W+\$?\w+') - queue_template = Unicode('#PBS -q {queue}') - - -class PBSControllerLauncher(PBSLauncher, BatchClusterAppMixin): - """Launch a controller using PBS.""" - - batch_file_name = Unicode(u'pbs_controller', config=True, - help="batch file name for the controller job.") - default_template= Unicode("""#!/bin/sh -#PBS -V -#PBS -N ipcontroller -%s --log-to-file --profile-dir="{profile_dir}" --cluster-id="{cluster_id}" -"""%(' '.join(map(pipes.quote, ipcontroller_cmd_argv)))) - - def start(self): - """Start the controller by profile or profile_dir.""" - return super(PBSControllerLauncher, self).start(1) - - -class PBSEngineSetLauncher(PBSLauncher, BatchClusterAppMixin): - """Launch Engines using PBS""" - batch_file_name = Unicode(u'pbs_engines', config=True, - help="batch file name for the engine(s) job.") - default_template= Unicode(u"""#!/bin/sh -#PBS -V -#PBS -N ipengine -%s --profile-dir="{profile_dir}" --cluster-id="{cluster_id}" -"""%(' '.join(map(pipes.quote,ipengine_cmd_argv)))) - - -#SGE is very similar to PBS - -class SGELauncher(PBSLauncher): - """Sun GridEngine is a PBS clone with slightly different syntax""" - job_array_regexp = CRegExp('#\$\W+\-t') - job_array_template = Unicode('#$ -t 1-{n}') - queue_regexp = CRegExp('#\$\W+-q\W+\$?\w+') - queue_template = Unicode('#$ -q {queue}') - - -class SGEControllerLauncher(SGELauncher, BatchClusterAppMixin): - """Launch a controller using SGE.""" - - batch_file_name = Unicode(u'sge_controller', config=True, - help="batch file name for the ipontroller job.") - default_template= Unicode(u"""#$ -V -#$ -S /bin/sh -#$ -N ipcontroller -%s --log-to-file --profile-dir="{profile_dir}" --cluster-id="{cluster_id}" -"""%(' '.join(map(pipes.quote, ipcontroller_cmd_argv)))) - - def start(self): - """Start the controller by profile or profile_dir.""" - return super(SGEControllerLauncher, self).start(1) - - -class SGEEngineSetLauncher(SGELauncher, BatchClusterAppMixin): - """Launch Engines with SGE""" - batch_file_name = Unicode(u'sge_engines', config=True, - help="batch file name for the engine(s) job.") - default_template = Unicode("""#$ -V -#$ -S /bin/sh -#$ -N ipengine -%s --profile-dir="{profile_dir}" --cluster-id="{cluster_id}" -"""%(' '.join(map(pipes.quote, ipengine_cmd_argv)))) - - -# LSF launchers - -class LSFLauncher(BatchSystemLauncher): - """A BatchSystemLauncher subclass for LSF.""" - - submit_command = List(['bsub'], config=True, - help="The PBS submit command ['bsub']") - delete_command = List(['bkill'], config=True, - help="The PBS delete command ['bkill']") - job_id_regexp = CRegExp(r'\d+', config=True, - help="Regular expresion for identifying the job ID [r'\d+']") - - batch_file = Unicode(u'') - job_array_regexp = CRegExp('#BSUB[ \t]-J+\w+\[\d+-\d+\]') - job_array_template = Unicode('#BSUB -J ipengine[1-{n}]') - queue_regexp = CRegExp('#BSUB[ \t]+-q[ \t]+\w+') - queue_template = Unicode('#BSUB -q {queue}') - - def start(self, n): - """Start n copies of the process using LSF batch system. - This cant inherit from the base class because bsub expects - to be piped a shell script in order to honor the #BSUB directives : - bsub < script - """ - # Here we save profile_dir in the context so they - # can be used in the batch script template as {profile_dir} - self.write_batch_script(n) - piped_cmd = self.args[0]+'<\"'+self.args[1]+'\"' - self.log.debug("Starting %s: %s", self.__class__.__name__, piped_cmd) - p = Popen(piped_cmd, shell=True,env=os.environ,stdout=PIPE) - output,err = p.communicate() - output = output.decode(DEFAULT_ENCODING, 'replace') - job_id = self.parse_job_id(output) - self.notify_start(job_id) - return job_id - - -class LSFControllerLauncher(LSFLauncher, BatchClusterAppMixin): - """Launch a controller using LSF.""" - - batch_file_name = Unicode(u'lsf_controller', config=True, - help="batch file name for the controller job.") - default_template= Unicode("""#!/bin/sh - #BSUB -J ipcontroller - #BSUB -oo ipcontroller.o.%%J - #BSUB -eo ipcontroller.e.%%J - %s --log-to-file --profile-dir="{profile_dir}" --cluster-id="{cluster_id}" - """%(' '.join(map(pipes.quote,ipcontroller_cmd_argv)))) - - def start(self): - """Start the controller by profile or profile_dir.""" - return super(LSFControllerLauncher, self).start(1) - - -class LSFEngineSetLauncher(LSFLauncher, BatchClusterAppMixin): - """Launch Engines using LSF""" - batch_file_name = Unicode(u'lsf_engines', config=True, - help="batch file name for the engine(s) job.") - default_template= Unicode(u"""#!/bin/sh - #BSUB -oo ipengine.o.%%J - #BSUB -eo ipengine.e.%%J - %s --profile-dir="{profile_dir}" --cluster-id="{cluster_id}" - """%(' '.join(map(pipes.quote, ipengine_cmd_argv)))) - - - -class HTCondorLauncher(BatchSystemLauncher): - """A BatchSystemLauncher subclass for HTCondor. - - HTCondor requires that we launch the ipengine/ipcontroller scripts rather - that the python instance but otherwise is very similar to PBS. This is because - HTCondor destroys sys.executable when launching remote processes - a launched - python process depends on sys.executable to effectively evaluate its - module search paths. Without it, regardless of which python interpreter you launch - you will get the to built in module search paths. - - We use the ip{cluster, engine, controller} scripts as our executable to circumvent - this - the mechanism of shebanged scripts means that the python binary will be - launched with argv[0] set to the *location of the ip{cluster, engine, controller} - scripts on the remote node*. This means you need to take care that: - - a. Your remote nodes have their paths configured correctly, with the ipengine and ipcontroller - of the python environment you wish to execute code in having top precedence. - b. This functionality is untested on Windows. - - If you need different behavior, consider making you own template. - """ - - submit_command = List(['condor_submit'], config=True, - help="The HTCondor submit command ['condor_submit']") - delete_command = List(['condor_rm'], config=True, - help="The HTCondor delete command ['condor_rm']") - job_id_regexp = CRegExp(r'(\d+)\.$', config=True, - help="Regular expression for identifying the job ID [r'(\d+)\.$']") - job_id_regexp_group = Integer(1, config=True, - help="""The group we wish to match in job_id_regexp [1]""") - - job_array_regexp = CRegExp('queue\W+\$') - job_array_template = Unicode('queue {n}') - - - def _insert_job_array_in_script(self): - """Inserts a job array if required into the batch script. - """ - if not self.job_array_regexp.search(self.batch_template): - self.log.debug("adding job array settings to batch script") - #HTCondor requires that the job array goes at the bottom of the script - self.batch_template = '\n'.join([self.batch_template, - self.job_array_template]) - - def _insert_queue_in_script(self): - """AFAIK, HTCondor doesn't have a concept of multiple queues that can be - specified in the script. - """ - pass - - -class HTCondorControllerLauncher(HTCondorLauncher, BatchClusterAppMixin): - """Launch a controller using HTCondor.""" - - batch_file_name = Unicode(u'htcondor_controller', config=True, - help="batch file name for the controller job.") - default_template = Unicode(r""" -universe = vanilla -executable = ipcontroller -# by default we expect a shared file system -transfer_executable = False -arguments = --log-to-file '--profile-dir={profile_dir}' --cluster-id='{cluster_id}' -""") - - def start(self): - """Start the controller by profile or profile_dir.""" - return super(HTCondorControllerLauncher, self).start(1) - - -class HTCondorEngineSetLauncher(HTCondorLauncher, BatchClusterAppMixin): - """Launch Engines using HTCondor""" - batch_file_name = Unicode(u'htcondor_engines', config=True, - help="batch file name for the engine(s) job.") - default_template = Unicode(""" -universe = vanilla -executable = ipengine -# by default we expect a shared file system -transfer_executable = False -arguments = "--log-to-file '--profile-dir={profile_dir}' '--cluster-id={cluster_id}'" -""") - - -#----------------------------------------------------------------------------- -# A launcher for ipcluster itself! -#----------------------------------------------------------------------------- - - -class IPClusterLauncher(LocalProcessLauncher): - """Launch the ipcluster program in an external process.""" - - ipcluster_cmd = List(ipcluster_cmd_argv, config=True, - help="Popen command for ipcluster") - ipcluster_args = List( - ['--clean-logs=True', '--log-to-file', '--log-level=%i'%logging.INFO], config=True, - help="Command line arguments to pass to ipcluster.") - ipcluster_subcommand = Unicode('start') - profile = Unicode('default') - n = Integer(2) - - def find_args(self): - return self.ipcluster_cmd + [self.ipcluster_subcommand] + \ - ['--n=%i'%self.n, '--profile=%s'%self.profile] + \ - self.ipcluster_args - - def start(self): - return super(IPClusterLauncher, self).start() - -#----------------------------------------------------------------------------- -# Collections of launchers -#----------------------------------------------------------------------------- - -local_launchers = [ - LocalControllerLauncher, - LocalEngineLauncher, - LocalEngineSetLauncher, -] -mpi_launchers = [ - MPILauncher, - MPIControllerLauncher, - MPIEngineSetLauncher, -] -ssh_launchers = [ - SSHLauncher, - SSHControllerLauncher, - SSHEngineLauncher, - SSHEngineSetLauncher, - SSHProxyEngineSetLauncher, -] -winhpc_launchers = [ - WindowsHPCLauncher, - WindowsHPCControllerLauncher, - WindowsHPCEngineSetLauncher, -] -pbs_launchers = [ - PBSLauncher, - PBSControllerLauncher, - PBSEngineSetLauncher, -] -sge_launchers = [ - SGELauncher, - SGEControllerLauncher, - SGEEngineSetLauncher, -] -lsf_launchers = [ - LSFLauncher, - LSFControllerLauncher, - LSFEngineSetLauncher, -] -htcondor_launchers = [ - HTCondorLauncher, - HTCondorControllerLauncher, - HTCondorEngineSetLauncher, -] -all_launchers = local_launchers + mpi_launchers + ssh_launchers + winhpc_launchers\ - + pbs_launchers + sge_launchers + lsf_launchers + htcondor_launchers diff --git a/ipython_parallel/apps/logwatcher.py b/ipython_parallel/apps/logwatcher.py deleted file mode 100644 index 3a72423..0000000 --- a/ipython_parallel/apps/logwatcher.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -A simple logger object that consolidates messages incoming from ipcluster processes. - -Authors: - -* MinRK - -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - - -import logging -import sys - -import zmq -from zmq.eventloop import ioloop, zmqstream - -from IPython.config.configurable import LoggingConfigurable -from IPython.utils.localinterfaces import localhost -from IPython.utils.traitlets import Int, Unicode, Instance, List - -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - - -class LogWatcher(LoggingConfigurable): - """A simple class that receives messages on a SUB socket, as published - by subclasses of `zmq.log.handlers.PUBHandler`, and logs them itself. - - This can subscribe to multiple topics, but defaults to all topics. - """ - - # configurables - topics = List([''], config=True, - help="The ZMQ topics to subscribe to. Default is to subscribe to all messages") - url = Unicode(config=True, - help="ZMQ url on which to listen for log messages") - def _url_default(self): - return 'tcp://%s:20202' % localhost() - - # internals - stream = Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True) - - context = Instance(zmq.Context) - def _context_default(self): - return zmq.Context.instance() - - loop = Instance(zmq.eventloop.ioloop.IOLoop) - def _loop_default(self): - return ioloop.IOLoop.instance() - - def __init__(self, **kwargs): - super(LogWatcher, self).__init__(**kwargs) - s = self.context.socket(zmq.SUB) - s.bind(self.url) - self.stream = zmqstream.ZMQStream(s, self.loop) - self.subscribe() - self.on_trait_change(self.subscribe, 'topics') - - def start(self): - self.stream.on_recv(self.log_message) - - def stop(self): - self.stream.stop_on_recv() - - def subscribe(self): - """Update our SUB socket's subscriptions.""" - self.stream.setsockopt(zmq.UNSUBSCRIBE, '') - if '' in self.topics: - self.log.debug("Subscribing to: everything") - self.stream.setsockopt(zmq.SUBSCRIBE, '') - else: - for topic in self.topics: - self.log.debug("Subscribing to: %r"%(topic)) - self.stream.setsockopt(zmq.SUBSCRIBE, topic) - - def _extract_level(self, topic_str): - """Turn 'engine.0.INFO.extra' into (logging.INFO, 'engine.0.extra')""" - topics = topic_str.split('.') - for idx,t in enumerate(topics): - level = getattr(logging, t, None) - if level is not None: - break - - if level is None: - level = logging.INFO - else: - topics.pop(idx) - - return level, '.'.join(topics) - - - def log_message(self, raw): - """receive and parse a message, then log it.""" - if len(raw) != 2 or '.' not in raw[0]: - self.log.error("Invalid log message: %s"%raw) - return - else: - topic, msg = raw - # don't newline, since log messages always newline: - topic,level_name = topic.rsplit('.',1) - level,topic = self._extract_level(topic) - if msg[-1] == '\n': - msg = msg[:-1] - self.log.log(level, "[%s] %s" % (topic, msg)) - diff --git a/ipython_parallel/apps/win32support.py b/ipython_parallel/apps/win32support.py deleted file mode 100644 index 81ab06d..0000000 --- a/ipython_parallel/apps/win32support.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Utility for forwarding file read events over a zmq socket. - -This is necessary because select on Windows only supports sockets, not FDs. - -Authors: - -* MinRK - -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import uuid -import zmq - -from threading import Thread - -from IPython.utils.py3compat import unicode_type - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -class ForwarderThread(Thread): - def __init__(self, sock, fd): - Thread.__init__(self) - self.daemon=True - self.sock = sock - self.fd = fd - - def run(self): - """Loop through lines in self.fd, and send them over self.sock.""" - line = self.fd.readline() - # allow for files opened in unicode mode - if isinstance(line, unicode_type): - send = self.sock.send_unicode - else: - send = self.sock.send - while line: - send(line) - line = self.fd.readline() - # line == '' means EOF - self.fd.close() - self.sock.close() - -def forward_read_events(fd, context=None): - """Forward read events from an FD over a socket. - - This method wraps a file in a socket pair, so it can - be polled for read events by select (specifically zmq.eventloop.ioloop) - """ - if context is None: - context = zmq.Context.instance() - push = context.socket(zmq.PUSH) - push.setsockopt(zmq.LINGER, -1) - pull = context.socket(zmq.PULL) - addr='inproc://%s'%uuid.uuid4() - push.bind(addr) - pull.connect(addr) - forwarder = ForwarderThread(push, fd) - forwarder.start() - return pull - - -__all__ = ['forward_read_events'] diff --git a/ipython_parallel/apps/winhpcjob.py b/ipython_parallel/apps/winhpcjob.py deleted file mode 100644 index c61c963..0000000 --- a/ipython_parallel/apps/winhpcjob.py +++ /dev/null @@ -1,320 +0,0 @@ -# encoding: utf-8 -""" -Job and task components for writing .xml files that the Windows HPC Server -2008 can use to start jobs. - -Authors: - -* Brian Granger -* MinRK - -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import re -import uuid - -from xml.etree import ElementTree as ET - -from IPython.config.configurable import Configurable -from IPython.utils.py3compat import iteritems -from IPython.utils.traitlets import ( - Unicode, Integer, List, Instance, - Enum, Bool -) - -#----------------------------------------------------------------------------- -# Job and Task classes -#----------------------------------------------------------------------------- - - -def as_str(value): - if isinstance(value, str): - return value - elif isinstance(value, bool): - if value: - return 'true' - else: - return 'false' - elif isinstance(value, (int, float)): - return repr(value) - else: - return value - - -def indent(elem, level=0): - i = "\n" + level*" " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - indent(elem, level+1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - -def find_username(): - domain = os.environ.get('USERDOMAIN') - username = os.environ.get('USERNAME','') - if domain is None: - return username - else: - return '%s\\%s' % (domain, username) - - -class WinHPCJob(Configurable): - - job_id = Unicode('') - job_name = Unicode('MyJob', config=True) - min_cores = Integer(1, config=True) - max_cores = Integer(1, config=True) - min_sockets = Integer(1, config=True) - max_sockets = Integer(1, config=True) - min_nodes = Integer(1, config=True) - max_nodes = Integer(1, config=True) - unit_type = Unicode("Core", config=True) - auto_calculate_min = Bool(True, config=True) - auto_calculate_max = Bool(True, config=True) - run_until_canceled = Bool(False, config=True) - is_exclusive = Bool(False, config=True) - username = Unicode(find_username(), config=True) - job_type = Unicode('Batch', config=True) - priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'), - default_value='Highest', config=True) - requested_nodes = Unicode('', config=True) - project = Unicode('IPython', config=True) - xmlns = Unicode('http://schemas.microsoft.com/HPCS2008/scheduler/') - version = Unicode("2.000") - tasks = List([]) - - @property - def owner(self): - return self.username - - def _write_attr(self, root, attr, key): - s = as_str(getattr(self, attr, '')) - if s: - root.set(key, s) - - def as_element(self): - # We have to add _A_ type things to get the right order than - # the MSFT XML parser expects. - root = ET.Element('Job') - self._write_attr(root, 'version', '_A_Version') - self._write_attr(root, 'job_name', '_B_Name') - self._write_attr(root, 'unit_type', '_C_UnitType') - self._write_attr(root, 'min_cores', '_D_MinCores') - self._write_attr(root, 'max_cores', '_E_MaxCores') - self._write_attr(root, 'min_sockets', '_F_MinSockets') - self._write_attr(root, 'max_sockets', '_G_MaxSockets') - self._write_attr(root, 'min_nodes', '_H_MinNodes') - self._write_attr(root, 'max_nodes', '_I_MaxNodes') - self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled') - self._write_attr(root, 'is_exclusive', '_K_IsExclusive') - self._write_attr(root, 'username', '_L_UserName') - self._write_attr(root, 'job_type', '_M_JobType') - self._write_attr(root, 'priority', '_N_Priority') - self._write_attr(root, 'requested_nodes', '_O_RequestedNodes') - self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax') - self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin') - self._write_attr(root, 'project', '_R_Project') - self._write_attr(root, 'owner', '_S_Owner') - self._write_attr(root, 'xmlns', '_T_xmlns') - dependencies = ET.SubElement(root, "Dependencies") - etasks = ET.SubElement(root, "Tasks") - for t in self.tasks: - etasks.append(t.as_element()) - return root - - def tostring(self): - """Return the string representation of the job description XML.""" - root = self.as_element() - indent(root) - txt = ET.tostring(root, encoding="utf-8").decode('utf-8') - # Now remove the tokens used to order the attributes. - txt = re.sub(r'_[A-Z]_','',txt) - txt = '\n' + txt - return txt - - def write(self, filename): - """Write the XML job description to a file.""" - txt = self.tostring() - with open(filename, 'w') as f: - f.write(txt) - - def add_task(self, task): - """Add a task to the job. - - Parameters - ---------- - task : :class:`WinHPCTask` - The task object to add. - """ - self.tasks.append(task) - - -class WinHPCTask(Configurable): - - task_id = Unicode('') - task_name = Unicode('') - version = Unicode("2.000") - min_cores = Integer(1, config=True) - max_cores = Integer(1, config=True) - min_sockets = Integer(1, config=True) - max_sockets = Integer(1, config=True) - min_nodes = Integer(1, config=True) - max_nodes = Integer(1, config=True) - unit_type = Unicode("Core", config=True) - command_line = Unicode('', config=True) - work_directory = Unicode('', config=True) - is_rerunnaable = Bool(True, config=True) - std_out_file_path = Unicode('', config=True) - std_err_file_path = Unicode('', config=True) - is_parametric = Bool(False, config=True) - environment_variables = Instance(dict, args=(), config=True) - - def _write_attr(self, root, attr, key): - s = as_str(getattr(self, attr, '')) - if s: - root.set(key, s) - - def as_element(self): - root = ET.Element('Task') - self._write_attr(root, 'version', '_A_Version') - self._write_attr(root, 'task_name', '_B_Name') - self._write_attr(root, 'min_cores', '_C_MinCores') - self._write_attr(root, 'max_cores', '_D_MaxCores') - self._write_attr(root, 'min_sockets', '_E_MinSockets') - self._write_attr(root, 'max_sockets', '_F_MaxSockets') - self._write_attr(root, 'min_nodes', '_G_MinNodes') - self._write_attr(root, 'max_nodes', '_H_MaxNodes') - self._write_attr(root, 'command_line', '_I_CommandLine') - self._write_attr(root, 'work_directory', '_J_WorkDirectory') - self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable') - self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath') - self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath') - self._write_attr(root, 'is_parametric', '_N_IsParametric') - self._write_attr(root, 'unit_type', '_O_UnitType') - root.append(self.get_env_vars()) - return root - - def get_env_vars(self): - env_vars = ET.Element('EnvironmentVariables') - for k, v in iteritems(self.environment_variables): - variable = ET.SubElement(env_vars, "Variable") - name = ET.SubElement(variable, "Name") - name.text = k - value = ET.SubElement(variable, "Value") - value.text = v - return env_vars - - - -# By declaring these, we can configure the controller and engine separately! - -class IPControllerJob(WinHPCJob): - job_name = Unicode('IPController', config=False) - is_exclusive = Bool(False, config=True) - username = Unicode(find_username(), config=True) - priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'), - default_value='Highest', config=True) - requested_nodes = Unicode('', config=True) - project = Unicode('IPython', config=True) - - -class IPEngineSetJob(WinHPCJob): - job_name = Unicode('IPEngineSet', config=False) - is_exclusive = Bool(False, config=True) - username = Unicode(find_username(), config=True) - priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'), - default_value='Highest', config=True) - requested_nodes = Unicode('', config=True) - project = Unicode('IPython', config=True) - - -class IPControllerTask(WinHPCTask): - - task_name = Unicode('IPController', config=True) - controller_cmd = List(['ipcontroller.exe'], config=True) - controller_args = List(['--log-to-file', '--log-level=40'], config=True) - # I don't want these to be configurable - std_out_file_path = Unicode('', config=False) - std_err_file_path = Unicode('', config=False) - min_cores = Integer(1, config=False) - max_cores = Integer(1, config=False) - min_sockets = Integer(1, config=False) - max_sockets = Integer(1, config=False) - min_nodes = Integer(1, config=False) - max_nodes = Integer(1, config=False) - unit_type = Unicode("Core", config=False) - work_directory = Unicode('', config=False) - - def __init__(self, **kwargs): - super(IPControllerTask, self).__init__(**kwargs) - the_uuid = uuid.uuid1() - self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid) - self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid) - - @property - def command_line(self): - return ' '.join(self.controller_cmd + self.controller_args) - - -class IPEngineTask(WinHPCTask): - - task_name = Unicode('IPEngine', config=True) - engine_cmd = List(['ipengine.exe'], config=True) - engine_args = List(['--log-to-file', '--log-level=40'], config=True) - # I don't want these to be configurable - std_out_file_path = Unicode('', config=False) - std_err_file_path = Unicode('', config=False) - min_cores = Integer(1, config=False) - max_cores = Integer(1, config=False) - min_sockets = Integer(1, config=False) - max_sockets = Integer(1, config=False) - min_nodes = Integer(1, config=False) - max_nodes = Integer(1, config=False) - unit_type = Unicode("Core", config=False) - work_directory = Unicode('', config=False) - - def __init__(self, **kwargs): - super(IPEngineTask,self).__init__(**kwargs) - the_uuid = uuid.uuid1() - self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid) - self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid) - - @property - def command_line(self): - return ' '.join(self.engine_cmd + self.engine_args) - - -# j = WinHPCJob(None) -# j.job_name = 'IPCluster' -# j.username = 'GNET\\bgranger' -# j.requested_nodes = 'GREEN' -# -# t = WinHPCTask(None) -# t.task_name = 'Controller' -# t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10" -# t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default" -# t.std_out_file_path = 'controller-out.txt' -# t.std_err_file_path = 'controller-err.txt' -# t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages" -# j.add_task(t) - diff --git a/ipython_parallel/client/__init__.py b/ipython_parallel/client/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/ipython_parallel/client/__init__.py +++ /dev/null diff --git a/ipython_parallel/client/asyncresult.py b/ipython_parallel/client/asyncresult.py deleted file mode 100644 index 9f7f754..0000000 --- a/ipython_parallel/client/asyncresult.py +++ /dev/null @@ -1,703 +0,0 @@ -"""AsyncResult objects for the client""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import sys -import time -from datetime import datetime - -from zmq import MessageTracker - -from IPython.core.display import clear_output, display, display_pretty -from decorator import decorator -from ipython_parallel import error -from IPython.utils.py3compat import string_types - - -def _raw_text(s): - display_pretty(s, raw=True) - - -# global empty tracker that's always done: -finished_tracker = MessageTracker() - -@decorator -def check_ready(f, self, *args, **kwargs): - """Call spin() to sync state prior to calling the method.""" - self.wait(0) - if not self._ready: - raise error.TimeoutError("result not ready") - return f(self, *args, **kwargs) - -class AsyncResult(object): - """Class for representing results of non-blocking calls. - - Provides the same interface as :py:class:`multiprocessing.pool.AsyncResult`. - """ - - msg_ids = None - _targets = None - _tracker = None - _single_result = False - owner = False, - - def __init__(self, client, msg_ids, fname='unknown', targets=None, tracker=None, - owner=False, - ): - if isinstance(msg_ids, string_types): - # always a list - msg_ids = [msg_ids] - self._single_result = True - else: - self._single_result = False - if tracker is None: - # default to always done - tracker = finished_tracker - self._client = client - self.msg_ids = msg_ids - self._fname=fname - self._targets = targets - self._tracker = tracker - self.owner = owner - - self._ready = False - self._outputs_ready = False - self._success = None - self._metadata = [self._client.metadata[id] for id in self.msg_ids] - - def __repr__(self): - if self._ready: - return "<%s: finished>"%(self.__class__.__name__) - else: - return "<%s: %s>"%(self.__class__.__name__,self._fname) - - - def _reconstruct_result(self, res): - """Reconstruct our result from actual result list (always a list) - - Override me in subclasses for turning a list of results - into the expected form. - """ - if self._single_result: - return res[0] - else: - return res - - def get(self, timeout=-1): - """Return the result when it arrives. - - If `timeout` is not ``None`` and the result does not arrive within - `timeout` seconds then ``TimeoutError`` is raised. If the - remote call raised an exception then that exception will be reraised - by get() inside a `RemoteError`. - """ - if not self.ready(): - self.wait(timeout) - - if self._ready: - if self._success: - return self._result - else: - raise self._exception - else: - raise error.TimeoutError("Result not ready.") - - def _check_ready(self): - if not self.ready(): - raise error.TimeoutError("Result not ready.") - - def ready(self): - """Return whether the call has completed.""" - if not self._ready: - self.wait(0) - elif not self._outputs_ready: - self._wait_for_outputs(0) - - return self._ready - - def wait(self, timeout=-1): - """Wait until the result is available or until `timeout` seconds pass. - - This method always returns None. - """ - if self._ready: - self._wait_for_outputs(timeout) - return - self._ready = self._client.wait(self.msg_ids, timeout) - if self._ready: - try: - results = list(map(self._client.results.get, self.msg_ids)) - self._result = results - if self._single_result: - r = results[0] - if isinstance(r, Exception): - raise r - else: - results = error.collect_exceptions(results, self._fname) - self._result = self._reconstruct_result(results) - except Exception as e: - self._exception = e - self._success = False - else: - self._success = True - finally: - if timeout is None or timeout < 0: - # cutoff infinite wait at 10s - timeout = 10 - self._wait_for_outputs(timeout) - - if self.owner: - - self._metadata = [self._client.metadata.pop(mid) for mid in self.msg_ids] - [self._client.results.pop(mid) for mid in self.msg_ids] - - - - def successful(self): - """Return whether the call completed without raising an exception. - - Will raise ``AssertionError`` if the result is not ready. - """ - assert self.ready() - return self._success - - #---------------------------------------------------------------- - # Extra methods not in mp.pool.AsyncResult - #---------------------------------------------------------------- - - def get_dict(self, timeout=-1): - """Get the results as a dict, keyed by engine_id. - - timeout behavior is described in `get()`. - """ - - results = self.get(timeout) - if self._single_result: - results = [results] - engine_ids = [ md['engine_id'] for md in self._metadata ] - - - rdict = {} - for engine_id, result in zip(engine_ids, results): - if engine_id in rdict: - raise ValueError("Cannot build dict, %i jobs ran on engine #%i" % ( - engine_ids.count(engine_id), engine_id) - ) - else: - rdict[engine_id] = result - - return rdict - - @property - def result(self): - """result property wrapper for `get(timeout=-1)`.""" - return self.get() - - # abbreviated alias: - r = result - - @property - def metadata(self): - """property for accessing execution metadata.""" - if self._single_result: - return self._metadata[0] - else: - return self._metadata - - @property - def result_dict(self): - """result property as a dict.""" - return self.get_dict() - - def __dict__(self): - return self.get_dict(0) - - def abort(self): - """abort my tasks.""" - assert not self.ready(), "Can't abort, I am already done!" - return self._client.abort(self.msg_ids, targets=self._targets, block=True) - - @property - def sent(self): - """check whether my messages have been sent.""" - return self._tracker.done - - def wait_for_send(self, timeout=-1): - """wait for pyzmq send to complete. - - This is necessary when sending arrays that you intend to edit in-place. - `timeout` is in seconds, and will raise TimeoutError if it is reached - before the send completes. - """ - return self._tracker.wait(timeout) - - #------------------------------------- - # dict-access - #------------------------------------- - - def __getitem__(self, key): - """getitem returns result value(s) if keyed by int/slice, or metadata if key is str. - """ - if isinstance(key, int): - self._check_ready() - return error.collect_exceptions([self._result[key]], self._fname)[0] - elif isinstance(key, slice): - self._check_ready() - return error.collect_exceptions(self._result[key], self._fname) - elif isinstance(key, string_types): - # metadata proxy *does not* require that results are done - self.wait(0) - values = [ md[key] for md in self._metadata ] - if self._single_result: - return values[0] - else: - return values - else: - raise TypeError("Invalid key type %r, must be 'int','slice', or 'str'"%type(key)) - - def __getattr__(self, key): - """getattr maps to getitem for convenient attr access to metadata.""" - try: - return self.__getitem__(key) - except (error.TimeoutError, KeyError): - raise AttributeError("%r object has no attribute %r"%( - self.__class__.__name__, key)) - - # asynchronous iterator: - def __iter__(self): - if self._single_result: - raise TypeError("AsyncResults with a single result are not iterable.") - try: - rlist = self.get(0) - except error.TimeoutError: - # wait for each result individually - for msg_id in self.msg_ids: - ar = AsyncResult(self._client, msg_id, self._fname) - yield ar.get() - else: - # already done - for r in rlist: - yield r - - def __len__(self): - return len(self.msg_ids) - - #------------------------------------- - # Sugar methods and attributes - #------------------------------------- - - def timedelta(self, start, end, start_key=min, end_key=max): - """compute the difference between two sets of timestamps - - The default behavior is to use the earliest of the first - and the latest of the second list, but this can be changed - by passing a different - - Parameters - ---------- - - start : one or more datetime objects (e.g. ar.submitted) - end : one or more datetime objects (e.g. ar.received) - start_key : callable - Function to call on `start` to extract the relevant - entry [defalt: min] - end_key : callable - Function to call on `end` to extract the relevant - entry [default: max] - - Returns - ------- - - dt : float - The time elapsed (in seconds) between the two selected timestamps. - """ - if not isinstance(start, datetime): - # handle single_result AsyncResults, where ar.stamp is single object, - # not a list - start = start_key(start) - if not isinstance(end, datetime): - # handle single_result AsyncResults, where ar.stamp is single object, - # not a list - end = end_key(end) - return (end - start).total_seconds() - - @property - def progress(self): - """the number of tasks which have been completed at this point. - - Fractional progress would be given by 1.0 * ar.progress / len(ar) - """ - self.wait(0) - return len(self) - len(set(self.msg_ids).intersection(self._client.outstanding)) - - @property - def elapsed(self): - """elapsed time since initial submission""" - if self.ready(): - return self.wall_time - - now = submitted = datetime.now() - for msg_id in self.msg_ids: - if msg_id in self._client.metadata: - stamp = self._client.metadata[msg_id]['submitted'] - if stamp and stamp < submitted: - submitted = stamp - return (now-submitted).total_seconds() - - @property - @check_ready - def serial_time(self): - """serial computation time of a parallel calculation - - Computed as the sum of (completed-started) of each task - """ - t = 0 - for md in self._metadata: - t += (md['completed'] - md['started']).total_seconds() - return t - - @property - @check_ready - def wall_time(self): - """actual computation time of a parallel calculation - - Computed as the time between the latest `received` stamp - and the earliest `submitted`. - - Only reliable if Client was spinning/waiting when the task finished, because - the `received` timestamp is created when a result is pulled off of the zmq queue, - which happens as a result of `client.spin()`. - - For similar comparison of other timestamp pairs, check out AsyncResult.timedelta. - - """ - return self.timedelta(self.submitted, self.received) - - def wait_interactive(self, interval=1., timeout=-1): - """interactive wait, printing progress at regular intervals""" - if timeout is None: - timeout = -1 - N = len(self) - tic = time.time() - while not self.ready() and (timeout < 0 or time.time() - tic <= timeout): - self.wait(interval) - clear_output(wait=True) - print("%4i/%i tasks finished after %4i s" % (self.progress, N, self.elapsed), end="") - sys.stdout.flush() - print() - print("done") - - def _republish_displaypub(self, content, eid): - """republish individual displaypub content dicts""" - try: - ip = get_ipython() - except NameError: - # displaypub is meaningless outside IPython - return - md = content['metadata'] or {} - md['engine'] = eid - ip.display_pub.publish(data=content['data'], metadata=md) - - def _display_stream(self, text, prefix='', file=None): - if not text: - # nothing to display - return - if file is None: - file = sys.stdout - end = '' if text.endswith('\n') else '\n' - - multiline = text.count('\n') > int(text.endswith('\n')) - if prefix and multiline and not text.startswith('\n'): - prefix = prefix + '\n' - print("%s%s" % (prefix, text), file=file, end=end) - - - def _display_single_result(self): - self._display_stream(self.stdout) - self._display_stream(self.stderr, file=sys.stderr) - - try: - get_ipython() - except NameError: - # displaypub is meaningless outside IPython - return - - for output in self.outputs: - self._republish_displaypub(output, self.engine_id) - - if self.execute_result is not None: - display(self.get()) - - def _wait_for_outputs(self, timeout=-1): - """wait for the 'status=idle' message that indicates we have all outputs - """ - if self._outputs_ready or not self._success: - # don't wait on errors - return - - # cast None to -1 for infinite timeout - if timeout is None: - timeout = -1 - - tic = time.time() - while True: - self._client._flush_iopub(self._client._iopub_socket) - self._outputs_ready = all(md['outputs_ready'] - for md in self._metadata) - if self._outputs_ready or \ - (timeout >= 0 and time.time() > tic + timeout): - break - time.sleep(0.01) - - @check_ready - def display_outputs(self, groupby="type"): - """republish the outputs of the computation - - Parameters - ---------- - - groupby : str [default: type] - if 'type': - Group outputs by type (show all stdout, then all stderr, etc.): - - [stdout:1] foo - [stdout:2] foo - [stderr:1] bar - [stderr:2] bar - if 'engine': - Display outputs for each engine before moving on to the next: - - [stdout:1] foo - [stderr:1] bar - [stdout:2] foo - [stderr:2] bar - - if 'order': - Like 'type', but further collate individual displaypub - outputs. This is meant for cases of each command producing - several plots, and you would like to see all of the first - plots together, then all of the second plots, and so on. - """ - if self._single_result: - self._display_single_result() - return - - stdouts = self.stdout - stderrs = self.stderr - execute_results = self.execute_result - output_lists = self.outputs - results = self.get() - - targets = self.engine_id - - if groupby == "engine": - for eid,stdout,stderr,outputs,r,execute_result in zip( - targets, stdouts, stderrs, output_lists, results, execute_results - ): - self._display_stream(stdout, '[stdout:%i] ' % eid) - self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr) - - try: - get_ipython() - except NameError: - # displaypub is meaningless outside IPython - return - - if outputs or execute_result is not None: - _raw_text('[output:%i]' % eid) - - for output in outputs: - self._republish_displaypub(output, eid) - - if execute_result is not None: - display(r) - - elif groupby in ('type', 'order'): - # republish stdout: - for eid,stdout in zip(targets, stdouts): - self._display_stream(stdout, '[stdout:%i] ' % eid) - - # republish stderr: - for eid,stderr in zip(targets, stderrs): - self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr) - - try: - get_ipython() - except NameError: - # displaypub is meaningless outside IPython - return - - if groupby == 'order': - output_dict = dict((eid, outputs) for eid,outputs in zip(targets, output_lists)) - N = max(len(outputs) for outputs in output_lists) - for i in range(N): - for eid in targets: - outputs = output_dict[eid] - if len(outputs) >= N: - _raw_text('[output:%i]' % eid) - self._republish_displaypub(outputs[i], eid) - else: - # republish displaypub output - for eid,outputs in zip(targets, output_lists): - if outputs: - _raw_text('[output:%i]' % eid) - for output in outputs: - self._republish_displaypub(output, eid) - - # finally, add execute_result: - for eid,r,execute_result in zip(targets, results, execute_results): - if execute_result is not None: - display(r) - - else: - raise ValueError("groupby must be one of 'type', 'engine', 'collate', not %r" % groupby) - - - - -class AsyncMapResult(AsyncResult): - """Class for representing results of non-blocking gathers. - - This will properly reconstruct the gather. - - This class is iterable at any time, and will wait on results as they come. - - If ordered=False, then the first results to arrive will come first, otherwise - results will be yielded in the order they were submitted. - - """ - - def __init__(self, client, msg_ids, mapObject, fname='', ordered=True): - AsyncResult.__init__(self, client, msg_ids, fname=fname) - self._mapObject = mapObject - self._single_result = False - self.ordered = ordered - - def _reconstruct_result(self, res): - """Perform the gather on the actual results.""" - return self._mapObject.joinPartitions(res) - - # asynchronous iterator: - def __iter__(self): - it = self._ordered_iter if self.ordered else self._unordered_iter - for r in it(): - yield r - - # asynchronous ordered iterator: - def _ordered_iter(self): - """iterator for results *as they arrive*, preserving submission order.""" - try: - rlist = self.get(0) - except error.TimeoutError: - # wait for each result individually - for msg_id in self.msg_ids: - ar = AsyncResult(self._client, msg_id, self._fname) - rlist = ar.get() - try: - for r in rlist: - yield r - except TypeError: - # flattened, not a list - # this could get broken by flattened data that returns iterables - # but most calls to map do not expose the `flatten` argument - yield rlist - else: - # already done - for r in rlist: - yield r - - # asynchronous unordered iterator: - def _unordered_iter(self): - """iterator for results *as they arrive*, on FCFS basis, ignoring submission order.""" - try: - rlist = self.get(0) - except error.TimeoutError: - pending = set(self.msg_ids) - while pending: - try: - self._client.wait(pending, 1e-3) - except error.TimeoutError: - # ignore timeout error, because that only means - # *some* jobs are outstanding - pass - # update ready set with those no longer outstanding: - ready = pending.difference(self._client.outstanding) - # update pending to exclude those that are finished - pending = pending.difference(ready) - while ready: - msg_id = ready.pop() - ar = AsyncResult(self._client, msg_id, self._fname) - rlist = ar.get() - try: - for r in rlist: - yield r - except TypeError: - # flattened, not a list - # this could get broken by flattened data that returns iterables - # but most calls to map do not expose the `flatten` argument - yield rlist - else: - # already done - for r in rlist: - yield r - - -class AsyncHubResult(AsyncResult): - """Class to wrap pending results that must be requested from the Hub. - - Note that waiting/polling on these objects requires polling the Hubover the network, - so use `AsyncHubResult.wait()` sparingly. - """ - - def _wait_for_outputs(self, timeout=-1): - """no-op, because HubResults are never incomplete""" - self._outputs_ready = True - - def wait(self, timeout=-1): - """wait for result to complete.""" - start = time.time() - if self._ready: - return - local_ids = [m for m in self.msg_ids if m in self._client.outstanding] - local_ready = self._client.wait(local_ids, timeout) - if local_ready: - remote_ids = [m for m in self.msg_ids if m not in self._client.results] - if not remote_ids: - self._ready = True - else: - rdict = self._client.result_status(remote_ids, status_only=False) - pending = rdict['pending'] - while pending and (timeout < 0 or time.time() < start+timeout): - rdict = self._client.result_status(remote_ids, status_only=False) - pending = rdict['pending'] - if pending: - time.sleep(0.1) - if not pending: - self._ready = True - if self._ready: - try: - results = list(map(self._client.results.get, self.msg_ids)) - self._result = results - if self._single_result: - r = results[0] - if isinstance(r, Exception): - raise r - else: - results = error.collect_exceptions(results, self._fname) - self._result = self._reconstruct_result(results) - except Exception as e: - self._exception = e - self._success = False - else: - self._success = True - finally: - self._metadata = [self._client.metadata[mid] for mid in self.msg_ids] - if self.owner: - [self._client.metadata.pop(mid) for mid in self.msg_ids] - [self._client.results.pop(mid) for mid in self.msg_ids] - - -__all__ = ['AsyncResult', 'AsyncMapResult', 'AsyncHubResult'] diff --git a/ipython_parallel/client/client.py b/ipython_parallel/client/client.py deleted file mode 100644 index e913007..0000000 --- a/ipython_parallel/client/client.py +++ /dev/null @@ -1,1892 +0,0 @@ -"""A semi-synchronous Client for IPython parallel""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import os -import json -import sys -from threading import Thread, Event -import time -import warnings -from datetime import datetime -from getpass import getpass -from pprint import pprint - -pjoin = os.path.join - -import zmq - -from IPython.config.configurable import MultipleInstanceError -from IPython.core.application import BaseIPythonApplication -from IPython.core.profiledir import ProfileDir, ProfileDirError - -from IPython.utils.capture import RichOutput -from IPython.utils.coloransi import TermColors -from jupyter_client.jsonutil import rekey, extract_dates, parse_date -from IPython.utils.localinterfaces import localhost, is_local_ip -from IPython.utils.path import get_ipython_dir, compress_user -from IPython.utils.py3compat import cast_bytes, string_types, xrange, iteritems -from IPython.utils.traitlets import (HasTraits, Integer, Instance, Unicode, - Dict, List, Bool, Set, Any) -from decorator import decorator - -from ipython_parallel import Reference -from ipython_parallel import error -from ipython_parallel import util - -from IPython.kernel.zmq.session import Session, Message -from IPython.kernel.zmq import serialize - -from .asyncresult import AsyncResult, AsyncHubResult -from .view import DirectView, LoadBalancedView - -#-------------------------------------------------------------------------- -# Decorators for Client methods -#-------------------------------------------------------------------------- - - -@decorator -def spin_first(f, self, *args, **kwargs): - """Call spin() to sync state prior to calling the method.""" - self.spin() - return f(self, *args, **kwargs) - - -#-------------------------------------------------------------------------- -# Classes -#-------------------------------------------------------------------------- - -_no_connection_file_msg = """ -Failed to connect because no Controller could be found. -Please double-check your profile and ensure that a cluster is running. -""" - -class ExecuteReply(RichOutput): - """wrapper for finished Execute results""" - def __init__(self, msg_id, content, metadata): - self.msg_id = msg_id - self._content = content - self.execution_count = content['execution_count'] - self.metadata = metadata - - # RichOutput overrides - - @property - def source(self): - execute_result = self.metadata['execute_result'] - if execute_result: - return execute_result.get('source', '') - - @property - def data(self): - execute_result = self.metadata['execute_result'] - if execute_result: - return execute_result.get('data', {}) - - @property - def _metadata(self): - execute_result = self.metadata['execute_result'] - if execute_result: - return execute_result.get('metadata', {}) - - def display(self): - from IPython.display import publish_display_data - publish_display_data(self.data, self.metadata) - - def _repr_mime_(self, mime): - if mime not in self.data: - return - data = self.data[mime] - if mime in self._metadata: - return data, self._metadata[mime] - else: - return data - - def __getitem__(self, key): - return self.metadata[key] - - def __getattr__(self, key): - if key not in self.metadata: - raise AttributeError(key) - return self.metadata[key] - - def __repr__(self): - execute_result = self.metadata['execute_result'] or {'data':{}} - text_out = execute_result['data'].get('text/plain', '') - if len(text_out) > 32: - text_out = text_out[:29] + '...' - - return "" % (self.execution_count, text_out) - - def _repr_pretty_(self, p, cycle): - execute_result = self.metadata['execute_result'] or {'data':{}} - text_out = execute_result['data'].get('text/plain', '') - - if not text_out: - return - - try: - ip = get_ipython() - except NameError: - colors = "NoColor" - else: - colors = ip.colors - - if colors == "NoColor": - out = normal = "" - else: - out = TermColors.Red - normal = TermColors.Normal - - if '\n' in text_out and not text_out.startswith('\n'): - # add newline for multiline reprs - text_out = '\n' + text_out - - p.text( - out + u'Out[%i:%i]: ' % ( - self.metadata['engine_id'], self.execution_count - ) + normal + text_out - ) - - -class Metadata(dict): - """Subclass of dict for initializing metadata values. - - Attribute access works on keys. - - These objects have a strict set of keys - errors will raise if you try - to add new keys. - """ - def __init__(self, *args, **kwargs): - dict.__init__(self) - md = {'msg_id' : None, - 'submitted' : None, - 'started' : None, - 'completed' : None, - 'received' : None, - 'engine_uuid' : None, - 'engine_id' : None, - 'follow' : None, - 'after' : None, - 'status' : None, - - 'execute_input' : None, - 'execute_result' : None, - 'error' : None, - 'stdout' : '', - 'stderr' : '', - 'outputs' : [], - 'data': {}, - 'outputs_ready' : False, - } - self.update(md) - self.update(dict(*args, **kwargs)) - - def __getattr__(self, key): - """getattr aliased to getitem""" - if key in self: - return self[key] - else: - raise AttributeError(key) - - def __setattr__(self, key, value): - """setattr aliased to setitem, with strict""" - if key in self: - self[key] = value - else: - raise AttributeError(key) - - def __setitem__(self, key, value): - """strict static key enforcement""" - if key in self: - dict.__setitem__(self, key, value) - else: - raise KeyError(key) - - -class Client(HasTraits): - """A semi-synchronous client to the IPython ZMQ cluster - - Parameters - ---------- - - url_file : str/unicode; path to ipcontroller-client.json - This JSON file should contain all the information needed to connect to a cluster, - and is likely the only argument needed. - Connection information for the Hub's registration. If a json connector - file is given, then likely no further configuration is necessary. - [Default: use profile] - profile : bytes - The name of the Cluster profile to be used to find connector information. - If run from an IPython application, the default profile will be the same - as the running application, otherwise it will be 'default'. - cluster_id : str - String id to added to runtime files, to prevent name collisions when using - multiple clusters with a single profile simultaneously. - When set, will look for files named like: 'ipcontroller--client.json' - Since this is text inserted into filenames, typical recommendations apply: - Simple character strings are ideal, and spaces are not recommended (but - should generally work) - context : zmq.Context - Pass an existing zmq.Context instance, otherwise the client will create its own. - debug : bool - flag for lots of message printing for debug purposes - timeout : int/float - time (in seconds) to wait for connection replies from the Hub - [Default: 10] - - #-------------- session related args ---------------- - - config : Config object - If specified, this will be relayed to the Session for configuration - username : str - set username for the session object - - #-------------- ssh related args ---------------- - # These are args for configuring the ssh tunnel to be used - # credentials are used to forward connections over ssh to the Controller - # Note that the ip given in `addr` needs to be relative to sshserver - # The most basic case is to leave addr as pointing to localhost (127.0.0.1), - # and set sshserver as the same machine the Controller is on. However, - # the only requirement is that sshserver is able to see the Controller - # (i.e. is within the same trusted network). - - sshserver : str - A string of the form passed to ssh, i.e. 'server.tld' or 'user@server.tld:port' - If keyfile or password is specified, and this is not, it will default to - the ip given in addr. - sshkey : str; path to ssh private key file - This specifies a key to be used in ssh login, default None. - Regular default ssh keys will be used without specifying this argument. - password : str - Your ssh password to sshserver. Note that if this is left None, - you will be prompted for it if passwordless key based login is unavailable. - paramiko : bool - flag for whether to use paramiko instead of shell ssh for tunneling. - [default: True on win32, False else] - - - Attributes - ---------- - - ids : list of int engine IDs - requesting the ids attribute always synchronizes - the registration state. To request ids without synchronization, - use semi-private _ids attributes. - - history : list of msg_ids - a list of msg_ids, keeping track of all the execution - messages you have submitted in order. - - outstanding : set of msg_ids - a set of msg_ids that have been submitted, but whose - results have not yet been received. - - results : dict - a dict of all our results, keyed by msg_id - - block : bool - determines default behavior when block not specified - in execution methods - - Methods - ------- - - spin - flushes incoming results and registration state changes - control methods spin, and requesting `ids` also ensures up to date - - wait - wait on one or more msg_ids - - execution methods - apply - legacy: execute, run - - data movement - push, pull, scatter, gather - - query methods - queue_status, get_result, purge, result_status - - control methods - abort, shutdown - - """ - - - block = Bool(False) - outstanding = Set() - results = Instance('collections.defaultdict', (dict,)) - metadata = Instance('collections.defaultdict', (Metadata,)) - history = List() - debug = Bool(False) - _spin_thread = Any() - _stop_spinning = Any() - - profile=Unicode() - def _profile_default(self): - if BaseIPythonApplication.initialized(): - # an IPython app *might* be running, try to get its profile - try: - return BaseIPythonApplication.instance().profile - except (AttributeError, MultipleInstanceError): - # could be a *different* subclass of config.Application, - # which would raise one of these two errors. - return u'default' - else: - return u'default' - - - _outstanding_dict = Instance('collections.defaultdict', (set,)) - _ids = List() - _connected=Bool(False) - _ssh=Bool(False) - _context = Instance('zmq.Context', allow_none=True) - _config = Dict() - _engines=Instance(util.ReverseDict, (), {}) - _query_socket=Instance('zmq.Socket', allow_none=True) - _control_socket=Instance('zmq.Socket', allow_none=True) - _iopub_socket=Instance('zmq.Socket', allow_none=True) - _notification_socket=Instance('zmq.Socket', allow_none=True) - _mux_socket=Instance('zmq.Socket', allow_none=True) - _task_socket=Instance('zmq.Socket', allow_none=True) - _task_scheme=Unicode() - _closed = False - _ignored_control_replies=Integer(0) - _ignored_hub_replies=Integer(0) - - def __new__(self, *args, **kw): - # don't raise on positional args - return HasTraits.__new__(self, **kw) - - def __init__(self, url_file=None, profile=None, profile_dir=None, ipython_dir=None, - context=None, debug=False, - sshserver=None, sshkey=None, password=None, paramiko=None, - timeout=10, cluster_id=None, **extra_args - ): - if profile: - super(Client, self).__init__(debug=debug, profile=profile) - else: - super(Client, self).__init__(debug=debug) - if context is None: - context = zmq.Context.instance() - self._context = context - self._stop_spinning = Event() - - if 'url_or_file' in extra_args: - url_file = extra_args['url_or_file'] - warnings.warn("url_or_file arg no longer supported, use url_file", DeprecationWarning) - - if url_file and util.is_url(url_file): - raise ValueError("single urls cannot be specified, url-files must be used.") - - self._setup_profile_dir(self.profile, profile_dir, ipython_dir) - - no_file_msg = '\n'.join([ - "You have attempted to connect to an IPython Cluster but no Controller could be found.", - "Please double-check your configuration and ensure that a cluster is running.", - ]) - - if self._cd is not None: - if url_file is None: - if not cluster_id: - client_json = 'ipcontroller-client.json' - else: - client_json = 'ipcontroller-%s-client.json' % cluster_id - url_file = pjoin(self._cd.security_dir, client_json) - if not os.path.exists(url_file): - msg = '\n'.join([ - "Connection file %r not found." % compress_user(url_file), - no_file_msg, - ]) - raise IOError(msg) - if url_file is None: - raise IOError(no_file_msg) - - if not os.path.exists(url_file): - # Connection file explicitly specified, but not found - raise IOError("Connection file %r not found. Is a controller running?" % \ - compress_user(url_file) - ) - - with open(url_file) as f: - cfg = json.load(f) - - self._task_scheme = cfg['task_scheme'] - - # sync defaults from args, json: - if sshserver: - cfg['ssh'] = sshserver - - location = cfg.setdefault('location', None) - - proto,addr = cfg['interface'].split('://') - addr = util.disambiguate_ip_address(addr, location) - cfg['interface'] = "%s://%s" % (proto, addr) - - # turn interface,port into full urls: - for key in ('control', 'task', 'mux', 'iopub', 'notification', 'registration'): - cfg[key] = cfg['interface'] + ':%i' % cfg[key] - - url = cfg['registration'] - - if location is not None and addr == localhost(): - # location specified, and connection is expected to be local - if not is_local_ip(location) and not sshserver: - # load ssh from JSON *only* if the controller is not on - # this machine - sshserver=cfg['ssh'] - if not is_local_ip(location) and not sshserver: - # warn if no ssh specified, but SSH is probably needed - # This is only a warning, because the most likely cause - # is a local Controller on a laptop whose IP is dynamic - warnings.warn(""" - Controller appears to be listening on localhost, but not on this machine. - If this is true, you should specify Client(...,sshserver='you@%s') - or instruct your controller to listen on an external IP."""%location, - RuntimeWarning) - elif not sshserver: - # otherwise sync with cfg - sshserver = cfg['ssh'] - - self._config = cfg - - self._ssh = bool(sshserver or sshkey or password) - if self._ssh and sshserver is None: - # default to ssh via localhost - sshserver = addr - if self._ssh and password is None: - from zmq.ssh import tunnel - if tunnel.try_passwordless_ssh(sshserver, sshkey, paramiko): - password=False - else: - password = getpass("SSH Password for %s: "%sshserver) - ssh_kwargs = dict(keyfile=sshkey, password=password, paramiko=paramiko) - - # configure and construct the session - try: - extra_args['packer'] = cfg['pack'] - extra_args['unpacker'] = cfg['unpack'] - extra_args['key'] = cast_bytes(cfg['key']) - extra_args['signature_scheme'] = cfg['signature_scheme'] - except KeyError as exc: - msg = '\n'.join([ - "Connection file is invalid (missing '{}'), possibly from an old version of IPython.", - "If you are reusing connection files, remove them and start ipcontroller again." - ]) - raise ValueError(msg.format(exc.message)) - - self.session = Session(**extra_args) - - self._query_socket = self._context.socket(zmq.DEALER) - - if self._ssh: - from zmq.ssh import tunnel - tunnel.tunnel_connection(self._query_socket, cfg['registration'], sshserver, **ssh_kwargs) - else: - self._query_socket.connect(cfg['registration']) - - self.session.debug = self.debug - - self._notification_handlers = {'registration_notification' : self._register_engine, - 'unregistration_notification' : self._unregister_engine, - 'shutdown_notification' : lambda msg: self.close(), - } - self._queue_handlers = {'execute_reply' : self._handle_execute_reply, - 'apply_reply' : self._handle_apply_reply} - - try: - self._connect(sshserver, ssh_kwargs, timeout) - except: - self.close(linger=0) - raise - - # last step: setup magics, if we are in IPython: - - try: - ip = get_ipython() - except NameError: - return - else: - if 'px' not in ip.magics_manager.magics: - # in IPython but we are the first Client. - # activate a default view for parallel magics. - self.activate() - - def __del__(self): - """cleanup sockets, but _not_ context.""" - self.close() - - def _setup_profile_dir(self, profile, profile_dir, ipython_dir): - if ipython_dir is None: - ipython_dir = get_ipython_dir() - if profile_dir is not None: - try: - self._cd = ProfileDir.find_profile_dir(profile_dir) - return - except ProfileDirError: - pass - elif profile is not None: - try: - self._cd = ProfileDir.find_profile_dir_by_name( - ipython_dir, profile) - return - except ProfileDirError: - pass - self._cd = None - - def _update_engines(self, engines): - """Update our engines dict and _ids from a dict of the form: {id:uuid}.""" - for k,v in iteritems(engines): - eid = int(k) - if eid not in self._engines: - self._ids.append(eid) - self._engines[eid] = v - self._ids = sorted(self._ids) - if sorted(self._engines.keys()) != list(range(len(self._engines))) and \ - self._task_scheme == 'pure' and self._task_socket: - self._stop_scheduling_tasks() - - def _stop_scheduling_tasks(self): - """Stop scheduling tasks because an engine has been unregistered - from a pure ZMQ scheduler. - """ - self._task_socket.close() - self._task_socket = None - msg = "An engine has been unregistered, and we are using pure " +\ - "ZMQ task scheduling. Task farming will be disabled." - if self.outstanding: - msg += " If you were running tasks when this happened, " +\ - "some `outstanding` msg_ids may never resolve." - warnings.warn(msg, RuntimeWarning) - - def _build_targets(self, targets): - """Turn valid target IDs or 'all' into two lists: - (int_ids, uuids). - """ - if not self._ids: - # flush notification socket if no engines yet, just in case - if not self.ids: - raise error.NoEnginesRegistered("Can't build targets without any engines") - - if targets is None: - targets = self._ids - elif isinstance(targets, string_types): - if targets.lower() == 'all': - targets = self._ids - else: - raise TypeError("%r not valid str target, must be 'all'"%(targets)) - elif isinstance(targets, int): - if targets < 0: - targets = self.ids[targets] - if targets not in self._ids: - raise IndexError("No such engine: %i"%targets) - targets = [targets] - - if isinstance(targets, slice): - indices = list(range(len(self._ids))[targets]) - ids = self.ids - targets = [ ids[i] for i in indices ] - - if not isinstance(targets, (tuple, list, xrange)): - raise TypeError("targets by int/slice/collection of ints only, not %s"%(type(targets))) - - return [cast_bytes(self._engines[t]) for t in targets], list(targets) - - def _connect(self, sshserver, ssh_kwargs, timeout): - """setup all our socket connections to the cluster. This is called from - __init__.""" - - # Maybe allow reconnecting? - if self._connected: - return - self._connected=True - - def connect_socket(s, url): - if self._ssh: - from zmq.ssh import tunnel - return tunnel.tunnel_connection(s, url, sshserver, **ssh_kwargs) - else: - return s.connect(url) - - self.session.send(self._query_socket, 'connection_request') - # use Poller because zmq.select has wrong units in pyzmq 2.1.7 - poller = zmq.Poller() - poller.register(self._query_socket, zmq.POLLIN) - # poll expects milliseconds, timeout is seconds - evts = poller.poll(timeout*1000) - if not evts: - raise error.TimeoutError("Hub connection request timed out") - idents,msg = self.session.recv(self._query_socket,mode=0) - if self.debug: - pprint(msg) - content = msg['content'] - # self._config['registration'] = dict(content) - cfg = self._config - if content['status'] == 'ok': - self._mux_socket = self._context.socket(zmq.DEALER) - connect_socket(self._mux_socket, cfg['mux']) - - self._task_socket = self._context.socket(zmq.DEALER) - connect_socket(self._task_socket, cfg['task']) - - self._notification_socket = self._context.socket(zmq.SUB) - self._notification_socket.setsockopt(zmq.SUBSCRIBE, b'') - connect_socket(self._notification_socket, cfg['notification']) - - self._control_socket = self._context.socket(zmq.DEALER) - connect_socket(self._control_socket, cfg['control']) - - self._iopub_socket = self._context.socket(zmq.SUB) - self._iopub_socket.setsockopt(zmq.SUBSCRIBE, b'') - connect_socket(self._iopub_socket, cfg['iopub']) - - self._update_engines(dict(content['engines'])) - else: - self._connected = False - raise Exception("Failed to connect!") - - #-------------------------------------------------------------------------- - # handlers and callbacks for incoming messages - #-------------------------------------------------------------------------- - - def _unwrap_exception(self, content): - """unwrap exception, and remap engine_id to int.""" - e = error.unwrap_exception(content) - # print e.traceback - if e.engine_info: - e_uuid = e.engine_info['engine_uuid'] - eid = self._engines[e_uuid] - e.engine_info['engine_id'] = eid - return e - - def _extract_metadata(self, msg): - header = msg['header'] - parent = msg['parent_header'] - msg_meta = msg['metadata'] - content = msg['content'] - md = {'msg_id' : parent['msg_id'], - 'received' : datetime.now(), - 'engine_uuid' : msg_meta.get('engine', None), - 'follow' : msg_meta.get('follow', []), - 'after' : msg_meta.get('after', []), - 'status' : content['status'], - } - - if md['engine_uuid'] is not None: - md['engine_id'] = self._engines.get(md['engine_uuid'], None) - - if 'date' in parent: - md['submitted'] = parent['date'] - if 'started' in msg_meta: - md['started'] = parse_date(msg_meta['started']) - if 'date' in header: - md['completed'] = header['date'] - return md - - def _register_engine(self, msg): - """Register a new engine, and update our connection info.""" - content = msg['content'] - eid = content['id'] - d = {eid : content['uuid']} - self._update_engines(d) - - def _unregister_engine(self, msg): - """Unregister an engine that has died.""" - content = msg['content'] - eid = int(content['id']) - if eid in self._ids: - self._ids.remove(eid) - uuid = self._engines.pop(eid) - - self._handle_stranded_msgs(eid, uuid) - - if self._task_socket and self._task_scheme == 'pure': - self._stop_scheduling_tasks() - - def _handle_stranded_msgs(self, eid, uuid): - """Handle messages known to be on an engine when the engine unregisters. - - It is possible that this will fire prematurely - that is, an engine will - go down after completing a result, and the client will be notified - of the unregistration and later receive the successful result. - """ - - outstanding = self._outstanding_dict[uuid] - - for msg_id in list(outstanding): - if msg_id in self.results: - # we already - continue - try: - raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id)) - except: - content = error.wrap_exception() - # build a fake message: - msg = self.session.msg('apply_reply', content=content) - msg['parent_header']['msg_id'] = msg_id - msg['metadata']['engine'] = uuid - self._handle_apply_reply(msg) - - def _handle_execute_reply(self, msg): - """Save the reply to an execute_request into our results. - - execute messages are never actually used. apply is used instead. - """ - - parent = msg['parent_header'] - msg_id = parent['msg_id'] - if msg_id not in self.outstanding: - if msg_id in self.history: - print("got stale result: %s"%msg_id) - else: - print("got unknown result: %s"%msg_id) - else: - self.outstanding.remove(msg_id) - - content = msg['content'] - header = msg['header'] - - # construct metadata: - md = self.metadata[msg_id] - md.update(self._extract_metadata(msg)) - # is this redundant? - self.metadata[msg_id] = md - - e_outstanding = self._outstanding_dict[md['engine_uuid']] - if msg_id in e_outstanding: - e_outstanding.remove(msg_id) - - # construct result: - if content['status'] == 'ok': - self.results[msg_id] = ExecuteReply(msg_id, content, md) - elif content['status'] == 'aborted': - self.results[msg_id] = error.TaskAborted(msg_id) - elif content['status'] == 'resubmitted': - # TODO: handle resubmission - pass - else: - self.results[msg_id] = self._unwrap_exception(content) - - def _handle_apply_reply(self, msg): - """Save the reply to an apply_request into our results.""" - parent = msg['parent_header'] - msg_id = parent['msg_id'] - if msg_id not in self.outstanding: - if msg_id in self.history: - print("got stale result: %s"%msg_id) - print(self.results[msg_id]) - print(msg) - else: - print("got unknown result: %s"%msg_id) - else: - self.outstanding.remove(msg_id) - content = msg['content'] - header = msg['header'] - - # construct metadata: - md = self.metadata[msg_id] - md.update(self._extract_metadata(msg)) - # is this redundant? - self.metadata[msg_id] = md - - e_outstanding = self._outstanding_dict[md['engine_uuid']] - if msg_id in e_outstanding: - e_outstanding.remove(msg_id) - - # construct result: - if content['status'] == 'ok': - self.results[msg_id] = serialize.deserialize_object(msg['buffers'])[0] - elif content['status'] == 'aborted': - self.results[msg_id] = error.TaskAborted(msg_id) - elif content['status'] == 'resubmitted': - # TODO: handle resubmission - pass - else: - self.results[msg_id] = self._unwrap_exception(content) - - def _flush_notifications(self): - """Flush notifications of engine registrations waiting - in ZMQ queue.""" - idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK) - while msg is not None: - if self.debug: - pprint(msg) - msg_type = msg['header']['msg_type'] - handler = self._notification_handlers.get(msg_type, None) - if handler is None: - raise Exception("Unhandled message type: %s" % msg_type) - else: - handler(msg) - idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK) - - def _flush_results(self, sock): - """Flush task or queue results waiting in ZMQ queue.""" - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - while msg is not None: - if self.debug: - pprint(msg) - msg_type = msg['header']['msg_type'] - handler = self._queue_handlers.get(msg_type, None) - if handler is None: - raise Exception("Unhandled message type: %s" % msg_type) - else: - handler(msg) - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - - def _flush_control(self, sock): - """Flush replies from the control channel waiting - in the ZMQ queue. - - Currently: ignore them.""" - if self._ignored_control_replies <= 0: - return - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - while msg is not None: - self._ignored_control_replies -= 1 - if self.debug: - pprint(msg) - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - - def _flush_ignored_control(self): - """flush ignored control replies""" - while self._ignored_control_replies > 0: - self.session.recv(self._control_socket) - self._ignored_control_replies -= 1 - - def _flush_ignored_hub_replies(self): - ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK) - while msg is not None: - ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK) - - def _flush_iopub(self, sock): - """Flush replies from the iopub channel waiting - in the ZMQ queue. - """ - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - while msg is not None: - if self.debug: - pprint(msg) - parent = msg['parent_header'] - if not parent or parent['session'] != self.session.session: - # ignore IOPub messages not from here - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - continue - msg_id = parent['msg_id'] - content = msg['content'] - header = msg['header'] - msg_type = msg['header']['msg_type'] - - if msg_type == 'status' and msg_id not in self.metadata: - # ignore status messages if they aren't mine - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - continue - - # init metadata: - md = self.metadata[msg_id] - - if msg_type == 'stream': - name = content['name'] - s = md[name] or '' - md[name] = s + content['text'] - elif msg_type == 'error': - md.update({'error' : self._unwrap_exception(content)}) - elif msg_type == 'execute_input': - md.update({'execute_input' : content['code']}) - elif msg_type == 'display_data': - md['outputs'].append(content) - elif msg_type == 'execute_result': - md['execute_result'] = content - elif msg_type == 'data_message': - data, remainder = serialize.deserialize_object(msg['buffers']) - md['data'].update(data) - elif msg_type == 'status': - # idle message comes after all outputs - if content['execution_state'] == 'idle': - md['outputs_ready'] = True - else: - # unhandled msg_type (status, etc.) - pass - - # reduntant? - self.metadata[msg_id] = md - - idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK) - - #-------------------------------------------------------------------------- - # len, getitem - #-------------------------------------------------------------------------- - - def __len__(self): - """len(client) returns # of engines.""" - return len(self.ids) - - def __getitem__(self, key): - """index access returns DirectView multiplexer objects - - Must be int, slice, or list/tuple/xrange of ints""" - if not isinstance(key, (int, slice, tuple, list, xrange)): - raise TypeError("key by int/slice/iterable of ints only, not %s"%(type(key))) - else: - return self.direct_view(key) - - def __iter__(self): - """Since we define getitem, Client is iterable - - but unless we also define __iter__, it won't work correctly unless engine IDs - start at zero and are continuous. - """ - for eid in self.ids: - yield self.direct_view(eid) - - #-------------------------------------------------------------------------- - # Begin public methods - #-------------------------------------------------------------------------- - - @property - def ids(self): - """Always up-to-date ids property.""" - self._flush_notifications() - # always copy: - return list(self._ids) - - def activate(self, targets='all', suffix=''): - """Create a DirectView and register it with IPython magics - - Defines the magics `%px, %autopx, %pxresult, %%px` - - Parameters - ---------- - - targets: int, list of ints, or 'all' - The engines on which the view's magics will run - suffix: str [default: ''] - The suffix, if any, for the magics. This allows you to have - multiple views associated with parallel magics at the same time. - - e.g. ``rc.activate(targets=0, suffix='0')`` will give you - the magics ``%px0``, ``%pxresult0``, etc. for running magics just - on engine 0. - """ - view = self.direct_view(targets) - view.block = True - view.activate(suffix) - return view - - def close(self, linger=None): - """Close my zmq Sockets - - If `linger`, set the zmq LINGER socket option, - which allows discarding of messages. - """ - if self._closed: - return - self.stop_spin_thread() - snames = [ trait for trait in self.trait_names() if trait.endswith("socket") ] - for name in snames: - socket = getattr(self, name) - if socket is not None and not socket.closed: - if linger is not None: - socket.close(linger=linger) - else: - socket.close() - self._closed = True - - def _spin_every(self, interval=1): - """target func for use in spin_thread""" - while True: - if self._stop_spinning.is_set(): - return - time.sleep(interval) - self.spin() - - def spin_thread(self, interval=1): - """call Client.spin() in a background thread on some regular interval - - This helps ensure that messages don't pile up too much in the zmq queue - while you are working on other things, or just leaving an idle terminal. - - It also helps limit potential padding of the `received` timestamp - on AsyncResult objects, used for timings. - - Parameters - ---------- - - interval : float, optional - The interval on which to spin the client in the background thread - (simply passed to time.sleep). - - Notes - ----- - - For precision timing, you may want to use this method to put a bound - on the jitter (in seconds) in `received` timestamps used - in AsyncResult.wall_time. - - """ - if self._spin_thread is not None: - self.stop_spin_thread() - self._stop_spinning.clear() - self._spin_thread = Thread(target=self._spin_every, args=(interval,)) - self._spin_thread.daemon = True - self._spin_thread.start() - - def stop_spin_thread(self): - """stop background spin_thread, if any""" - if self._spin_thread is not None: - self._stop_spinning.set() - self._spin_thread.join() - self._spin_thread = None - - def spin(self): - """Flush any registration notifications and execution results - waiting in the ZMQ queue. - """ - if self._notification_socket: - self._flush_notifications() - if self._iopub_socket: - self._flush_iopub(self._iopub_socket) - if self._mux_socket: - self._flush_results(self._mux_socket) - if self._task_socket: - self._flush_results(self._task_socket) - if self._control_socket: - self._flush_control(self._control_socket) - if self._query_socket: - self._flush_ignored_hub_replies() - - def wait(self, jobs=None, timeout=-1): - """waits on one or more `jobs`, for up to `timeout` seconds. - - Parameters - ---------- - - jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects - ints are indices to self.history - strs are msg_ids - default: wait on all outstanding messages - timeout : float - a time in seconds, after which to give up. - default is -1, which means no timeout - - Returns - ------- - - True : when all msg_ids are done - False : timeout reached, some msg_ids still outstanding - """ - tic = time.time() - if jobs is None: - theids = self.outstanding - else: - if isinstance(jobs, string_types + (int, AsyncResult)): - jobs = [jobs] - theids = set() - for job in jobs: - if isinstance(job, int): - # index access - job = self.history[job] - elif isinstance(job, AsyncResult): - theids.update(job.msg_ids) - continue - theids.add(job) - if not theids.intersection(self.outstanding): - return True - self.spin() - while theids.intersection(self.outstanding): - if timeout >= 0 and ( time.time()-tic ) > timeout: - break - time.sleep(1e-3) - self.spin() - return len(theids.intersection(self.outstanding)) == 0 - - #-------------------------------------------------------------------------- - # Control methods - #-------------------------------------------------------------------------- - - @spin_first - def clear(self, targets=None, block=None): - """Clear the namespace in target(s).""" - block = self.block if block is None else block - targets = self._build_targets(targets)[0] - for t in targets: - self.session.send(self._control_socket, 'clear_request', content={}, ident=t) - error = False - if block: - self._flush_ignored_control() - for i in range(len(targets)): - idents,msg = self.session.recv(self._control_socket,0) - if self.debug: - pprint(msg) - if msg['content']['status'] != 'ok': - error = self._unwrap_exception(msg['content']) - else: - self._ignored_control_replies += len(targets) - if error: - raise error - - - @spin_first - def abort(self, jobs=None, targets=None, block=None): - """Abort specific jobs from the execution queues of target(s). - - This is a mechanism to prevent jobs that have already been submitted - from executing. - - Parameters - ---------- - - jobs : msg_id, list of msg_ids, or AsyncResult - The jobs to be aborted - - If unspecified/None: abort all outstanding jobs. - - """ - block = self.block if block is None else block - jobs = jobs if jobs is not None else list(self.outstanding) - targets = self._build_targets(targets)[0] - - msg_ids = [] - if isinstance(jobs, string_types + (AsyncResult,)): - jobs = [jobs] - bad_ids = [obj for obj in jobs if not isinstance(obj, string_types + (AsyncResult,))] - if bad_ids: - raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0]) - for j in jobs: - if isinstance(j, AsyncResult): - msg_ids.extend(j.msg_ids) - else: - msg_ids.append(j) - content = dict(msg_ids=msg_ids) - for t in targets: - self.session.send(self._control_socket, 'abort_request', - content=content, ident=t) - error = False - if block: - self._flush_ignored_control() - for i in range(len(targets)): - idents,msg = self.session.recv(self._control_socket,0) - if self.debug: - pprint(msg) - if msg['content']['status'] != 'ok': - error = self._unwrap_exception(msg['content']) - else: - self._ignored_control_replies += len(targets) - if error: - raise error - - @spin_first - def shutdown(self, targets='all', restart=False, hub=False, block=None): - """Terminates one or more engine processes, optionally including the hub. - - Parameters - ---------- - - targets: list of ints or 'all' [default: all] - Which engines to shutdown. - hub: bool [default: False] - Whether to include the Hub. hub=True implies targets='all'. - block: bool [default: self.block] - Whether to wait for clean shutdown replies or not. - restart: bool [default: False] - NOT IMPLEMENTED - whether to restart engines after shutting them down. - """ - from ipython_parallel.error import NoEnginesRegistered - if restart: - raise NotImplementedError("Engine restart is not yet implemented") - - block = self.block if block is None else block - if hub: - targets = 'all' - try: - targets = self._build_targets(targets)[0] - except NoEnginesRegistered: - targets = [] - for t in targets: - self.session.send(self._control_socket, 'shutdown_request', - content={'restart':restart},ident=t) - error = False - if block or hub: - self._flush_ignored_control() - for i in range(len(targets)): - idents,msg = self.session.recv(self._control_socket, 0) - if self.debug: - pprint(msg) - if msg['content']['status'] != 'ok': - error = self._unwrap_exception(msg['content']) - else: - self._ignored_control_replies += len(targets) - - if hub: - time.sleep(0.25) - self.session.send(self._query_socket, 'shutdown_request') - idents,msg = self.session.recv(self._query_socket, 0) - if self.debug: - pprint(msg) - if msg['content']['status'] != 'ok': - error = self._unwrap_exception(msg['content']) - - if error: - raise error - - #-------------------------------------------------------------------------- - # Execution related methods - #-------------------------------------------------------------------------- - - def _maybe_raise(self, result): - """wrapper for maybe raising an exception if apply failed.""" - if isinstance(result, error.RemoteError): - raise result - - return result - - def send_apply_request(self, socket, f, args=None, kwargs=None, metadata=None, track=False, - ident=None): - """construct and send an apply message via a socket. - - This is the principal method with which all engine execution is performed by views. - """ - - if self._closed: - raise RuntimeError("Client cannot be used after its sockets have been closed") - - # defaults: - args = args if args is not None else [] - kwargs = kwargs if kwargs is not None else {} - metadata = metadata if metadata is not None else {} - - # validate arguments - if not callable(f) and not isinstance(f, Reference): - raise TypeError("f must be callable, not %s"%type(f)) - if not isinstance(args, (tuple, list)): - raise TypeError("args must be tuple or list, not %s"%type(args)) - if not isinstance(kwargs, dict): - raise TypeError("kwargs must be dict, not %s"%type(kwargs)) - if not isinstance(metadata, dict): - raise TypeError("metadata must be dict, not %s"%type(metadata)) - - bufs = serialize.pack_apply_message(f, args, kwargs, - buffer_threshold=self.session.buffer_threshold, - item_threshold=self.session.item_threshold, - ) - - msg = self.session.send(socket, "apply_request", buffers=bufs, ident=ident, - metadata=metadata, track=track) - - msg_id = msg['header']['msg_id'] - self.outstanding.add(msg_id) - if ident: - # possibly routed to a specific engine - if isinstance(ident, list): - ident = ident[-1] - if ident in self._engines.values(): - # save for later, in case of engine death - self._outstanding_dict[ident].add(msg_id) - self.history.append(msg_id) - self.metadata[msg_id]['submitted'] = datetime.now() - - return msg - - def send_execute_request(self, socket, code, silent=True, metadata=None, ident=None): - """construct and send an execute request via a socket. - - """ - - if self._closed: - raise RuntimeError("Client cannot be used after its sockets have been closed") - - # defaults: - metadata = metadata if metadata is not None else {} - - # validate arguments - if not isinstance(code, string_types): - raise TypeError("code must be text, not %s" % type(code)) - if not isinstance(metadata, dict): - raise TypeError("metadata must be dict, not %s" % type(metadata)) - - content = dict(code=code, silent=bool(silent), user_expressions={}) - - - msg = self.session.send(socket, "execute_request", content=content, ident=ident, - metadata=metadata) - - msg_id = msg['header']['msg_id'] - self.outstanding.add(msg_id) - if ident: - # possibly routed to a specific engine - if isinstance(ident, list): - ident = ident[-1] - if ident in self._engines.values(): - # save for later, in case of engine death - self._outstanding_dict[ident].add(msg_id) - self.history.append(msg_id) - self.metadata[msg_id]['submitted'] = datetime.now() - - return msg - - #-------------------------------------------------------------------------- - # construct a View object - #-------------------------------------------------------------------------- - - def load_balanced_view(self, targets=None): - """construct a DirectView object. - - If no arguments are specified, create a LoadBalancedView - using all engines. - - Parameters - ---------- - - targets: list,slice,int,etc. [default: use all engines] - The subset of engines across which to load-balance - """ - if targets == 'all': - targets = None - if targets is not None: - targets = self._build_targets(targets)[1] - return LoadBalancedView(client=self, socket=self._task_socket, targets=targets) - - def direct_view(self, targets='all'): - """construct a DirectView object. - - If no targets are specified, create a DirectView using all engines. - - rc.direct_view('all') is distinguished from rc[:] in that 'all' will - evaluate the target engines at each execution, whereas rc[:] will connect to - all *current* engines, and that list will not change. - - That is, 'all' will always use all engines, whereas rc[:] will not use - engines added after the DirectView is constructed. - - Parameters - ---------- - - targets: list,slice,int,etc. [default: use all engines] - The engines to use for the View - """ - single = isinstance(targets, int) - # allow 'all' to be lazily evaluated at each execution - if targets != 'all': - targets = self._build_targets(targets)[1] - if single: - targets = targets[0] - return DirectView(client=self, socket=self._mux_socket, targets=targets) - - #-------------------------------------------------------------------------- - # Query methods - #-------------------------------------------------------------------------- - - @spin_first - def get_result(self, indices_or_msg_ids=None, block=None, owner=True): - """Retrieve a result by msg_id or history index, wrapped in an AsyncResult object. - - If the client already has the results, no request to the Hub will be made. - - This is a convenient way to construct AsyncResult objects, which are wrappers - that include metadata about execution, and allow for awaiting results that - were not submitted by this Client. - - It can also be a convenient way to retrieve the metadata associated with - blocking execution, since it always retrieves - - Examples - -------- - :: - - In [10]: r = client.apply() - - Parameters - ---------- - - indices_or_msg_ids : integer history index, str msg_id, or list of either - The indices or msg_ids of indices to be retrieved - - block : bool - Whether to wait for the result to be done - owner : bool [default: True] - Whether this AsyncResult should own the result. - If so, calling `ar.get()` will remove data from the - client's result and metadata cache. - There should only be one owner of any given msg_id. - - Returns - ------- - - AsyncResult - A single AsyncResult object will always be returned. - - AsyncHubResult - A subclass of AsyncResult that retrieves results from the Hub - - """ - block = self.block if block is None else block - if indices_or_msg_ids is None: - indices_or_msg_ids = -1 - - single_result = False - if not isinstance(indices_or_msg_ids, (list,tuple)): - indices_or_msg_ids = [indices_or_msg_ids] - single_result = True - - theids = [] - for id in indices_or_msg_ids: - if isinstance(id, int): - id = self.history[id] - if not isinstance(id, string_types): - raise TypeError("indices must be str or int, not %r"%id) - theids.append(id) - - local_ids = [msg_id for msg_id in theids if (msg_id in self.outstanding or msg_id in self.results)] - remote_ids = [msg_id for msg_id in theids if msg_id not in local_ids] - - # given single msg_id initially, get_result shot get the result itself, - # not a length-one list - if single_result: - theids = theids[0] - - if remote_ids: - ar = AsyncHubResult(self, msg_ids=theids, owner=owner) - else: - ar = AsyncResult(self, msg_ids=theids, owner=owner) - - if block: - ar.wait() - - return ar - - @spin_first - def resubmit(self, indices_or_msg_ids=None, metadata=None, block=None): - """Resubmit one or more tasks. - - in-flight tasks may not be resubmitted. - - Parameters - ---------- - - indices_or_msg_ids : integer history index, str msg_id, or list of either - The indices or msg_ids of indices to be retrieved - - block : bool - Whether to wait for the result to be done - - Returns - ------- - - AsyncHubResult - A subclass of AsyncResult that retrieves results from the Hub - - """ - block = self.block if block is None else block - if indices_or_msg_ids is None: - indices_or_msg_ids = -1 - - if not isinstance(indices_or_msg_ids, (list,tuple)): - indices_or_msg_ids = [indices_or_msg_ids] - - theids = [] - for id in indices_or_msg_ids: - if isinstance(id, int): - id = self.history[id] - if not isinstance(id, string_types): - raise TypeError("indices must be str or int, not %r"%id) - theids.append(id) - - content = dict(msg_ids = theids) - - self.session.send(self._query_socket, 'resubmit_request', content) - - zmq.select([self._query_socket], [], []) - idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK) - if self.debug: - pprint(msg) - content = msg['content'] - if content['status'] != 'ok': - raise self._unwrap_exception(content) - mapping = content['resubmitted'] - new_ids = [ mapping[msg_id] for msg_id in theids ] - - ar = AsyncHubResult(self, msg_ids=new_ids) - - if block: - ar.wait() - - return ar - - @spin_first - def result_status(self, msg_ids, status_only=True): - """Check on the status of the result(s) of the apply request with `msg_ids`. - - If status_only is False, then the actual results will be retrieved, else - only the status of the results will be checked. - - Parameters - ---------- - - msg_ids : list of msg_ids - if int: - Passed as index to self.history for convenience. - status_only : bool (default: True) - if False: - Retrieve the actual results of completed tasks. - - Returns - ------- - - results : dict - There will always be the keys 'pending' and 'completed', which will - be lists of msg_ids that are incomplete or complete. If `status_only` - is False, then completed results will be keyed by their `msg_id`. - """ - if not isinstance(msg_ids, (list,tuple)): - msg_ids = [msg_ids] - - theids = [] - for msg_id in msg_ids: - if isinstance(msg_id, int): - msg_id = self.history[msg_id] - if not isinstance(msg_id, string_types): - raise TypeError("msg_ids must be str, not %r"%msg_id) - theids.append(msg_id) - - completed = [] - local_results = {} - - # comment this block out to temporarily disable local shortcut: - for msg_id in theids: - if msg_id in self.results: - completed.append(msg_id) - local_results[msg_id] = self.results[msg_id] - theids.remove(msg_id) - - if theids: # some not locally cached - content = dict(msg_ids=theids, status_only=status_only) - msg = self.session.send(self._query_socket, "result_request", content=content) - zmq.select([self._query_socket], [], []) - idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK) - if self.debug: - pprint(msg) - content = msg['content'] - if content['status'] != 'ok': - raise self._unwrap_exception(content) - buffers = msg['buffers'] - else: - content = dict(completed=[],pending=[]) - - content['completed'].extend(completed) - - if status_only: - return content - - failures = [] - # load cached results into result: - content.update(local_results) - - # update cache with results: - for msg_id in sorted(theids): - if msg_id in content['completed']: - rec = content[msg_id] - parent = extract_dates(rec['header']) - header = extract_dates(rec['result_header']) - rcontent = rec['result_content'] - iodict = rec['io'] - if isinstance(rcontent, str): - rcontent = self.session.unpack(rcontent) - - md = self.metadata[msg_id] - md_msg = dict( - content=rcontent, - parent_header=parent, - header=header, - metadata=rec['result_metadata'], - ) - md.update(self._extract_metadata(md_msg)) - if rec.get('received'): - md['received'] = parse_date(rec['received']) - md.update(iodict) - - if rcontent['status'] == 'ok': - if header['msg_type'] == 'apply_reply': - res,buffers = serialize.deserialize_object(buffers) - elif header['msg_type'] == 'execute_reply': - res = ExecuteReply(msg_id, rcontent, md) - else: - raise KeyError("unhandled msg type: %r" % header['msg_type']) - else: - res = self._unwrap_exception(rcontent) - failures.append(res) - - self.results[msg_id] = res - content[msg_id] = res - - if len(theids) == 1 and failures: - raise failures[0] - - error.collect_exceptions(failures, "result_status") - return content - - @spin_first - def queue_status(self, targets='all', verbose=False): - """Fetch the status of engine queues. - - Parameters - ---------- - - targets : int/str/list of ints/strs - the engines whose states are to be queried. - default : all - verbose : bool - Whether to return lengths only, or lists of ids for each element - """ - if targets == 'all': - # allow 'all' to be evaluated on the engine - engine_ids = None - else: - engine_ids = self._build_targets(targets)[1] - content = dict(targets=engine_ids, verbose=verbose) - self.session.send(self._query_socket, "queue_request", content=content) - idents,msg = self.session.recv(self._query_socket, 0) - if self.debug: - pprint(msg) - content = msg['content'] - status = content.pop('status') - if status != 'ok': - raise self._unwrap_exception(content) - content = rekey(content) - if isinstance(targets, int): - return content[targets] - else: - return content - - def _build_msgids_from_target(self, targets=None): - """Build a list of msg_ids from the list of engine targets""" - if not targets: # needed as _build_targets otherwise uses all engines - return [] - target_ids = self._build_targets(targets)[0] - return [md_id for md_id in self.metadata if self.metadata[md_id]["engine_uuid"] in target_ids] - - def _build_msgids_from_jobs(self, jobs=None): - """Build a list of msg_ids from "jobs" """ - if not jobs: - return [] - msg_ids = [] - if isinstance(jobs, string_types + (AsyncResult,)): - jobs = [jobs] - bad_ids = [obj for obj in jobs if not isinstance(obj, string_types + (AsyncResult,))] - if bad_ids: - raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0]) - for j in jobs: - if isinstance(j, AsyncResult): - msg_ids.extend(j.msg_ids) - else: - msg_ids.append(j) - return msg_ids - - def purge_local_results(self, jobs=[], targets=[]): - """Clears the client caches of results and their metadata. - - Individual results can be purged by msg_id, or the entire - history of specific targets can be purged. - - Use `purge_local_results('all')` to scrub everything from the Clients's - results and metadata caches. - - After this call all `AsyncResults` are invalid and should be discarded. - - If you must "reget" the results, you can still do so by using - `client.get_result(msg_id)` or `client.get_result(asyncresult)`. This will - redownload the results from the hub if they are still available - (i.e `client.purge_hub_results(...)` has not been called. - - Parameters - ---------- - - jobs : str or list of str or AsyncResult objects - the msg_ids whose results should be purged. - targets : int/list of ints - The engines, by integer ID, whose entire result histories are to be purged. - - Raises - ------ - - RuntimeError : if any of the tasks to be purged are still outstanding. - - """ - if not targets and not jobs: - raise ValueError("Must specify at least one of `targets` and `jobs`") - - if jobs == 'all': - if self.outstanding: - raise RuntimeError("Can't purge outstanding tasks: %s" % self.outstanding) - self.results.clear() - self.metadata.clear() - else: - msg_ids = set() - msg_ids.update(self._build_msgids_from_target(targets)) - msg_ids.update(self._build_msgids_from_jobs(jobs)) - still_outstanding = self.outstanding.intersection(msg_ids) - if still_outstanding: - raise RuntimeError("Can't purge outstanding tasks: %s" % still_outstanding) - for mid in msg_ids: - self.results.pop(mid, None) - self.metadata.pop(mid, None) - - - @spin_first - def purge_hub_results(self, jobs=[], targets=[]): - """Tell the Hub to forget results. - - Individual results can be purged by msg_id, or the entire - history of specific targets can be purged. - - Use `purge_results('all')` to scrub everything from the Hub's db. - - Parameters - ---------- - - jobs : str or list of str or AsyncResult objects - the msg_ids whose results should be forgotten. - targets : int/str/list of ints/strs - The targets, by int_id, whose entire history is to be purged. - - default : None - """ - if not targets and not jobs: - raise ValueError("Must specify at least one of `targets` and `jobs`") - if targets: - targets = self._build_targets(targets)[1] - - # construct msg_ids from jobs - if jobs == 'all': - msg_ids = jobs - else: - msg_ids = self._build_msgids_from_jobs(jobs) - - content = dict(engine_ids=targets, msg_ids=msg_ids) - self.session.send(self._query_socket, "purge_request", content=content) - idents, msg = self.session.recv(self._query_socket, 0) - if self.debug: - pprint(msg) - content = msg['content'] - if content['status'] != 'ok': - raise self._unwrap_exception(content) - - def purge_results(self, jobs=[], targets=[]): - """Clears the cached results from both the hub and the local client - - Individual results can be purged by msg_id, or the entire - history of specific targets can be purged. - - Use `purge_results('all')` to scrub every cached result from both the Hub's and - the Client's db. - - Equivalent to calling both `purge_hub_results()` and `purge_client_results()` with - the same arguments. - - Parameters - ---------- - - jobs : str or list of str or AsyncResult objects - the msg_ids whose results should be forgotten. - targets : int/str/list of ints/strs - The targets, by int_id, whose entire history is to be purged. - - default : None - """ - self.purge_local_results(jobs=jobs, targets=targets) - self.purge_hub_results(jobs=jobs, targets=targets) - - def purge_everything(self): - """Clears all content from previous Tasks from both the hub and the local client - - In addition to calling `purge_results("all")` it also deletes the history and - other bookkeeping lists. - """ - self.purge_results("all") - self.history = [] - self.session.digest_history.clear() - - @spin_first - def hub_history(self): - """Get the Hub's history - - Just like the Client, the Hub has a history, which is a list of msg_ids. - This will contain the history of all clients, and, depending on configuration, - may contain history across multiple cluster sessions. - - Any msg_id returned here is a valid argument to `get_result`. - - Returns - ------- - - msg_ids : list of strs - list of all msg_ids, ordered by task submission time. - """ - - self.session.send(self._query_socket, "history_request", content={}) - idents, msg = self.session.recv(self._query_socket, 0) - - if self.debug: - pprint(msg) - content = msg['content'] - if content['status'] != 'ok': - raise self._unwrap_exception(content) - else: - return content['history'] - - @spin_first - def db_query(self, query, keys=None): - """Query the Hub's TaskRecord database - - This will return a list of task record dicts that match `query` - - Parameters - ---------- - - query : mongodb query dict - The search dict. See mongodb query docs for details. - keys : list of strs [optional] - The subset of keys to be returned. The default is to fetch everything but buffers. - 'msg_id' will *always* be included. - """ - if isinstance(keys, string_types): - keys = [keys] - content = dict(query=query, keys=keys) - self.session.send(self._query_socket, "db_request", content=content) - idents, msg = self.session.recv(self._query_socket, 0) - if self.debug: - pprint(msg) - content = msg['content'] - if content['status'] != 'ok': - raise self._unwrap_exception(content) - - records = content['records'] - - buffer_lens = content['buffer_lens'] - result_buffer_lens = content['result_buffer_lens'] - buffers = msg['buffers'] - has_bufs = buffer_lens is not None - has_rbufs = result_buffer_lens is not None - for i,rec in enumerate(records): - # unpack datetime objects - for hkey in ('header', 'result_header'): - if hkey in rec: - rec[hkey] = extract_dates(rec[hkey]) - for dtkey in ('submitted', 'started', 'completed', 'received'): - if dtkey in rec: - rec[dtkey] = parse_date(rec[dtkey]) - # relink buffers - if has_bufs: - blen = buffer_lens[i] - rec['buffers'], buffers = buffers[:blen],buffers[blen:] - if has_rbufs: - blen = result_buffer_lens[i] - rec['result_buffers'], buffers = buffers[:blen],buffers[blen:] - - return records - -__all__ = [ 'Client' ] diff --git a/ipython_parallel/client/magics.py b/ipython_parallel/client/magics.py deleted file mode 100644 index da13046..0000000 --- a/ipython_parallel/client/magics.py +++ /dev/null @@ -1,436 +0,0 @@ -# encoding: utf-8 -""" -============= -parallelmagic -============= - -Magic command interface for interactive parallel work. - -Usage -===== - -``%autopx`` - -{AUTOPX_DOC} - -``%px`` - -{PX_DOC} - -``%pxresult`` - -{RESULT_DOC} - -``%pxconfig`` - -{CONFIG_DOC} - -""" -from __future__ import print_function - -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import ast -import re - -from IPython.core.error import UsageError -from IPython.core.magic import Magics -from IPython.core import magic_arguments -from IPython.utils.text import dedent - -#----------------------------------------------------------------------------- -# Definitions of magic functions for use with IPython -#----------------------------------------------------------------------------- - - -NO_LAST_RESULT = "%pxresult recalls last %px result, which has not yet been used." - -def exec_args(f): - """decorator for adding block/targets args for execution - - applied to %pxconfig and %%px - """ - args = [ - magic_arguments.argument('-b', '--block', action="store_const", - const=True, dest='block', - help="use blocking (sync) execution", - ), - magic_arguments.argument('-a', '--noblock', action="store_const", - const=False, dest='block', - help="use non-blocking (async) execution", - ), - magic_arguments.argument('-t', '--targets', type=str, - help="specify the targets on which to execute", - ), - magic_arguments.argument('--local', action="store_const", - const=True, dest="local", - help="also execute the cell in the local namespace", - ), - magic_arguments.argument('--verbose', action="store_const", - const=True, dest="set_verbose", - help="print a message at each execution", - ), - magic_arguments.argument('--no-verbose', action="store_const", - const=False, dest="set_verbose", - help="don't print any messages", - ), - ] - for a in args: - f = a(f) - return f - -def output_args(f): - """decorator for output-formatting args - - applied to %pxresult and %%px - """ - args = [ - magic_arguments.argument('-r', action="store_const", dest='groupby', - const='order', - help="collate outputs in order (same as group-outputs=order)" - ), - magic_arguments.argument('-e', action="store_const", dest='groupby', - const='engine', - help="group outputs by engine (same as group-outputs=engine)" - ), - magic_arguments.argument('--group-outputs', dest='groupby', type=str, - choices=['engine', 'order', 'type'], default='type', - help="""Group the outputs in a particular way. - - Choices are: - - **type**: group outputs of all engines by type (stdout, stderr, displaypub, etc.). - **engine**: display all output for each engine together. - **order**: like type, but individual displaypub output from each engine is collated. - For example, if multiple plots are generated by each engine, the first - figure of each engine will be displayed, then the second of each, etc. - """ - ), - magic_arguments.argument('-o', '--out', dest='save_name', type=str, - help="""store the AsyncResult object for this computation - in the global namespace under this name. - """ - ), - ] - for a in args: - f = a(f) - return f - -class ParallelMagics(Magics): - """A set of magics useful when controlling a parallel IPython cluster. - """ - - # magic-related - magics = None - registered = True - - # suffix for magics - suffix = '' - # A flag showing if autopx is activated or not - _autopx = False - # the current view used by the magics: - view = None - # last result cache for %pxresult - last_result = None - # verbose flag - verbose = False - - def __init__(self, shell, view, suffix=''): - self.view = view - self.suffix = suffix - - # register magics - self.magics = dict(cell={},line={}) - line_magics = self.magics['line'] - - px = 'px' + suffix - if not suffix: - # keep %result for legacy compatibility - line_magics['result'] = self.result - - line_magics['pxresult' + suffix] = self.result - line_magics[px] = self.px - line_magics['pxconfig' + suffix] = self.pxconfig - line_magics['auto' + px] = self.autopx - - self.magics['cell'][px] = self.cell_px - - super(ParallelMagics, self).__init__(shell=shell) - - def _eval_target_str(self, ts): - if ':' in ts: - targets = eval("self.view.client.ids[%s]" % ts) - elif 'all' in ts: - targets = 'all' - else: - targets = eval(ts) - return targets - - @magic_arguments.magic_arguments() - @exec_args - def pxconfig(self, line): - """configure default targets/blocking for %px magics""" - args = magic_arguments.parse_argstring(self.pxconfig, line) - if args.targets: - self.view.targets = self._eval_target_str(args.targets) - if args.block is not None: - self.view.block = args.block - if args.set_verbose is not None: - self.verbose = args.set_verbose - - @magic_arguments.magic_arguments() - @output_args - def result(self, line=''): - """Print the result of the last asynchronous %px command. - - This lets you recall the results of %px computations after - asynchronous submission (block=False). - - Examples - -------- - :: - - In [23]: %px os.getpid() - Async parallel execution on engine(s): all - - In [24]: %pxresult - Out[8:10]: 60920 - Out[9:10]: 60921 - Out[10:10]: 60922 - Out[11:10]: 60923 - """ - args = magic_arguments.parse_argstring(self.result, line) - - if self.last_result is None: - raise UsageError(NO_LAST_RESULT) - - self.last_result.get() - self.last_result.display_outputs(groupby=args.groupby) - - def px(self, line=''): - """Executes the given python command in parallel. - - Examples - -------- - :: - - In [24]: %px a = os.getpid() - Parallel execution on engine(s): all - - In [25]: %px print a - [stdout:0] 1234 - [stdout:1] 1235 - [stdout:2] 1236 - [stdout:3] 1237 - """ - return self.parallel_execute(line) - - def parallel_execute(self, cell, block=None, groupby='type', save_name=None): - """implementation used by %px and %%parallel""" - - # defaults: - block = self.view.block if block is None else block - - base = "Parallel" if block else "Async parallel" - - targets = self.view.targets - if isinstance(targets, list) and len(targets) > 10: - str_targets = str(targets[:4])[:-1] + ', ..., ' + str(targets[-4:])[1:] - else: - str_targets = str(targets) - if self.verbose: - print(base + " execution on engine(s): %s" % str_targets) - - result = self.view.execute(cell, silent=False, block=False) - self.last_result = result - - if save_name: - self.shell.user_ns[save_name] = result - - if block: - result.get() - result.display_outputs(groupby) - else: - # return AsyncResult only on non-blocking submission - return result - - @magic_arguments.magic_arguments() - @exec_args - @output_args - def cell_px(self, line='', cell=None): - """Executes the cell in parallel. - - Examples - -------- - :: - - In [24]: %%px --noblock - ....: a = os.getpid() - Async parallel execution on engine(s): all - - In [25]: %%px - ....: print a - [stdout:0] 1234 - [stdout:1] 1235 - [stdout:2] 1236 - [stdout:3] 1237 - """ - - args = magic_arguments.parse_argstring(self.cell_px, line) - - if args.targets: - save_targets = self.view.targets - self.view.targets = self._eval_target_str(args.targets) - # if running local, don't block until after local has run - block = False if args.local else args.block - try: - ar = self.parallel_execute(cell, block=block, - groupby=args.groupby, - save_name=args.save_name, - ) - finally: - if args.targets: - self.view.targets = save_targets - - # run locally after submitting remote - block = self.view.block if args.block is None else args.block - if args.local: - self.shell.run_cell(cell) - # now apply blocking behavor to remote execution - if block: - ar.get() - ar.display_outputs(args.groupby) - if not block: - return ar - - def autopx(self, line=''): - """Toggles auto parallel mode. - - Once this is called, all commands typed at the command line are send to - the engines to be executed in parallel. To control which engine are - used, the ``targets`` attribute of the view before - entering ``%autopx`` mode. - - - Then you can do the following:: - - In [25]: %autopx - %autopx to enabled - - In [26]: a = 10 - Parallel execution on engine(s): [0,1,2,3] - In [27]: print a - Parallel execution on engine(s): [0,1,2,3] - [stdout:0] 10 - [stdout:1] 10 - [stdout:2] 10 - [stdout:3] 10 - - - In [27]: %autopx - %autopx disabled - """ - if self._autopx: - self._disable_autopx() - else: - self._enable_autopx() - - def _enable_autopx(self): - """Enable %autopx mode by saving the original run_cell and installing - pxrun_cell. - """ - # override run_cell - self._original_run_cell = self.shell.run_cell - self.shell.run_cell = self.pxrun_cell - - self._autopx = True - print("%autopx enabled") - - def _disable_autopx(self): - """Disable %autopx by restoring the original InteractiveShell.run_cell. - """ - if self._autopx: - self.shell.run_cell = self._original_run_cell - self._autopx = False - print("%autopx disabled") - - def pxrun_cell(self, raw_cell, store_history=False, silent=False): - """drop-in replacement for InteractiveShell.run_cell. - - This executes code remotely, instead of in the local namespace. - - See InteractiveShell.run_cell for details. - """ - - if (not raw_cell) or raw_cell.isspace(): - return - - ipself = self.shell - - with ipself.builtin_trap: - cell = ipself.prefilter_manager.prefilter_lines(raw_cell) - - # Store raw and processed history - if store_history: - ipself.history_manager.store_inputs(ipself.execution_count, - cell, raw_cell) - - # ipself.logger.log(cell, raw_cell) - - cell_name = ipself.compile.cache(cell, ipself.execution_count) - - try: - ast.parse(cell, filename=cell_name) - except (OverflowError, SyntaxError, ValueError, TypeError, - MemoryError): - # Case 1 - ipself.showsyntaxerror() - ipself.execution_count += 1 - return None - except NameError: - # ignore name errors, because we don't know the remote keys - pass - - if store_history: - # Write output to the database. Does nothing unless - # history output logging is enabled. - ipself.history_manager.store_output(ipself.execution_count) - # Each cell is a *single* input, regardless of how many lines it has - ipself.execution_count += 1 - if re.search(r'get_ipython\(\)\.magic\(u?["\']%?autopx', cell): - self._disable_autopx() - return False - else: - try: - result = self.view.execute(cell, silent=False, block=False) - except: - ipself.showtraceback() - return True - else: - if self.view.block: - try: - result.get() - except: - self.shell.showtraceback() - return True - else: - with ipself.builtin_trap: - result.display_outputs() - return False - - -__doc__ = __doc__.format( - AUTOPX_DOC = dedent(ParallelMagics.autopx.__doc__), - PX_DOC = dedent(ParallelMagics.px.__doc__), - RESULT_DOC = dedent(ParallelMagics.result.__doc__), - CONFIG_DOC = dedent(ParallelMagics.pxconfig.__doc__), -) diff --git a/ipython_parallel/client/map.py b/ipython_parallel/client/map.py deleted file mode 100644 index a3b908a..0000000 --- a/ipython_parallel/client/map.py +++ /dev/null @@ -1,124 +0,0 @@ -# encoding: utf-8 - -"""Classes used in scattering and gathering sequences. - -Scattering consists of partitioning a sequence and sending the various -pieces to individual nodes in a cluster. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import division - -import sys -from itertools import islice, chain - -numpy = None - -def is_array(obj): - """Is an object a numpy array? - - Avoids importing numpy until it is requested - """ - global numpy - if 'numpy' not in sys.modules: - return False - - if numpy is None: - import numpy - return isinstance(obj, numpy.ndarray) - -class Map(object): - """A class for partitioning a sequence using a map.""" - - def getPartition(self, seq, p, q, n=None): - """Returns the pth partition of q partitions of seq. - - The length can be specified as `n`, - otherwise it is the value of `len(seq)` - """ - n = len(seq) if n is None else n - # Test for error conditions here - if p<0 or p>=q: - raise ValueError("must have 0 <= p <= q, but have p=%s,q=%s" % (p, q)) - - remainder = n % q - basesize = n // q - - if p < remainder: - low = p * (basesize + 1) - high = low + basesize + 1 - else: - low = p * basesize + remainder - high = low + basesize - - try: - result = seq[low:high] - except TypeError: - # some objects (iterators) can't be sliced, - # use islice: - result = list(islice(seq, low, high)) - - return result - - def joinPartitions(self, listOfPartitions): - return self.concatenate(listOfPartitions) - - def concatenate(self, listOfPartitions): - testObject = listOfPartitions[0] - # First see if we have a known array type - if is_array(testObject): - return numpy.concatenate(listOfPartitions) - # Next try for Python sequence types - if isinstance(testObject, (list, tuple)): - return list(chain.from_iterable(listOfPartitions)) - # If we have scalars, just return listOfPartitions - return listOfPartitions - -class RoundRobinMap(Map): - """Partitions a sequence in a round robin fashion. - - This currently does not work! - """ - - def getPartition(self, seq, p, q, n=None): - n = len(seq) if n is None else n - return seq[p:n:q] - - def joinPartitions(self, listOfPartitions): - testObject = listOfPartitions[0] - # First see if we have a known array type - if is_array(testObject): - return self.flatten_array(listOfPartitions) - if isinstance(testObject, (list, tuple)): - return self.flatten_list(listOfPartitions) - return listOfPartitions - - def flatten_array(self, listOfPartitions): - test = listOfPartitions[0] - shape = list(test.shape) - shape[0] = sum([ p.shape[0] for p in listOfPartitions]) - A = numpy.ndarray(shape) - N = shape[0] - q = len(listOfPartitions) - for p,part in enumerate(listOfPartitions): - A[p:N:q] = part - return A - - def flatten_list(self, listOfPartitions): - flat = [] - for i in range(len(listOfPartitions[0])): - flat.extend([ part[i] for part in listOfPartitions if len(part) > i ]) - return flat - -def mappable(obj): - """return whether an object is mappable or not.""" - if isinstance(obj, (tuple,list)): - return True - if is_array(obj): - return True - return False - -dists = {'b':Map,'r':RoundRobinMap} - diff --git a/ipython_parallel/client/remotefunction.py b/ipython_parallel/client/remotefunction.py deleted file mode 100644 index 620d4f8..0000000 --- a/ipython_parallel/client/remotefunction.py +++ /dev/null @@ -1,273 +0,0 @@ -"""Remote Functions and decorators for Views.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import division - -import sys -import warnings - -from decorator import decorator - -from . import map as Map -from .asyncresult import AsyncMapResult - -#----------------------------------------------------------------------------- -# Functions and Decorators -#----------------------------------------------------------------------------- - -def remote(view, block=None, **flags): - """Turn a function into a remote function. - - This method can be used for map: - - In [1]: @remote(view,block=True) - ...: def func(a): - ...: pass - """ - - def remote_function(f): - return RemoteFunction(view, f, block=block, **flags) - return remote_function - -def parallel(view, dist='b', block=None, ordered=True, **flags): - """Turn a function into a parallel remote function. - - This method can be used for map: - - In [1]: @parallel(view, block=True) - ...: def func(a): - ...: pass - """ - - def parallel_function(f): - return ParallelFunction(view, f, dist=dist, block=block, ordered=ordered, **flags) - return parallel_function - -def getname(f): - """Get the name of an object. - - For use in case of callables that are not functions, and - thus may not have __name__ defined. - - Order: f.__name__ > f.name > str(f) - """ - try: - return f.__name__ - except: - pass - try: - return f.name - except: - pass - - return str(f) - -@decorator -def sync_view_results(f, self, *args, **kwargs): - """sync relevant results from self.client to our results attribute. - - This is a clone of view.sync_results, but for remote functions - """ - view = self.view - if view._in_sync_results: - return f(self, *args, **kwargs) - view._in_sync_results = True - try: - ret = f(self, *args, **kwargs) - finally: - view._in_sync_results = False - view._sync_results() - return ret - -#-------------------------------------------------------------------------- -# Classes -#-------------------------------------------------------------------------- - -class RemoteFunction(object): - """Turn an existing function into a remote function. - - Parameters - ---------- - - view : View instance - The view to be used for execution - f : callable - The function to be wrapped into a remote function - block : bool [default: None] - Whether to wait for results or not. The default behavior is - to use the current `block` attribute of `view` - - **flags : remaining kwargs are passed to View.temp_flags - """ - - view = None # the remote connection - func = None # the wrapped function - block = None # whether to block - flags = None # dict of extra kwargs for temp_flags - - def __init__(self, view, f, block=None, **flags): - self.view = view - self.func = f - self.block=block - self.flags=flags - - def __call__(self, *args, **kwargs): - block = self.view.block if self.block is None else self.block - with self.view.temp_flags(block=block, **self.flags): - return self.view.apply(self.func, *args, **kwargs) - - -class ParallelFunction(RemoteFunction): - """Class for mapping a function to sequences. - - This will distribute the sequences according the a mapper, and call - the function on each sub-sequence. If called via map, then the function - will be called once on each element, rather that each sub-sequence. - - Parameters - ---------- - - view : View instance - The view to be used for execution - f : callable - The function to be wrapped into a remote function - dist : str [default: 'b'] - The key for which mapObject to use to distribute sequences - options are: - - * 'b' : use contiguous chunks in order - * 'r' : use round-robin striping - - block : bool [default: None] - Whether to wait for results or not. The default behavior is - to use the current `block` attribute of `view` - chunksize : int or None - The size of chunk to use when breaking up sequences in a load-balanced manner - ordered : bool [default: True] - Whether the result should be kept in order. If False, - results become available as they arrive, regardless of submission order. - **flags - remaining kwargs are passed to View.temp_flags - """ - - chunksize = None - ordered = None - mapObject = None - _mapping = False - - def __init__(self, view, f, dist='b', block=None, chunksize=None, ordered=True, **flags): - super(ParallelFunction, self).__init__(view, f, block=block, **flags) - self.chunksize = chunksize - self.ordered = ordered - - mapClass = Map.dists[dist] - self.mapObject = mapClass() - - @sync_view_results - def __call__(self, *sequences): - client = self.view.client - - lens = [] - maxlen = minlen = -1 - for i, seq in enumerate(sequences): - try: - n = len(seq) - except Exception: - seq = list(seq) - if isinstance(sequences, tuple): - # can't alter a tuple - sequences = list(sequences) - sequences[i] = seq - n = len(seq) - if n > maxlen: - maxlen = n - if minlen == -1 or n < minlen: - minlen = n - lens.append(n) - - if maxlen == 0: - # nothing to iterate over - return [] - - # check that the length of sequences match - if not self._mapping and minlen != maxlen: - msg = 'all sequences must have equal length, but have %s' % lens - raise ValueError(msg) - - balanced = 'Balanced' in self.view.__class__.__name__ - if balanced: - if self.chunksize: - nparts = maxlen // self.chunksize + int(maxlen % self.chunksize > 0) - else: - nparts = maxlen - targets = [None]*nparts - else: - if self.chunksize: - warnings.warn("`chunksize` is ignored unless load balancing", UserWarning) - # multiplexed: - targets = self.view.targets - # 'all' is lazily evaluated at execution time, which is now: - if targets == 'all': - targets = client._build_targets(targets)[1] - elif isinstance(targets, int): - # single-engine view, targets must be iterable - targets = [targets] - nparts = len(targets) - - msg_ids = [] - for index, t in enumerate(targets): - args = [] - for seq in sequences: - part = self.mapObject.getPartition(seq, index, nparts, maxlen) - args.append(part) - - if sum([len(arg) for arg in args]) == 0: - continue - - if self._mapping: - if sys.version_info[0] >= 3: - f = lambda f, *sequences: list(map(f, *sequences)) - else: - f = map - args = [self.func] + args - else: - f=self.func - - view = self.view if balanced else client[t] - with view.temp_flags(block=False, **self.flags): - ar = view.apply(f, *args) - - msg_ids.extend(ar.msg_ids) - - r = AsyncMapResult(self.view.client, msg_ids, self.mapObject, - fname=getname(self.func), - ordered=self.ordered - ) - - if self.block: - try: - return r.get() - except KeyboardInterrupt: - return r - else: - return r - - def map(self, *sequences): - """call a function on each element of one or more sequence(s) remotely. - This should behave very much like the builtin map, but return an AsyncMapResult - if self.block is False. - - That means it can take generators (will be cast to lists locally), - and mismatched sequence lengths will be padded with None. - """ - # set _mapping as a flag for use inside self.__call__ - self._mapping = True - try: - ret = self(*sequences) - finally: - self._mapping = False - return ret - -__all__ = ['remote', 'parallel', 'RemoteFunction', 'ParallelFunction'] diff --git a/ipython_parallel/client/view.py b/ipython_parallel/client/view.py deleted file mode 100644 index 3b62c77..0000000 --- a/ipython_parallel/client/view.py +++ /dev/null @@ -1,1121 +0,0 @@ -"""Views of remote engines.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import imp -import sys -import warnings -from contextlib import contextmanager -from types import ModuleType - -import zmq - -from IPython.utils import pickleutil -from IPython.utils.traitlets import ( - HasTraits, Any, Bool, List, Dict, Set, Instance, CFloat, Integer -) -from decorator import decorator - -from ipython_parallel import util -from ipython_parallel.controller.dependency import Dependency, dependent -from IPython.utils.py3compat import string_types, iteritems, PY3 - -from . import map as Map -from .asyncresult import AsyncResult, AsyncMapResult -from .remotefunction import ParallelFunction, parallel, remote, getname - -#----------------------------------------------------------------------------- -# Decorators -#----------------------------------------------------------------------------- - -@decorator -def save_ids(f, self, *args, **kwargs): - """Keep our history and outstanding attributes up to date after a method call.""" - n_previous = len(self.client.history) - try: - ret = f(self, *args, **kwargs) - finally: - nmsgs = len(self.client.history) - n_previous - msg_ids = self.client.history[-nmsgs:] - self.history.extend(msg_ids) - self.outstanding.update(msg_ids) - return ret - -@decorator -def sync_results(f, self, *args, **kwargs): - """sync relevant results from self.client to our results attribute.""" - if self._in_sync_results: - return f(self, *args, **kwargs) - self._in_sync_results = True - try: - ret = f(self, *args, **kwargs) - finally: - self._in_sync_results = False - self._sync_results() - return ret - -@decorator -def spin_after(f, self, *args, **kwargs): - """call spin after the method.""" - ret = f(self, *args, **kwargs) - self.spin() - return ret - -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - -class View(HasTraits): - """Base View class for more convenint apply(f,*args,**kwargs) syntax via attributes. - - Don't use this class, use subclasses. - - Methods - ------- - - spin - flushes incoming results and registration state changes - control methods spin, and requesting `ids` also ensures up to date - - wait - wait on one or more msg_ids - - execution methods - apply - legacy: execute, run - - data movement - push, pull, scatter, gather - - query methods - get_result, queue_status, purge_results, result_status - - control methods - abort, shutdown - - """ - # flags - block=Bool(False) - track=Bool(True) - targets = Any() - - history=List() - outstanding = Set() - results = Dict() - client = Instance('ipython_parallel.Client', allow_none=True) - - _socket = Instance('zmq.Socket', allow_none=True) - _flag_names = List(['targets', 'block', 'track']) - _in_sync_results = Bool(False) - _targets = Any() - _idents = Any() - - def __init__(self, client=None, socket=None, **flags): - super(View, self).__init__(client=client, _socket=socket) - self.results = client.results - self.block = client.block - - self.set_flags(**flags) - - assert not self.__class__ is View, "Don't use base View objects, use subclasses" - - def __repr__(self): - strtargets = str(self.targets) - if len(strtargets) > 16: - strtargets = strtargets[:12]+'...]' - return "<%s %s>"%(self.__class__.__name__, strtargets) - - def __len__(self): - if isinstance(self.targets, list): - return len(self.targets) - elif isinstance(self.targets, int): - return 1 - else: - return len(self.client) - - def set_flags(self, **kwargs): - """set my attribute flags by keyword. - - Views determine behavior with a few attributes (`block`, `track`, etc.). - These attributes can be set all at once by name with this method. - - Parameters - ---------- - - block : bool - whether to wait for results - track : bool - whether to create a MessageTracker to allow the user to - safely edit after arrays and buffers during non-copying - sends. - """ - for name, value in iteritems(kwargs): - if name not in self._flag_names: - raise KeyError("Invalid name: %r"%name) - else: - setattr(self, name, value) - - @contextmanager - def temp_flags(self, **kwargs): - """temporarily set flags, for use in `with` statements. - - See set_flags for permanent setting of flags - - Examples - -------- - - >>> view.track=False - ... - >>> with view.temp_flags(track=True): - ... ar = view.apply(dostuff, my_big_array) - ... ar.tracker.wait() # wait for send to finish - >>> view.track - False - - """ - # preflight: save flags, and set temporaries - saved_flags = {} - for f in self._flag_names: - saved_flags[f] = getattr(self, f) - self.set_flags(**kwargs) - # yield to the with-statement block - try: - yield - finally: - # postflight: restore saved flags - self.set_flags(**saved_flags) - - - #---------------------------------------------------------------- - # apply - #---------------------------------------------------------------- - - def _sync_results(self): - """to be called by @sync_results decorator - - after submitting any tasks. - """ - delta = self.outstanding.difference(self.client.outstanding) - completed = self.outstanding.intersection(delta) - self.outstanding = self.outstanding.difference(completed) - - @sync_results - @save_ids - def _really_apply(self, f, args, kwargs, block=None, **options): - """wrapper for client.send_apply_request""" - raise NotImplementedError("Implement in subclasses") - - def apply(self, f, *args, **kwargs): - """calls ``f(*args, **kwargs)`` on remote engines, returning the result. - - This method sets all apply flags via this View's attributes. - - Returns :class:`~IPython.parallel.client.asyncresult.AsyncResult` - instance if ``self.block`` is False, otherwise the return value of - ``f(*args, **kwargs)``. - """ - return self._really_apply(f, args, kwargs) - - def apply_async(self, f, *args, **kwargs): - """calls ``f(*args, **kwargs)`` on remote engines in a nonblocking manner. - - Returns :class:`~IPython.parallel.client.asyncresult.AsyncResult` instance. - """ - return self._really_apply(f, args, kwargs, block=False) - - @spin_after - def apply_sync(self, f, *args, **kwargs): - """calls ``f(*args, **kwargs)`` on remote engines in a blocking manner, - returning the result. - """ - return self._really_apply(f, args, kwargs, block=True) - - #---------------------------------------------------------------- - # wrappers for client and control methods - #---------------------------------------------------------------- - @sync_results - def spin(self): - """spin the client, and sync""" - self.client.spin() - - @sync_results - def wait(self, jobs=None, timeout=-1): - """waits on one or more `jobs`, for up to `timeout` seconds. - - Parameters - ---------- - - jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects - ints are indices to self.history - strs are msg_ids - default: wait on all outstanding messages - timeout : float - a time in seconds, after which to give up. - default is -1, which means no timeout - - Returns - ------- - - True : when all msg_ids are done - False : timeout reached, some msg_ids still outstanding - """ - if jobs is None: - jobs = self.history - return self.client.wait(jobs, timeout) - - def abort(self, jobs=None, targets=None, block=None): - """Abort jobs on my engines. - - Parameters - ---------- - - jobs : None, str, list of strs, optional - if None: abort all jobs. - else: abort specific msg_id(s). - """ - block = block if block is not None else self.block - targets = targets if targets is not None else self.targets - jobs = jobs if jobs is not None else list(self.outstanding) - - return self.client.abort(jobs=jobs, targets=targets, block=block) - - def queue_status(self, targets=None, verbose=False): - """Fetch the Queue status of my engines""" - targets = targets if targets is not None else self.targets - return self.client.queue_status(targets=targets, verbose=verbose) - - def purge_results(self, jobs=[], targets=[]): - """Instruct the controller to forget specific results.""" - if targets is None or targets == 'all': - targets = self.targets - return self.client.purge_results(jobs=jobs, targets=targets) - - def shutdown(self, targets=None, restart=False, hub=False, block=None): - """Terminates one or more engine processes, optionally including the hub. - """ - block = self.block if block is None else block - if targets is None or targets == 'all': - targets = self.targets - return self.client.shutdown(targets=targets, restart=restart, hub=hub, block=block) - - @spin_after - def get_result(self, indices_or_msg_ids=None, block=None, owner=True): - """return one or more results, specified by history index or msg_id. - - See :meth:`IPython.parallel.client.client.Client.get_result` for details. - """ - - if indices_or_msg_ids is None: - indices_or_msg_ids = -1 - if isinstance(indices_or_msg_ids, int): - indices_or_msg_ids = self.history[indices_or_msg_ids] - elif isinstance(indices_or_msg_ids, (list,tuple,set)): - indices_or_msg_ids = list(indices_or_msg_ids) - for i,index in enumerate(indices_or_msg_ids): - if isinstance(index, int): - indices_or_msg_ids[i] = self.history[index] - return self.client.get_result(indices_or_msg_ids, block=block, owner=owner) - - #------------------------------------------------------------------- - # Map - #------------------------------------------------------------------- - - @sync_results - def map(self, f, *sequences, **kwargs): - """override in subclasses""" - raise NotImplementedError - - def map_async(self, f, *sequences, **kwargs): - """Parallel version of builtin :func:`python:map`, using this view's engines. - - This is equivalent to ``map(...block=False)``. - - See `self.map` for details. - """ - if 'block' in kwargs: - raise TypeError("map_async doesn't take a `block` keyword argument.") - kwargs['block'] = False - return self.map(f,*sequences,**kwargs) - - def map_sync(self, f, *sequences, **kwargs): - """Parallel version of builtin :func:`python:map`, using this view's engines. - - This is equivalent to ``map(...block=True)``. - - See `self.map` for details. - """ - if 'block' in kwargs: - raise TypeError("map_sync doesn't take a `block` keyword argument.") - kwargs['block'] = True - return self.map(f,*sequences,**kwargs) - - def imap(self, f, *sequences, **kwargs): - """Parallel version of :func:`itertools.imap`. - - See `self.map` for details. - - """ - - return iter(self.map_async(f,*sequences, **kwargs)) - - #------------------------------------------------------------------- - # Decorators - #------------------------------------------------------------------- - - def remote(self, block=None, **flags): - """Decorator for making a RemoteFunction""" - block = self.block if block is None else block - return remote(self, block=block, **flags) - - def parallel(self, dist='b', block=None, **flags): - """Decorator for making a ParallelFunction""" - block = self.block if block is None else block - return parallel(self, dist=dist, block=block, **flags) - -class DirectView(View): - """Direct Multiplexer View of one or more engines. - - These are created via indexed access to a client: - - >>> dv_1 = client[1] - >>> dv_all = client[:] - >>> dv_even = client[::2] - >>> dv_some = client[1:3] - - This object provides dictionary access to engine namespaces: - - # push a=5: - >>> dv['a'] = 5 - # pull 'foo': - >>> dv['foo'] - - """ - - def __init__(self, client=None, socket=None, targets=None): - super(DirectView, self).__init__(client=client, socket=socket, targets=targets) - - @property - def importer(self): - """sync_imports(local=True) as a property. - - See sync_imports for details. - - """ - return self.sync_imports(True) - - @contextmanager - def sync_imports(self, local=True, quiet=False): - """Context Manager for performing simultaneous local and remote imports. - - 'import x as y' will *not* work. The 'as y' part will simply be ignored. - - If `local=True`, then the package will also be imported locally. - - If `quiet=True`, no output will be produced when attempting remote - imports. - - Note that remote-only (`local=False`) imports have not been implemented. - - >>> with view.sync_imports(): - ... from numpy import recarray - importing recarray from numpy on engine(s) - - """ - from IPython.utils.py3compat import builtin_mod - local_import = builtin_mod.__import__ - modules = set() - results = [] - @util.interactive - def remote_import(name, fromlist, level): - """the function to be passed to apply, that actually performs the import - on the engine, and loads up the user namespace. - """ - import sys - user_ns = globals() - mod = __import__(name, fromlist=fromlist, level=level) - if fromlist: - for key in fromlist: - user_ns[key] = getattr(mod, key) - else: - user_ns[name] = sys.modules[name] - - def view_import(name, globals={}, locals={}, fromlist=[], level=0): - """the drop-in replacement for __import__, that optionally imports - locally as well. - """ - # don't override nested imports - save_import = builtin_mod.__import__ - builtin_mod.__import__ = local_import - - if imp.lock_held(): - # this is a side-effect import, don't do it remotely, or even - # ignore the local effects - return local_import(name, globals, locals, fromlist, level) - - imp.acquire_lock() - if local: - mod = local_import(name, globals, locals, fromlist, level) - else: - raise NotImplementedError("remote-only imports not yet implemented") - imp.release_lock() - - key = name+':'+','.join(fromlist or []) - if level <= 0 and key not in modules: - modules.add(key) - if not quiet: - if fromlist: - print("importing %s from %s on engine(s)"%(','.join(fromlist), name)) - else: - print("importing %s on engine(s)"%name) - results.append(self.apply_async(remote_import, name, fromlist, level)) - # restore override - builtin_mod.__import__ = save_import - - return mod - - # override __import__ - builtin_mod.__import__ = view_import - try: - # enter the block - yield - except ImportError: - if local: - raise - else: - # ignore import errors if not doing local imports - pass - finally: - # always restore __import__ - builtin_mod.__import__ = local_import - - for r in results: - # raise possible remote ImportErrors here - r.get() - - def use_dill(self): - """Expand serialization support with dill - - adds support for closures, etc. - - This calls ipython_kernel.pickleutil.use_dill() here and on each engine. - """ - pickleutil.use_dill() - return self.apply(pickleutil.use_dill) - - def use_cloudpickle(self): - """Expand serialization support with cloudpickle. - """ - pickleutil.use_cloudpickle() - return self.apply(pickleutil.use_cloudpickle) - - - @sync_results - @save_ids - def _really_apply(self, f, args=None, kwargs=None, targets=None, block=None, track=None): - """calls f(*args, **kwargs) on remote engines, returning the result. - - This method sets all of `apply`'s flags via this View's attributes. - - Parameters - ---------- - - f : callable - - args : list [default: empty] - - kwargs : dict [default: empty] - - targets : target list [default: self.targets] - where to run - block : bool [default: self.block] - whether to block - track : bool [default: self.track] - whether to ask zmq to track the message, for safe non-copying sends - - Returns - ------- - - if self.block is False: - returns AsyncResult - else: - returns actual result of f(*args, **kwargs) on the engine(s) - This will be a list of self.targets is also a list (even length 1), or - the single result if self.targets is an integer engine id - """ - args = [] if args is None else args - kwargs = {} if kwargs is None else kwargs - block = self.block if block is None else block - track = self.track if track is None else track - targets = self.targets if targets is None else targets - - _idents, _targets = self.client._build_targets(targets) - msg_ids = [] - trackers = [] - for ident in _idents: - msg = self.client.send_apply_request(self._socket, f, args, kwargs, track=track, - ident=ident) - if track: - trackers.append(msg['tracker']) - msg_ids.append(msg['header']['msg_id']) - if isinstance(targets, int): - msg_ids = msg_ids[0] - tracker = None if track is False else zmq.MessageTracker(*trackers) - ar = AsyncResult(self.client, msg_ids, fname=getname(f), targets=_targets, - tracker=tracker, owner=True, - ) - if block: - try: - return ar.get() - except KeyboardInterrupt: - pass - return ar - - - @sync_results - def map(self, f, *sequences, **kwargs): - """``view.map(f, *sequences, block=self.block)`` => list|AsyncMapResult - - Parallel version of builtin `map`, using this View's `targets`. - - There will be one task per target, so work will be chunked - if the sequences are longer than `targets`. - - Results can be iterated as they are ready, but will become available in chunks. - - Parameters - ---------- - - f : callable - function to be mapped - *sequences: one or more sequences of matching length - the sequences to be distributed and passed to `f` - block : bool - whether to wait for the result or not [default self.block] - - Returns - ------- - - - If block=False - An :class:`~ipython_parallel.client.asyncresult.AsyncMapResult` instance. - An object like AsyncResult, but which reassembles the sequence of results - into a single list. AsyncMapResults can be iterated through before all - results are complete. - else - A list, the result of ``map(f,*sequences)`` - """ - - block = kwargs.pop('block', self.block) - for k in kwargs.keys(): - if k not in ['block', 'track']: - raise TypeError("invalid keyword arg, %r"%k) - - assert len(sequences) > 0, "must have some sequences to map onto!" - pf = ParallelFunction(self, f, block=block, **kwargs) - return pf.map(*sequences) - - @sync_results - @save_ids - def execute(self, code, silent=True, targets=None, block=None): - """Executes `code` on `targets` in blocking or nonblocking manner. - - ``execute`` is always `bound` (affects engine namespace) - - Parameters - ---------- - - code : str - the code string to be executed - block : bool - whether or not to wait until done to return - default: self.block - """ - block = self.block if block is None else block - targets = self.targets if targets is None else targets - - _idents, _targets = self.client._build_targets(targets) - msg_ids = [] - trackers = [] - for ident in _idents: - msg = self.client.send_execute_request(self._socket, code, silent=silent, ident=ident) - msg_ids.append(msg['header']['msg_id']) - if isinstance(targets, int): - msg_ids = msg_ids[0] - ar = AsyncResult(self.client, msg_ids, fname='execute', targets=_targets, owner=True) - if block: - try: - ar.get() - except KeyboardInterrupt: - pass - return ar - - def run(self, filename, targets=None, block=None): - """Execute contents of `filename` on my engine(s). - - This simply reads the contents of the file and calls `execute`. - - Parameters - ---------- - - filename : str - The path to the file - targets : int/str/list of ints/strs - the engines on which to execute - default : all - block : bool - whether or not to wait until done - default: self.block - - """ - with open(filename, 'r') as f: - # add newline in case of trailing indented whitespace - # which will cause SyntaxError - code = f.read()+'\n' - return self.execute(code, block=block, targets=targets) - - def update(self, ns): - """update remote namespace with dict `ns` - - See `push` for details. - """ - return self.push(ns, block=self.block, track=self.track) - - def push(self, ns, targets=None, block=None, track=None): - """update remote namespace with dict `ns` - - Parameters - ---------- - - ns : dict - dict of keys with which to update engine namespace(s) - block : bool [default : self.block] - whether to wait to be notified of engine receipt - - """ - - block = block if block is not None else self.block - track = track if track is not None else self.track - targets = targets if targets is not None else self.targets - # applier = self.apply_sync if block else self.apply_async - if not isinstance(ns, dict): - raise TypeError("Must be a dict, not %s"%type(ns)) - return self._really_apply(util._push, kwargs=ns, block=block, track=track, targets=targets) - - def get(self, key_s): - """get object(s) by `key_s` from remote namespace - - see `pull` for details. - """ - # block = block if block is not None else self.block - return self.pull(key_s, block=True) - - def pull(self, names, targets=None, block=None): - """get object(s) by `name` from remote namespace - - will return one object if it is a key. - can also take a list of keys, in which case it will return a list of objects. - """ - block = block if block is not None else self.block - targets = targets if targets is not None else self.targets - applier = self.apply_sync if block else self.apply_async - if isinstance(names, string_types): - pass - elif isinstance(names, (list,tuple,set)): - for key in names: - if not isinstance(key, string_types): - raise TypeError("keys must be str, not type %r"%type(key)) - else: - raise TypeError("names must be strs, not %r"%names) - return self._really_apply(util._pull, (names,), block=block, targets=targets) - - def scatter(self, key, seq, dist='b', flatten=False, targets=None, block=None, track=None): - """ - Partition a Python sequence and send the partitions to a set of engines. - """ - block = block if block is not None else self.block - track = track if track is not None else self.track - targets = targets if targets is not None else self.targets - - # construct integer ID list: - targets = self.client._build_targets(targets)[1] - - mapObject = Map.dists[dist]() - nparts = len(targets) - msg_ids = [] - trackers = [] - for index, engineid in enumerate(targets): - partition = mapObject.getPartition(seq, index, nparts) - if flatten and len(partition) == 1: - ns = {key: partition[0]} - else: - ns = {key: partition} - r = self.push(ns, block=False, track=track, targets=engineid) - msg_ids.extend(r.msg_ids) - if track: - trackers.append(r._tracker) - - if track: - tracker = zmq.MessageTracker(*trackers) - else: - tracker = None - - r = AsyncResult(self.client, msg_ids, fname='scatter', targets=targets, - tracker=tracker, owner=True, - ) - if block: - r.wait() - else: - return r - - @sync_results - @save_ids - def gather(self, key, dist='b', targets=None, block=None): - """ - Gather a partitioned sequence on a set of engines as a single local seq. - """ - block = block if block is not None else self.block - targets = targets if targets is not None else self.targets - mapObject = Map.dists[dist]() - msg_ids = [] - - # construct integer ID list: - targets = self.client._build_targets(targets)[1] - - for index, engineid in enumerate(targets): - msg_ids.extend(self.pull(key, block=False, targets=engineid).msg_ids) - - r = AsyncMapResult(self.client, msg_ids, mapObject, fname='gather') - - if block: - try: - return r.get() - except KeyboardInterrupt: - pass - return r - - def __getitem__(self, key): - return self.get(key) - - def __setitem__(self,key, value): - self.update({key:value}) - - def clear(self, targets=None, block=None): - """Clear the remote namespaces on my engines.""" - block = block if block is not None else self.block - targets = targets if targets is not None else self.targets - return self.client.clear(targets=targets, block=block) - - #---------------------------------------- - # activate for %px, %autopx, etc. magics - #---------------------------------------- - - def activate(self, suffix=''): - """Activate IPython magics associated with this View - - Defines the magics `%px, %autopx, %pxresult, %%px, %pxconfig` - - Parameters - ---------- - - suffix: str [default: ''] - The suffix, if any, for the magics. This allows you to have - multiple views associated with parallel magics at the same time. - - e.g. ``rc[::2].activate(suffix='_even')`` will give you - the magics ``%px_even``, ``%pxresult_even``, etc. for running magics - on the even engines. - """ - - from IPython.parallel.client.magics import ParallelMagics - - try: - # This is injected into __builtins__. - ip = get_ipython() - except NameError: - print("The IPython parallel magics (%px, etc.) only work within IPython.") - return - - M = ParallelMagics(ip, self, suffix) - ip.magics_manager.register(M) - - -class LoadBalancedView(View): - """An load-balancing View that only executes via the Task scheduler. - - Load-balanced views can be created with the client's `view` method: - - >>> v = client.load_balanced_view() - - or targets can be specified, to restrict the potential destinations: - - >>> v = client.load_balanced_view([1,3]) - - which would restrict loadbalancing to between engines 1 and 3. - - """ - - follow=Any() - after=Any() - timeout=CFloat() - retries = Integer(0) - - _task_scheme = Any() - _flag_names = List(['targets', 'block', 'track', 'follow', 'after', 'timeout', 'retries']) - - def __init__(self, client=None, socket=None, **flags): - super(LoadBalancedView, self).__init__(client=client, socket=socket, **flags) - self._task_scheme=client._task_scheme - - def _validate_dependency(self, dep): - """validate a dependency. - - For use in `set_flags`. - """ - if dep is None or isinstance(dep, string_types + (AsyncResult, Dependency)): - return True - elif isinstance(dep, (list,set, tuple)): - for d in dep: - if not isinstance(d, string_types + (AsyncResult,)): - return False - elif isinstance(dep, dict): - if set(dep.keys()) != set(Dependency().as_dict().keys()): - return False - if not isinstance(dep['msg_ids'], list): - return False - for d in dep['msg_ids']: - if not isinstance(d, string_types): - return False - else: - return False - - return True - - def _render_dependency(self, dep): - """helper for building jsonable dependencies from various input forms.""" - if isinstance(dep, Dependency): - return dep.as_dict() - elif isinstance(dep, AsyncResult): - return dep.msg_ids - elif dep is None: - return [] - else: - # pass to Dependency constructor - return list(Dependency(dep)) - - def set_flags(self, **kwargs): - """set my attribute flags by keyword. - - A View is a wrapper for the Client's apply method, but with attributes - that specify keyword arguments, those attributes can be set by keyword - argument with this method. - - Parameters - ---------- - - block : bool - whether to wait for results - track : bool - whether to create a MessageTracker to allow the user to - safely edit after arrays and buffers during non-copying - sends. - - after : Dependency or collection of msg_ids - Only for load-balanced execution (targets=None) - Specify a list of msg_ids as a time-based dependency. - This job will only be run *after* the dependencies - have been met. - - follow : Dependency or collection of msg_ids - Only for load-balanced execution (targets=None) - Specify a list of msg_ids as a location-based dependency. - This job will only be run on an engine where this dependency - is met. - - timeout : float/int or None - Only for load-balanced execution (targets=None) - Specify an amount of time (in seconds) for the scheduler to - wait for dependencies to be met before failing with a - DependencyTimeout. - - retries : int - Number of times a task will be retried on failure. - """ - - super(LoadBalancedView, self).set_flags(**kwargs) - for name in ('follow', 'after'): - if name in kwargs: - value = kwargs[name] - if self._validate_dependency(value): - setattr(self, name, value) - else: - raise ValueError("Invalid dependency: %r"%value) - if 'timeout' in kwargs: - t = kwargs['timeout'] - if not isinstance(t, (int, float, type(None))): - if (not PY3) and (not isinstance(t, long)): - raise TypeError("Invalid type for timeout: %r"%type(t)) - if t is not None: - if t < 0: - raise ValueError("Invalid timeout: %s"%t) - self.timeout = t - - @sync_results - @save_ids - def _really_apply(self, f, args=None, kwargs=None, block=None, track=None, - after=None, follow=None, timeout=None, - targets=None, retries=None): - """calls f(*args, **kwargs) on a remote engine, returning the result. - - This method temporarily sets all of `apply`'s flags for a single call. - - Parameters - ---------- - - f : callable - - args : list [default: empty] - - kwargs : dict [default: empty] - - block : bool [default: self.block] - whether to block - track : bool [default: self.track] - whether to ask zmq to track the message, for safe non-copying sends - - !!!!!! TODO: THE REST HERE !!!! - - Returns - ------- - - if self.block is False: - returns AsyncResult - else: - returns actual result of f(*args, **kwargs) on the engine(s) - This will be a list of self.targets is also a list (even length 1), or - the single result if self.targets is an integer engine id - """ - - # validate whether we can run - if self._socket.closed: - msg = "Task farming is disabled" - if self._task_scheme == 'pure': - msg += " because the pure ZMQ scheduler cannot handle" - msg += " disappearing engines." - raise RuntimeError(msg) - - if self._task_scheme == 'pure': - # pure zmq scheme doesn't support extra features - msg = "Pure ZMQ scheduler doesn't support the following flags:" - "follow, after, retries, targets, timeout" - if (follow or after or retries or targets or timeout): - # hard fail on Scheduler flags - raise RuntimeError(msg) - if isinstance(f, dependent): - # soft warn on functional dependencies - warnings.warn(msg, RuntimeWarning) - - # build args - args = [] if args is None else args - kwargs = {} if kwargs is None else kwargs - block = self.block if block is None else block - track = self.track if track is None else track - after = self.after if after is None else after - retries = self.retries if retries is None else retries - follow = self.follow if follow is None else follow - timeout = self.timeout if timeout is None else timeout - targets = self.targets if targets is None else targets - - if not isinstance(retries, int): - raise TypeError('retries must be int, not %r'%type(retries)) - - if targets is None: - idents = [] - else: - idents = self.client._build_targets(targets)[0] - # ensure *not* bytes - idents = [ ident.decode() for ident in idents ] - - after = self._render_dependency(after) - follow = self._render_dependency(follow) - metadata = dict(after=after, follow=follow, timeout=timeout, targets=idents, retries=retries) - - msg = self.client.send_apply_request(self._socket, f, args, kwargs, track=track, - metadata=metadata) - tracker = None if track is False else msg['tracker'] - - ar = AsyncResult(self.client, msg['header']['msg_id'], fname=getname(f), - targets=None, tracker=tracker, owner=True, - ) - if block: - try: - return ar.get() - except KeyboardInterrupt: - pass - return ar - - @sync_results - @save_ids - def map(self, f, *sequences, **kwargs): - """``view.map(f, *sequences, block=self.block, chunksize=1, ordered=True)`` => list|AsyncMapResult - - Parallel version of builtin `map`, load-balanced by this View. - - `block`, and `chunksize` can be specified by keyword only. - - Each `chunksize` elements will be a separate task, and will be - load-balanced. This lets individual elements be available for iteration - as soon as they arrive. - - Parameters - ---------- - - f : callable - function to be mapped - *sequences: one or more sequences of matching length - the sequences to be distributed and passed to `f` - block : bool [default self.block] - whether to wait for the result or not - track : bool - whether to create a MessageTracker to allow the user to - safely edit after arrays and buffers during non-copying - sends. - chunksize : int [default 1] - how many elements should be in each task. - ordered : bool [default True] - Whether the results should be gathered as they arrive, or enforce - the order of submission. - - Only applies when iterating through AsyncMapResult as results arrive. - Has no effect when block=True. - - Returns - ------- - - if block=False - An :class:`~ipython_parallel.client.asyncresult.AsyncMapResult` instance. - An object like AsyncResult, but which reassembles the sequence of results - into a single list. AsyncMapResults can be iterated through before all - results are complete. - else - A list, the result of ``map(f,*sequences)`` - """ - - # default - block = kwargs.get('block', self.block) - chunksize = kwargs.get('chunksize', 1) - ordered = kwargs.get('ordered', True) - - keyset = set(kwargs.keys()) - extra_keys = keyset.difference_update(set(['block', 'chunksize'])) - if extra_keys: - raise TypeError("Invalid kwargs: %s"%list(extra_keys)) - - assert len(sequences) > 0, "must have some sequences to map onto!" - - pf = ParallelFunction(self, f, block=block, chunksize=chunksize, ordered=ordered) - return pf.map(*sequences) - -__all__ = ['LoadBalancedView', 'DirectView'] diff --git a/ipython_parallel/cluster.py b/ipython_parallel/cluster.py deleted file mode 100644 index c1d532b..0000000 --- a/ipython_parallel/cluster.py +++ /dev/null @@ -1,3 +0,0 @@ -if __name__ == '__main__': - from ipython_parallel.apps import ipclusterapp as app - app.launch_new_instance() diff --git a/ipython_parallel/controller/__init__.py b/ipython_parallel/controller/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/ipython_parallel/controller/__init__.py +++ /dev/null diff --git a/ipython_parallel/controller/__main__.py b/ipython_parallel/controller/__main__.py deleted file mode 100644 index b32f14b..0000000 --- a/ipython_parallel/controller/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(): - from ipython_parallel.apps import ipcontrollerapp as app - app.launch_new_instance() - -if __name__ == '__main__': - main() diff --git a/ipython_parallel/controller/dependency.py b/ipython_parallel/controller/dependency.py deleted file mode 100644 index dbc409d..0000000 --- a/ipython_parallel/controller/dependency.py +++ /dev/null @@ -1,229 +0,0 @@ -"""Dependency utilities - -Authors: - -* Min RK -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -from types import ModuleType - -from ipython_parallel.client.asyncresult import AsyncResult -from ipython_parallel.error import UnmetDependency -from ipython_parallel.util import interactive -from IPython.utils import py3compat -from IPython.utils.py3compat import string_types -from ipython_kernel.pickleutil import can, uncan - -class depend(object): - """Dependency decorator, for use with tasks. - - `@depend` lets you define a function for engine dependencies - just like you use `apply` for tasks. - - - Examples - -------- - :: - - @depend(df, a,b, c=5) - def f(m,n,p) - - view.apply(f, 1,2,3) - - will call df(a,b,c=5) on the engine, and if it returns False or - raises an UnmetDependency error, then the task will not be run - and another engine will be tried. - """ - def __init__(self, _wrapped_f, *args, **kwargs): - self.f = _wrapped_f - self.args = args - self.kwargs = kwargs - - def __call__(self, f): - return dependent(f, self.f, *self.args, **self.kwargs) - -class dependent(object): - """A function that depends on another function. - This is an object to prevent the closure used - in traditional decorators, which are not picklable. - """ - - def __init__(self, _wrapped_f, _wrapped_df, *dargs, **dkwargs): - self.f = _wrapped_f - name = getattr(_wrapped_f, '__name__', 'f') - if py3compat.PY3: - self.__name__ = name - else: - self.func_name = name - self.df = _wrapped_df - self.dargs = dargs - self.dkwargs = dkwargs - - def check_dependency(self): - if self.df(*self.dargs, **self.dkwargs) is False: - raise UnmetDependency() - - def __call__(self, *args, **kwargs): - return self.f(*args, **kwargs) - - if not py3compat.PY3: - @property - def __name__(self): - return self.func_name - -@interactive -def _require(*modules, **mapping): - """Helper for @require decorator.""" - from ipython_parallel.error import UnmetDependency - from ipython_kernel.pickleutil import uncan - user_ns = globals() - for name in modules: - try: - exec('import %s' % name, user_ns) - except ImportError: - raise UnmetDependency(name) - - for name, cobj in mapping.items(): - user_ns[name] = uncan(cobj, user_ns) - return True - -def require(*objects, **mapping): - """Simple decorator for requiring local objects and modules to be available - when the decorated function is called on the engine. - - Modules specified by name or passed directly will be imported - prior to calling the decorated function. - - Objects other than modules will be pushed as a part of the task. - Functions can be passed positionally, - and will be pushed to the engine with their __name__. - Other objects can be passed by keyword arg. - - Examples:: - - In [1]: @require('numpy') - ...: def norm(a): - ...: return numpy.linalg.norm(a,2) - - In [2]: foo = lambda x: x*x - In [3]: @require(foo) - ...: def bar(a): - ...: return foo(1-a) - """ - names = [] - for obj in objects: - if isinstance(obj, ModuleType): - obj = obj.__name__ - - if isinstance(obj, string_types): - names.append(obj) - elif hasattr(obj, '__name__'): - mapping[obj.__name__] = obj - else: - raise TypeError("Objects other than modules and functions " - "must be passed by kwarg, but got: %s" % type(obj) - ) - - for name, obj in mapping.items(): - mapping[name] = can(obj) - return depend(_require, *names, **mapping) - -class Dependency(set): - """An object for representing a set of msg_id dependencies. - - Subclassed from set(). - - Parameters - ---------- - dependencies: list/set of msg_ids or AsyncResult objects or output of Dependency.as_dict() - The msg_ids to depend on - all : bool [default True] - Whether the dependency should be considered met when *all* depending tasks have completed - or only when *any* have been completed. - success : bool [default True] - Whether to consider successes as fulfilling dependencies. - failure : bool [default False] - Whether to consider failures as fulfilling dependencies. - - If `all=success=True` and `failure=False`, then the task will fail with an ImpossibleDependency - as soon as the first depended-upon task fails. - """ - - all=True - success=True - failure=True - - def __init__(self, dependencies=[], all=True, success=True, failure=False): - if isinstance(dependencies, dict): - # load from dict - all = dependencies.get('all', True) - success = dependencies.get('success', success) - failure = dependencies.get('failure', failure) - dependencies = dependencies.get('dependencies', []) - ids = [] - - # extract ids from various sources: - if isinstance(dependencies, string_types + (AsyncResult,)): - dependencies = [dependencies] - for d in dependencies: - if isinstance(d, string_types): - ids.append(d) - elif isinstance(d, AsyncResult): - ids.extend(d.msg_ids) - else: - raise TypeError("invalid dependency type: %r"%type(d)) - - set.__init__(self, ids) - self.all = all - if not (success or failure): - raise ValueError("Must depend on at least one of successes or failures!") - self.success=success - self.failure = failure - - def check(self, completed, failed=None): - """check whether our dependencies have been met.""" - if len(self) == 0: - return True - against = set() - if self.success: - against = completed - if failed is not None and self.failure: - against = against.union(failed) - if self.all: - return self.issubset(against) - else: - return not self.isdisjoint(against) - - def unreachable(self, completed, failed=None): - """return whether this dependency has become impossible.""" - if len(self) == 0: - return False - against = set() - if not self.success: - against = completed - if failed is not None and not self.failure: - against = against.union(failed) - if self.all: - return not self.isdisjoint(against) - else: - return self.issubset(against) - - - def as_dict(self): - """Represent this dependency as a dict. For json compatibility.""" - return dict( - dependencies=list(self), - all=self.all, - success=self.success, - failure=self.failure - ) - - -__all__ = ['depend', 'require', 'dependent', 'Dependency'] - diff --git a/ipython_parallel/controller/dictdb.py b/ipython_parallel/controller/dictdb.py deleted file mode 100644 index 9657580..0000000 --- a/ipython_parallel/controller/dictdb.py +++ /dev/null @@ -1,318 +0,0 @@ -"""A Task logger that presents our DB interface, -but exists entirely in memory and implemented with dicts. - - -TaskRecords are dicts of the form:: - - { - 'msg_id' : str(uuid), - 'client_uuid' : str(uuid), - 'engine_uuid' : str(uuid) or None, - 'header' : dict(header), - 'content': dict(content), - 'buffers': list(buffers), - 'submitted': datetime or None, - 'started': datetime or None, - 'completed': datetime or None, - 'received': datetime or None, - 'resubmitted': str(uuid) or None, - 'result_header' : dict(header) or None, - 'result_content' : dict(content) or None, - 'result_buffers' : list(buffers) or None, - } - -With this info, many of the special categories of tasks can be defined by query, -e.g.: - -* pending: completed is None -* client's outstanding: client_uuid = uuid && completed is None -* MIA: arrived is None (and completed is None) - -DictDB supports a subset of mongodb operators:: - - $lt,$gt,$lte,$gte,$ne,$in,$nin,$all,$mod,$exists -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import copy -from copy import deepcopy - -# Python can't copy memoryviews, but creating another memoryview works for us -copy._deepcopy_dispatch[memoryview] = lambda x, memo: memoryview(x) - - -from datetime import datetime - -from IPython.config.configurable import LoggingConfigurable - -from IPython.utils.py3compat import iteritems, itervalues -from IPython.utils.traitlets import Dict, Unicode, Integer, Float - -filters = { - '$lt' : lambda a,b: a < b, - '$gt' : lambda a,b: b > a, - '$eq' : lambda a,b: a == b, - '$ne' : lambda a,b: a != b, - '$lte': lambda a,b: a <= b, - '$gte': lambda a,b: a >= b, - '$in' : lambda a,b: a in b, - '$nin': lambda a,b: a not in b, - '$all': lambda a,b: all([ a in bb for bb in b ]), - '$mod': lambda a,b: a%b[0] == b[1], - '$exists' : lambda a,b: (b and a is not None) or (a is None and not b) -} - - -class CompositeFilter(object): - """Composite filter for matching multiple properties.""" - - def __init__(self, dikt): - self.tests = [] - self.values = [] - for key, value in iteritems(dikt): - self.tests.append(filters[key]) - self.values.append(value) - - def __call__(self, value): - for test,check in zip(self.tests, self.values): - if not test(value, check): - return False - return True - -class BaseDB(LoggingConfigurable): - """Empty Parent class so traitlets work on DB.""" - # base configurable traits: - session = Unicode("") - -class DictDB(BaseDB): - """Basic in-memory dict-based object for saving Task Records. - - This is the first object to present the DB interface - for logging tasks out of memory. - - The interface is based on MongoDB, so adding a MongoDB - backend should be straightforward. - """ - - _records = Dict() - _culled_ids = set() # set of ids which have been culled - _buffer_bytes = Integer(0) # running total of the bytes in the DB - - size_limit = Integer(1024**3, config=True, - help="""The maximum total size (in bytes) of the buffers stored in the db - - When the db exceeds this size, the oldest records will be culled until - the total size is under size_limit * (1-cull_fraction). - default: 1 GB - """ - ) - record_limit = Integer(1024, config=True, - help="""The maximum number of records in the db - - When the history exceeds this size, the first record_limit * cull_fraction - records will be culled. - """ - ) - cull_fraction = Float(0.1, config=True, - help="""The fraction by which the db should culled when one of the limits is exceeded - - In general, the db size will spend most of its time with a size in the range: - - [limit * (1-cull_fraction), limit] - - for each of size_limit and record_limit. - """ - ) - - def _match_one(self, rec, tests): - """Check if a specific record matches tests.""" - for key,test in iteritems(tests): - if not test(rec.get(key, None)): - return False - return True - - def _match(self, check): - """Find all the matches for a check dict.""" - matches = [] - tests = {} - for k,v in iteritems(check): - if isinstance(v, dict): - tests[k] = CompositeFilter(v) - else: - tests[k] = lambda o: o==v - - for rec in itervalues(self._records): - if self._match_one(rec, tests): - matches.append(deepcopy(rec)) - return matches - - def _extract_subdict(self, rec, keys): - """extract subdict of keys""" - d = {} - d['msg_id'] = rec['msg_id'] - for key in keys: - d[key] = rec[key] - return deepcopy(d) - - # methods for monitoring size / culling history - - def _add_bytes(self, rec): - for key in ('buffers', 'result_buffers'): - for buf in rec.get(key) or []: - self._buffer_bytes += len(buf) - - self._maybe_cull() - - def _drop_bytes(self, rec): - for key in ('buffers', 'result_buffers'): - for buf in rec.get(key) or []: - self._buffer_bytes -= len(buf) - - def _cull_oldest(self, n=1): - """cull the oldest N records""" - for msg_id in self.get_history()[:n]: - self.log.debug("Culling record: %r", msg_id) - self._culled_ids.add(msg_id) - self.drop_record(msg_id) - - def _maybe_cull(self): - # cull by count: - if len(self._records) > self.record_limit: - to_cull = int(self.cull_fraction * self.record_limit) - self.log.info("%i records exceeds limit of %i, culling oldest %i", - len(self._records), self.record_limit, to_cull - ) - self._cull_oldest(to_cull) - - # cull by size: - if self._buffer_bytes > self.size_limit: - limit = self.size_limit * (1 - self.cull_fraction) - - before = self._buffer_bytes - before_count = len(self._records) - culled = 0 - while self._buffer_bytes > limit: - self._cull_oldest(1) - culled += 1 - - self.log.info("%i records with total buffer size %i exceeds limit: %i. Culled oldest %i records.", - before_count, before, self.size_limit, culled - ) - - def _check_dates(self, rec): - for key in ('submitted', 'started', 'completed'): - value = rec.get(key, None) - if value is not None and not isinstance(value, datetime): - raise ValueError("%s must be None or datetime, not %r" % (key, value)) - - # public API methods: - - def add_record(self, msg_id, rec): - """Add a new Task Record, by msg_id.""" - if msg_id in self._records: - raise KeyError("Already have msg_id %r"%(msg_id)) - self._check_dates(rec) - self._records[msg_id] = rec - self._add_bytes(rec) - self._maybe_cull() - - def get_record(self, msg_id): - """Get a specific Task Record, by msg_id.""" - if msg_id in self._culled_ids: - raise KeyError("Record %r has been culled for size" % msg_id) - if not msg_id in self._records: - raise KeyError("No such msg_id %r"%(msg_id)) - return deepcopy(self._records[msg_id]) - - def update_record(self, msg_id, rec): - """Update the data in an existing record.""" - if msg_id in self._culled_ids: - raise KeyError("Record %r has been culled for size" % msg_id) - self._check_dates(rec) - _rec = self._records[msg_id] - self._drop_bytes(_rec) - _rec.update(rec) - self._add_bytes(_rec) - - def drop_matching_records(self, check): - """Remove a record from the DB.""" - matches = self._match(check) - for rec in matches: - self._drop_bytes(rec) - del self._records[rec['msg_id']] - - def drop_record(self, msg_id): - """Remove a record from the DB.""" - rec = self._records[msg_id] - self._drop_bytes(rec) - del self._records[msg_id] - - def find_records(self, check, keys=None): - """Find records matching a query dict, optionally extracting subset of keys. - - Returns dict keyed by msg_id of matching records. - - Parameters - ---------- - - check: dict - mongodb-style query argument - keys: list of strs [optional] - if specified, the subset of keys to extract. msg_id will *always* be - included. - """ - matches = self._match(check) - if keys: - return [ self._extract_subdict(rec, keys) for rec in matches ] - else: - return matches - - def get_history(self): - """get all msg_ids, ordered by time submitted.""" - msg_ids = self._records.keys() - # Remove any that do not have a submitted timestamp. - # This is extremely unlikely to happen, - # but it seems to come up in some tests on VMs. - msg_ids = [ m for m in msg_ids if self._records[m]['submitted'] is not None ] - return sorted(msg_ids, key=lambda m: self._records[m]['submitted']) - - -class NoData(KeyError): - """Special KeyError to raise when requesting data from NoDB""" - def __str__(self): - return "NoDB backend doesn't store any data. " - "Start the Controller with a DB backend to enable resubmission / result persistence." - - -class NoDB(BaseDB): - """A blackhole db backend that actually stores no information. - - Provides the full DB interface, but raises KeyErrors on any - method that tries to access the records. This can be used to - minimize the memory footprint of the Hub when its record-keeping - functionality is not required. - """ - - def add_record(self, msg_id, record): - pass - - def get_record(self, msg_id): - raise NoData() - - def update_record(self, msg_id, record): - pass - - def drop_matching_records(self, check): - pass - - def drop_record(self, msg_id): - pass - - def find_records(self, check, keys=None): - raise NoData() - - def get_history(self): - raise NoData() - diff --git a/ipython_parallel/controller/heartmonitor.py b/ipython_parallel/controller/heartmonitor.py deleted file mode 100755 index aa59fe2..0000000 --- a/ipython_parallel/controller/heartmonitor.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -""" -A multi-heart Heartbeat system using PUB and ROUTER sockets. pings are sent out on the PUB, -and hearts are tracked based on their DEALER identities. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function -import time -import uuid - -import zmq -from zmq.devices import ThreadDevice, ThreadMonitoredQueue -from zmq.eventloop import ioloop, zmqstream - -from IPython.config.configurable import LoggingConfigurable -from IPython.utils.py3compat import str_to_bytes -from IPython.utils.traitlets import Set, Instance, CFloat, Integer, Dict, Bool - -from ipython_parallel.util import log_errors - -class Heart(object): - """A basic heart object for responding to a HeartMonitor. - This is a simple wrapper with defaults for the most common - Device model for responding to heartbeats. - - It simply builds a threadsafe zmq.FORWARDER Device, defaulting to using - SUB/DEALER for in/out. - - You can specify the DEALER's IDENTITY via the optional heart_id argument.""" - device=None - id=None - def __init__(self, in_addr, out_addr, mon_addr=None, in_type=zmq.SUB, out_type=zmq.DEALER, mon_type=zmq.PUB, heart_id=None): - if mon_addr is None: - self.device = ThreadDevice(zmq.FORWARDER, in_type, out_type) - else: - self.device = ThreadMonitoredQueue(in_type, out_type, mon_type, in_prefix=b"", out_prefix=b"") - # do not allow the device to share global Context.instance, - # which is the default behavior in pyzmq > 2.1.10 - self.device.context_factory = zmq.Context - - self.device.daemon=True - self.device.connect_in(in_addr) - self.device.connect_out(out_addr) - if mon_addr is not None: - self.device.connect_mon(mon_addr) - if in_type == zmq.SUB: - self.device.setsockopt_in(zmq.SUBSCRIBE, b"") - if heart_id is None: - heart_id = uuid.uuid4().bytes - self.device.setsockopt_out(zmq.IDENTITY, heart_id) - self.id = heart_id - - def start(self): - return self.device.start() - - -class HeartMonitor(LoggingConfigurable): - """A basic HeartMonitor class - pingstream: a PUB stream - pongstream: an ROUTER stream - period: the period of the heartbeat in milliseconds""" - - debug = Bool(False, config=True, - help="""Whether to include every heartbeat in debugging output. - - Has to be set explicitly, because there will be *a lot* of output. - """ - ) - period = Integer(3000, config=True, - help='The frequency at which the Hub pings the engines for heartbeats ' - '(in ms)', - ) - max_heartmonitor_misses = Integer(10, config=True, - help='Allowed consecutive missed pings from controller Hub to engine before unregistering.', - ) - - pingstream=Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True) - pongstream=Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True) - loop = Instance('zmq.eventloop.ioloop.IOLoop') - def _loop_default(self): - return ioloop.IOLoop.instance() - - # not settable: - hearts=Set() - responses=Set() - on_probation=Dict() - last_ping=CFloat(0) - _new_handlers = Set() - _failure_handlers = Set() - lifetime = CFloat(0) - tic = CFloat(0) - - def __init__(self, **kwargs): - super(HeartMonitor, self).__init__(**kwargs) - - self.pongstream.on_recv(self.handle_pong) - - def start(self): - self.tic = time.time() - self.caller = ioloop.PeriodicCallback(self.beat, self.period, self.loop) - self.caller.start() - - def add_new_heart_handler(self, handler): - """add a new handler for new hearts""" - self.log.debug("heartbeat::new_heart_handler: %s", handler) - self._new_handlers.add(handler) - - def add_heart_failure_handler(self, handler): - """add a new handler for heart failure""" - self.log.debug("heartbeat::new heart failure handler: %s", handler) - self._failure_handlers.add(handler) - - def beat(self): - self.pongstream.flush() - self.last_ping = self.lifetime - - toc = time.time() - self.lifetime += toc-self.tic - self.tic = toc - if self.debug: - self.log.debug("heartbeat::sending %s", self.lifetime) - goodhearts = self.hearts.intersection(self.responses) - missed_beats = self.hearts.difference(goodhearts) - newhearts = self.responses.difference(goodhearts) - for heart in newhearts: - self.handle_new_heart(heart) - heartfailures, on_probation = self._check_missed(missed_beats, self.on_probation, - self.hearts) - for failure in heartfailures: - self.handle_heart_failure(failure) - self.on_probation = on_probation - self.responses = set() - #print self.on_probation, self.hearts - # self.log.debug("heartbeat::beat %.3f, %i beating hearts", self.lifetime, len(self.hearts)) - self.pingstream.send(str_to_bytes(str(self.lifetime))) - # flush stream to force immediate socket send - self.pingstream.flush() - - def _check_missed(self, missed_beats, on_probation, hearts): - """Update heartbeats on probation, identifying any that have too many misses. - """ - failures = [] - new_probation = {} - for cur_heart in (b for b in missed_beats if b in hearts): - miss_count = on_probation.get(cur_heart, 0) + 1 - self.log.info("heartbeat::missed %s : %s" % (cur_heart, miss_count)) - if miss_count > self.max_heartmonitor_misses: - failures.append(cur_heart) - else: - new_probation[cur_heart] = miss_count - return failures, new_probation - - def handle_new_heart(self, heart): - if self._new_handlers: - for handler in self._new_handlers: - handler(heart) - else: - self.log.info("heartbeat::yay, got new heart %s!", heart) - self.hearts.add(heart) - - def handle_heart_failure(self, heart): - if self._failure_handlers: - for handler in self._failure_handlers: - try: - handler(heart) - except Exception as e: - self.log.error("heartbeat::Bad Handler! %s", handler, exc_info=True) - pass - else: - self.log.info("heartbeat::Heart %s failed :(", heart) - self.hearts.remove(heart) - - - @log_errors - def handle_pong(self, msg): - "a heart just beat" - current = str_to_bytes(str(self.lifetime)) - last = str_to_bytes(str(self.last_ping)) - if msg[1] == current: - delta = time.time()-self.tic - if self.debug: - self.log.debug("heartbeat::heart %r took %.2f ms to respond", msg[0], 1000*delta) - self.responses.add(msg[0]) - elif msg[1] == last: - delta = time.time()-self.tic + (self.lifetime-self.last_ping) - self.log.warn("heartbeat::heart %r missed a beat, and took %.2f ms to respond", msg[0], 1000*delta) - self.responses.add(msg[0]) - else: - self.log.warn("heartbeat::got bad heartbeat (possibly old?): %s (current=%.3f)", msg[1], self.lifetime) - diff --git a/ipython_parallel/controller/hub.py b/ipython_parallel/controller/hub.py deleted file mode 100644 index 13c201b..0000000 --- a/ipython_parallel/controller/hub.py +++ /dev/null @@ -1,1438 +0,0 @@ -"""The IPython Controller Hub with 0MQ - -This is the master object that handles connections from engines and clients, -and monitors traffic through the various queues. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import json -import os -import sys -import time -from datetime import datetime - -import zmq -from zmq.eventloop.zmqstream import ZMQStream - -# internal: -from IPython.utils.importstring import import_item -from jupyter_client.jsonutil import extract_dates -from IPython.utils.localinterfaces import localhost -from IPython.utils.py3compat import cast_bytes, unicode_type, iteritems -from IPython.utils.traitlets import ( - HasTraits, Any, Instance, Integer, Unicode, Dict, Set, Tuple, DottedObjectName - ) - -from ipython_parallel import error, util -from ipython_parallel.factory import RegistrationFactory - -from IPython.kernel.zmq.session import SessionFactory - -from .heartmonitor import HeartMonitor - - -def _passer(*args, **kwargs): - return - -def _printer(*args, **kwargs): - print (args) - print (kwargs) - -def empty_record(): - """Return an empty dict with all record keys.""" - return { - 'msg_id' : None, - 'header' : None, - 'metadata' : None, - 'content': None, - 'buffers': None, - 'submitted': None, - 'client_uuid' : None, - 'engine_uuid' : None, - 'started': None, - 'completed': None, - 'resubmitted': None, - 'received': None, - 'result_header' : None, - 'result_metadata' : None, - 'result_content' : None, - 'result_buffers' : None, - 'queue' : None, - 'execute_input' : None, - 'execute_result': None, - 'error': None, - 'stdout': '', - 'stderr': '', - } - -def init_record(msg): - """Initialize a TaskRecord based on a request.""" - header = msg['header'] - return { - 'msg_id' : header['msg_id'], - 'header' : header, - 'content': msg['content'], - 'metadata': msg['metadata'], - 'buffers': msg['buffers'], - 'submitted': header['date'], - 'client_uuid' : None, - 'engine_uuid' : None, - 'started': None, - 'completed': None, - 'resubmitted': None, - 'received': None, - 'result_header' : None, - 'result_metadata': None, - 'result_content' : None, - 'result_buffers' : None, - 'queue' : None, - 'execute_input' : None, - 'execute_result': None, - 'error': None, - 'stdout': '', - 'stderr': '', - } - - -class EngineConnector(HasTraits): - """A simple object for accessing the various zmq connections of an object. - Attributes are: - id (int): engine ID - uuid (unicode): engine UUID - pending: set of msg_ids - stallback: tornado timeout for stalled registration - """ - - id = Integer(0) - uuid = Unicode() - pending = Set() - stallback = Any() - - -_db_shortcuts = { - 'sqlitedb' : 'ipython_parallel.controller.sqlitedb.SQLiteDB', - 'mongodb' : 'ipython_parallel.controller.mongodb.MongoDB', - 'dictdb' : 'ipython_parallel.controller.dictdb.DictDB', - 'nodb' : 'ipython_parallel.controller.dictdb.NoDB', -} - -class HubFactory(RegistrationFactory): - """The Configurable for setting up a Hub.""" - - # port-pairs for monitoredqueues: - hb = Tuple(Integer,Integer,config=True, - help="""PUB/ROUTER Port pair for Engine heartbeats""") - def _hb_default(self): - return tuple(util.select_random_ports(2)) - - mux = Tuple(Integer,Integer,config=True, - help="""Client/Engine Port pair for MUX queue""") - - def _mux_default(self): - return tuple(util.select_random_ports(2)) - - task = Tuple(Integer,Integer,config=True, - help="""Client/Engine Port pair for Task queue""") - def _task_default(self): - return tuple(util.select_random_ports(2)) - - control = Tuple(Integer,Integer,config=True, - help="""Client/Engine Port pair for Control queue""") - - def _control_default(self): - return tuple(util.select_random_ports(2)) - - iopub = Tuple(Integer,Integer,config=True, - help="""Client/Engine Port pair for IOPub relay""") - - def _iopub_default(self): - return tuple(util.select_random_ports(2)) - - # single ports: - mon_port = Integer(config=True, - help="""Monitor (SUB) port for queue traffic""") - - def _mon_port_default(self): - return util.select_random_ports(1)[0] - - notifier_port = Integer(config=True, - help="""PUB port for sending engine status notifications""") - - def _notifier_port_default(self): - return util.select_random_ports(1)[0] - - engine_ip = Unicode(config=True, - help="IP on which to listen for engine connections. [default: loopback]") - def _engine_ip_default(self): - return localhost() - engine_transport = Unicode('tcp', config=True, - help="0MQ transport for engine connections. [default: tcp]") - - client_ip = Unicode(config=True, - help="IP on which to listen for client connections. [default: loopback]") - client_transport = Unicode('tcp', config=True, - help="0MQ transport for client connections. [default : tcp]") - - monitor_ip = Unicode(config=True, - help="IP on which to listen for monitor messages. [default: loopback]") - monitor_transport = Unicode('tcp', config=True, - help="0MQ transport for monitor messages. [default : tcp]") - - _client_ip_default = _monitor_ip_default = _engine_ip_default - - - monitor_url = Unicode('') - - db_class = DottedObjectName('NoDB', - config=True, help="""The class to use for the DB backend - - Options include: - - SQLiteDB: SQLite - MongoDB : use MongoDB - DictDB : in-memory storage (fastest, but be mindful of memory growth of the Hub) - NoDB : disable database altogether (default) - - """) - - registration_timeout = Integer(0, config=True, - help="Engine registration timeout in seconds [default: max(30," - "10*heartmonitor.period)]" ) - - def _registration_timeout_default(self): - if self.heartmonitor is None: - # early initialization, this value will be ignored - return 0 - # heartmonitor period is in milliseconds, so 10x in seconds is .01 - return max(30, int(.01 * self.heartmonitor.period)) - - # not configurable - db = Instance('ipython_parallel.controller.dictdb.BaseDB', allow_none=True) - heartmonitor = Instance('ipython_parallel.controller.heartmonitor.HeartMonitor', allow_none=True) - - def _ip_changed(self, name, old, new): - self.engine_ip = new - self.client_ip = new - self.monitor_ip = new - self._update_monitor_url() - - def _update_monitor_url(self): - self.monitor_url = "%s://%s:%i" % (self.monitor_transport, self.monitor_ip, self.mon_port) - - def _transport_changed(self, name, old, new): - self.engine_transport = new - self.client_transport = new - self.monitor_transport = new - self._update_monitor_url() - - def __init__(self, **kwargs): - super(HubFactory, self).__init__(**kwargs) - self._update_monitor_url() - - - def construct(self): - self.init_hub() - - def start(self): - self.heartmonitor.start() - self.log.info("Heartmonitor started") - - def client_url(self, channel): - """return full zmq url for a named client channel""" - return "%s://%s:%i" % (self.client_transport, self.client_ip, self.client_info[channel]) - - def engine_url(self, channel): - """return full zmq url for a named engine channel""" - return "%s://%s:%i" % (self.engine_transport, self.engine_ip, self.engine_info[channel]) - - def init_hub(self): - """construct Hub object""" - - ctx = self.context - loop = self.loop - if 'TaskScheduler.scheme_name' in self.config: - scheme = self.config.TaskScheduler.scheme_name - else: - from .scheduler import TaskScheduler - scheme = TaskScheduler.scheme_name.get_default_value() - - # build connection dicts - engine = self.engine_info = { - 'interface' : "%s://%s" % (self.engine_transport, self.engine_ip), - 'registration' : self.regport, - 'control' : self.control[1], - 'mux' : self.mux[1], - 'hb_ping' : self.hb[0], - 'hb_pong' : self.hb[1], - 'task' : self.task[1], - 'iopub' : self.iopub[1], - } - - client = self.client_info = { - 'interface' : "%s://%s" % (self.client_transport, self.client_ip), - 'registration' : self.regport, - 'control' : self.control[0], - 'mux' : self.mux[0], - 'task' : self.task[0], - 'task_scheme' : scheme, - 'iopub' : self.iopub[0], - 'notification' : self.notifier_port, - } - - self.log.debug("Hub engine addrs: %s", self.engine_info) - self.log.debug("Hub client addrs: %s", self.client_info) - - # Registrar socket - q = ZMQStream(ctx.socket(zmq.ROUTER), loop) - util.set_hwm(q, 0) - q.bind(self.client_url('registration')) - self.log.info("Hub listening on %s for registration.", self.client_url('registration')) - if self.client_ip != self.engine_ip: - q.bind(self.engine_url('registration')) - self.log.info("Hub listening on %s for registration.", self.engine_url('registration')) - - ### Engine connections ### - - # heartbeat - hpub = ctx.socket(zmq.PUB) - hpub.bind(self.engine_url('hb_ping')) - hrep = ctx.socket(zmq.ROUTER) - util.set_hwm(hrep, 0) - hrep.bind(self.engine_url('hb_pong')) - self.heartmonitor = HeartMonitor(loop=loop, parent=self, log=self.log, - pingstream=ZMQStream(hpub,loop), - pongstream=ZMQStream(hrep,loop) - ) - - ### Client connections ### - - # Notifier socket - n = ZMQStream(ctx.socket(zmq.PUB), loop) - n.bind(self.client_url('notification')) - - ### build and launch the queues ### - - # monitor socket - sub = ctx.socket(zmq.SUB) - sub.setsockopt(zmq.SUBSCRIBE, b"") - sub.bind(self.monitor_url) - sub.bind('inproc://monitor') - sub = ZMQStream(sub, loop) - - # connect the db - db_class = _db_shortcuts.get(self.db_class.lower(), self.db_class) - self.log.info('Hub using DB backend: %r', (db_class.split('.')[-1])) - self.db = import_item(str(db_class))(session=self.session.session, - parent=self, log=self.log) - time.sleep(.25) - - # resubmit stream - r = ZMQStream(ctx.socket(zmq.DEALER), loop) - url = util.disambiguate_url(self.client_url('task')) - r.connect(url) - - self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor, - query=q, notifier=n, resubmit=r, db=self.db, - engine_info=self.engine_info, client_info=self.client_info, - log=self.log, registration_timeout=self.registration_timeout) - - -class Hub(SessionFactory): - """The IPython Controller Hub with 0MQ connections - - Parameters - ========== - loop: zmq IOLoop instance - session: Session object - context: zmq context for creating new connections (?) - queue: ZMQStream for monitoring the command queue (SUB) - query: ZMQStream for engine registration and client queries requests (ROUTER) - heartbeat: HeartMonitor object checking the pulse of the engines - notifier: ZMQStream for broadcasting engine registration changes (PUB) - db: connection to db for out of memory logging of commands - NotImplemented - engine_info: dict of zmq connection information for engines to connect - to the queues. - client_info: dict of zmq connection information for engines to connect - to the queues. - """ - - engine_state_file = Unicode() - - # internal data structures: - ids=Set() # engine IDs - keytable=Dict() - by_ident=Dict() - engines=Dict() - clients=Dict() - hearts=Dict() - pending=Set() - queues=Dict() # pending msg_ids keyed by engine_id - tasks=Dict() # pending msg_ids submitted as tasks, keyed by client_id - completed=Dict() # completed msg_ids keyed by engine_id - all_completed=Set() # completed msg_ids keyed by engine_id - dead_engines=Set() # completed msg_ids keyed by engine_id - unassigned=Set() # set of task msg_ds not yet assigned a destination - incoming_registrations=Dict() - registration_timeout=Integer() - _idcounter=Integer(0) - - # objects from constructor: - query=Instance(ZMQStream, allow_none=True) - monitor=Instance(ZMQStream, allow_none=True) - notifier=Instance(ZMQStream, allow_none=True) - resubmit=Instance(ZMQStream, allow_none=True) - heartmonitor=Instance(HeartMonitor, allow_none=True) - db=Instance(object, allow_none=True) - client_info=Dict() - engine_info=Dict() - - - def __init__(self, **kwargs): - """ - # universal: - loop: IOLoop for creating future connections - session: streamsession for sending serialized data - # engine: - queue: ZMQStream for monitoring queue messages - query: ZMQStream for engine+client registration and client requests - heartbeat: HeartMonitor object for tracking engines - # extra: - db: ZMQStream for db connection (NotImplemented) - engine_info: zmq address/protocol dict for engine connections - client_info: zmq address/protocol dict for client connections - """ - - super(Hub, self).__init__(**kwargs) - - # register our callbacks - self.query.on_recv(self.dispatch_query) - self.monitor.on_recv(self.dispatch_monitor_traffic) - - self.heartmonitor.add_heart_failure_handler(self.handle_heart_failure) - self.heartmonitor.add_new_heart_handler(self.handle_new_heart) - - self.monitor_handlers = {b'in' : self.save_queue_request, - b'out': self.save_queue_result, - b'intask': self.save_task_request, - b'outtask': self.save_task_result, - b'tracktask': self.save_task_destination, - b'incontrol': _passer, - b'outcontrol': _passer, - b'iopub': self.save_iopub_message, - } - - self.query_handlers = {'queue_request': self.queue_status, - 'result_request': self.get_results, - 'history_request': self.get_history, - 'db_request': self.db_query, - 'purge_request': self.purge_results, - 'load_request': self.check_load, - 'resubmit_request': self.resubmit_task, - 'shutdown_request': self.shutdown_request, - 'registration_request' : self.register_engine, - 'unregistration_request' : self.unregister_engine, - 'connection_request': self.connection_request, - } - - # ignore resubmit replies - self.resubmit.on_recv(lambda msg: None, copy=False) - - self.log.info("hub::created hub") - - @property - def _next_id(self): - """gemerate a new ID. - - No longer reuse old ids, just count from 0.""" - newid = self._idcounter - self._idcounter += 1 - return newid - # newid = 0 - # incoming = [id[0] for id in itervalues(self.incoming_registrations)] - # # print newid, self.ids, self.incoming_registrations - # while newid in self.ids or newid in incoming: - # newid += 1 - # return newid - - #----------------------------------------------------------------------------- - # message validation - #----------------------------------------------------------------------------- - - def _validate_targets(self, targets): - """turn any valid targets argument into a list of integer ids""" - if targets is None: - # default to all - return self.ids - - if isinstance(targets, (int,str,unicode_type)): - # only one target specified - targets = [targets] - _targets = [] - for t in targets: - # map raw identities to ids - if isinstance(t, (str,unicode_type)): - t = self.by_ident.get(cast_bytes(t), t) - _targets.append(t) - targets = _targets - bad_targets = [ t for t in targets if t not in self.ids ] - if bad_targets: - raise IndexError("No Such Engine: %r" % bad_targets) - if not targets: - raise IndexError("No Engines Registered") - return targets - - #----------------------------------------------------------------------------- - # dispatch methods (1 per stream) - #----------------------------------------------------------------------------- - - - @util.log_errors - def dispatch_monitor_traffic(self, msg): - """all ME and Task queue messages come through here, as well as - IOPub traffic.""" - self.log.debug("monitor traffic: %r", msg[0]) - switch = msg[0] - try: - idents, msg = self.session.feed_identities(msg[1:]) - except ValueError: - idents=[] - if not idents: - self.log.error("Monitor message without topic: %r", msg) - return - handler = self.monitor_handlers.get(switch, None) - if handler is not None: - handler(idents, msg) - else: - self.log.error("Unrecognized monitor topic: %r", switch) - - - @util.log_errors - def dispatch_query(self, msg): - """Route registration requests and queries from clients.""" - try: - idents, msg = self.session.feed_identities(msg) - except ValueError: - idents = [] - if not idents: - self.log.error("Bad Query Message: %r", msg) - return - client_id = idents[0] - try: - msg = self.session.deserialize(msg, content=True) - except Exception: - content = error.wrap_exception() - self.log.error("Bad Query Message: %r", msg, exc_info=True) - self.session.send(self.query, "hub_error", ident=client_id, - content=content) - return - # print client_id, header, parent, content - #switch on message type: - msg_type = msg['header']['msg_type'] - self.log.info("client::client %r requested %r", client_id, msg_type) - handler = self.query_handlers.get(msg_type, None) - try: - assert handler is not None, "Bad Message Type: %r" % msg_type - except: - content = error.wrap_exception() - self.log.error("Bad Message Type: %r", msg_type, exc_info=True) - self.session.send(self.query, "hub_error", ident=client_id, - content=content) - return - - else: - handler(idents, msg) - - def dispatch_db(self, msg): - """""" - raise NotImplementedError - - #--------------------------------------------------------------------------- - # handler methods (1 per event) - #--------------------------------------------------------------------------- - - #----------------------- Heartbeat -------------------------------------- - - def handle_new_heart(self, heart): - """handler to attach to heartbeater. - Called when a new heart starts to beat. - Triggers completion of registration.""" - self.log.debug("heartbeat::handle_new_heart(%r)", heart) - if heart not in self.incoming_registrations: - self.log.info("heartbeat::ignoring new heart: %r", heart) - else: - self.finish_registration(heart) - - - def handle_heart_failure(self, heart): - """handler to attach to heartbeater. - called when a previously registered heart fails to respond to beat request. - triggers unregistration""" - self.log.debug("heartbeat::handle_heart_failure(%r)", heart) - eid = self.hearts.get(heart, None) - uuid = self.engines[eid].uuid - if eid is None or self.keytable[eid] in self.dead_engines: - self.log.info("heartbeat::ignoring heart failure %r (not an engine or already dead)", heart) - else: - self.unregister_engine(heart, dict(content=dict(id=eid, queue=uuid))) - - #----------------------- MUX Queue Traffic ------------------------------ - - def save_queue_request(self, idents, msg): - if len(idents) < 2: - self.log.error("invalid identity prefix: %r", idents) - return - queue_id, client_id = idents[:2] - try: - msg = self.session.deserialize(msg) - except Exception: - self.log.error("queue::client %r sent invalid message to %r: %r", client_id, queue_id, msg, exc_info=True) - return - - eid = self.by_ident.get(queue_id, None) - if eid is None: - self.log.error("queue::target %r not registered", queue_id) - self.log.debug("queue:: valid are: %r", self.by_ident.keys()) - return - record = init_record(msg) - msg_id = record['msg_id'] - self.log.info("queue::client %r submitted request %r to %s", client_id, msg_id, eid) - # Unicode in records - record['engine_uuid'] = queue_id.decode('ascii') - record['client_uuid'] = msg['header']['session'] - record['queue'] = 'mux' - - try: - # it's posible iopub arrived first: - existing = self.db.get_record(msg_id) - for key,evalue in iteritems(existing): - rvalue = record.get(key, None) - if evalue and rvalue and evalue != rvalue: - self.log.warn("conflicting initial state for record: %r:%r <%r> %r", msg_id, rvalue, key, evalue) - elif evalue and not rvalue: - record[key] = evalue - try: - self.db.update_record(msg_id, record) - except Exception: - self.log.error("DB Error updating record %r", msg_id, exc_info=True) - except KeyError: - try: - self.db.add_record(msg_id, record) - except Exception: - self.log.error("DB Error adding record %r", msg_id, exc_info=True) - - - self.pending.add(msg_id) - self.queues[eid].append(msg_id) - - def save_queue_result(self, idents, msg): - if len(idents) < 2: - self.log.error("invalid identity prefix: %r", idents) - return - - client_id, queue_id = idents[:2] - try: - msg = self.session.deserialize(msg) - except Exception: - self.log.error("queue::engine %r sent invalid message to %r: %r", - queue_id, client_id, msg, exc_info=True) - return - - eid = self.by_ident.get(queue_id, None) - if eid is None: - self.log.error("queue::unknown engine %r is sending a reply: ", queue_id) - return - - parent = msg['parent_header'] - if not parent: - return - msg_id = parent['msg_id'] - if msg_id in self.pending: - self.pending.remove(msg_id) - self.all_completed.add(msg_id) - self.queues[eid].remove(msg_id) - self.completed[eid].append(msg_id) - self.log.info("queue::request %r completed on %s", msg_id, eid) - elif msg_id not in self.all_completed: - # it could be a result from a dead engine that died before delivering the - # result - self.log.warn("queue:: unknown msg finished %r", msg_id) - return - # update record anyway, because the unregistration could have been premature - rheader = msg['header'] - md = msg['metadata'] - completed = rheader['date'] - started = extract_dates(md.get('started', None)) - result = { - 'result_header' : rheader, - 'result_metadata': md, - 'result_content': msg['content'], - 'received': datetime.now(), - 'started' : started, - 'completed' : completed - } - - result['result_buffers'] = msg['buffers'] - try: - self.db.update_record(msg_id, result) - except Exception: - self.log.error("DB Error updating record %r", msg_id, exc_info=True) - - - #--------------------- Task Queue Traffic ------------------------------ - - def save_task_request(self, idents, msg): - """Save the submission of a task.""" - client_id = idents[0] - - try: - msg = self.session.deserialize(msg) - except Exception: - self.log.error("task::client %r sent invalid task message: %r", - client_id, msg, exc_info=True) - return - record = init_record(msg) - - record['client_uuid'] = msg['header']['session'] - record['queue'] = 'task' - header = msg['header'] - msg_id = header['msg_id'] - self.pending.add(msg_id) - self.unassigned.add(msg_id) - try: - # it's posible iopub arrived first: - existing = self.db.get_record(msg_id) - if existing['resubmitted']: - for key in ('submitted', 'client_uuid', 'buffers'): - # don't clobber these keys on resubmit - # submitted and client_uuid should be different - # and buffers might be big, and shouldn't have changed - record.pop(key) - # still check content,header which should not change - # but are not expensive to compare as buffers - - for key,evalue in iteritems(existing): - if key.endswith('buffers'): - # don't compare buffers - continue - rvalue = record.get(key, None) - if evalue and rvalue and evalue != rvalue: - self.log.warn("conflicting initial state for record: %r:%r <%r> %r", msg_id, rvalue, key, evalue) - elif evalue and not rvalue: - record[key] = evalue - try: - self.db.update_record(msg_id, record) - except Exception: - self.log.error("DB Error updating record %r", msg_id, exc_info=True) - except KeyError: - try: - self.db.add_record(msg_id, record) - except Exception: - self.log.error("DB Error adding record %r", msg_id, exc_info=True) - except Exception: - self.log.error("DB Error saving task request %r", msg_id, exc_info=True) - - def save_task_result(self, idents, msg): - """save the result of a completed task.""" - client_id = idents[0] - try: - msg = self.session.deserialize(msg) - except Exception: - self.log.error("task::invalid task result message send to %r: %r", - client_id, msg, exc_info=True) - return - - parent = msg['parent_header'] - if not parent: - # print msg - self.log.warn("Task %r had no parent!", msg) - return - msg_id = parent['msg_id'] - if msg_id in self.unassigned: - self.unassigned.remove(msg_id) - - header = msg['header'] - md = msg['metadata'] - engine_uuid = md.get('engine', u'') - eid = self.by_ident.get(cast_bytes(engine_uuid), None) - - status = md.get('status', None) - - if msg_id in self.pending: - self.log.info("task::task %r finished on %s", msg_id, eid) - self.pending.remove(msg_id) - self.all_completed.add(msg_id) - if eid is not None: - if status != 'aborted': - self.completed[eid].append(msg_id) - if msg_id in self.tasks[eid]: - self.tasks[eid].remove(msg_id) - completed = header['date'] - started = extract_dates(md.get('started', None)) - result = { - 'result_header' : header, - 'result_metadata': msg['metadata'], - 'result_content': msg['content'], - 'started' : started, - 'completed' : completed, - 'received' : datetime.now(), - 'engine_uuid': engine_uuid, - } - - result['result_buffers'] = msg['buffers'] - try: - self.db.update_record(msg_id, result) - except Exception: - self.log.error("DB Error saving task request %r", msg_id, exc_info=True) - - else: - self.log.debug("task::unknown task %r finished", msg_id) - - def save_task_destination(self, idents, msg): - try: - msg = self.session.deserialize(msg, content=True) - except Exception: - self.log.error("task::invalid task tracking message", exc_info=True) - return - content = msg['content'] - # print (content) - msg_id = content['msg_id'] - engine_uuid = content['engine_id'] - eid = self.by_ident[cast_bytes(engine_uuid)] - - self.log.info("task::task %r arrived on %r", msg_id, eid) - if msg_id in self.unassigned: - self.unassigned.remove(msg_id) - # else: - # self.log.debug("task::task %r not listed as MIA?!"%(msg_id)) - - self.tasks[eid].append(msg_id) - # self.pending[msg_id][1].update(received=datetime.now(),engine=(eid,engine_uuid)) - try: - self.db.update_record(msg_id, dict(engine_uuid=engine_uuid)) - except Exception: - self.log.error("DB Error saving task destination %r", msg_id, exc_info=True) - - - def mia_task_request(self, idents, msg): - raise NotImplementedError - client_id = idents[0] - # content = dict(mia=self.mia,status='ok') - # self.session.send('mia_reply', content=content, idents=client_id) - - - #--------------------- IOPub Traffic ------------------------------ - - def save_iopub_message(self, topics, msg): - """save an iopub message into the db""" - # print (topics) - try: - msg = self.session.deserialize(msg, content=True) - except Exception: - self.log.error("iopub::invalid IOPub message", exc_info=True) - return - - parent = msg['parent_header'] - if not parent: - self.log.debug("iopub::IOPub message lacks parent: %r", msg) - return - msg_id = parent['msg_id'] - msg_type = msg['header']['msg_type'] - content = msg['content'] - - # ensure msg_id is in db - try: - rec = self.db.get_record(msg_id) - except KeyError: - rec = None - - # stream - d = {} - if msg_type == 'stream': - name = content['name'] - s = '' if rec is None else rec[name] - d[name] = s + content['text'] - - elif msg_type == 'error': - d['error'] = content - elif msg_type == 'execute_input': - d['execute_input'] = content['code'] - elif msg_type in ('display_data', 'execute_result'): - d[msg_type] = content - elif msg_type == 'status': - pass - elif msg_type == 'data_pub': - self.log.info("ignored data_pub message for %s" % msg_id) - else: - self.log.warn("unhandled iopub msg_type: %r", msg_type) - - if not d: - return - - if rec is None: - # new record - rec = empty_record() - rec['msg_id'] = msg_id - rec.update(d) - d = rec - update_record = self.db.add_record - else: - update_record = self.db.update_record - - try: - update_record(msg_id, d) - except Exception: - self.log.error("DB Error saving iopub message %r", msg_id, exc_info=True) - - - - #------------------------------------------------------------------------- - # Registration requests - #------------------------------------------------------------------------- - - def connection_request(self, client_id, msg): - """Reply with connection addresses for clients.""" - self.log.info("client::client %r connected", client_id) - content = dict(status='ok') - jsonable = {} - for k,v in iteritems(self.keytable): - if v not in self.dead_engines: - jsonable[str(k)] = v - content['engines'] = jsonable - self.session.send(self.query, 'connection_reply', content, parent=msg, ident=client_id) - - def register_engine(self, reg, msg): - """Register a new engine.""" - content = msg['content'] - try: - uuid = content['uuid'] - except KeyError: - self.log.error("registration::queue not specified", exc_info=True) - return - - eid = self._next_id - - self.log.debug("registration::register_engine(%i, %r)", eid, uuid) - - content = dict(id=eid,status='ok',hb_period=self.heartmonitor.period) - # check if requesting available IDs: - if cast_bytes(uuid) in self.by_ident: - try: - raise KeyError("uuid %r in use" % uuid) - except: - content = error.wrap_exception() - self.log.error("uuid %r in use", uuid, exc_info=True) - else: - for h, ec in iteritems(self.incoming_registrations): - if uuid == h: - try: - raise KeyError("heart_id %r in use" % uuid) - except: - self.log.error("heart_id %r in use", uuid, exc_info=True) - content = error.wrap_exception() - break - elif uuid == ec.uuid: - try: - raise KeyError("uuid %r in use" % uuid) - except: - self.log.error("uuid %r in use", uuid, exc_info=True) - content = error.wrap_exception() - break - - msg = self.session.send(self.query, "registration_reply", - content=content, - ident=reg) - - heart = cast_bytes(uuid) - - if content['status'] == 'ok': - if heart in self.heartmonitor.hearts: - # already beating - self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid) - self.finish_registration(heart) - else: - purge = lambda : self._purge_stalled_registration(heart) - t = self.loop.add_timeout( - self.loop.time() + self.registration_timeout, - purge, - ) - self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid,stallback=t) - else: - self.log.error("registration::registration %i failed: %r", eid, content['evalue']) - - return eid - - def unregister_engine(self, ident, msg): - """Unregister an engine that explicitly requested to leave.""" - try: - eid = msg['content']['id'] - except: - self.log.error("registration::bad engine id for unregistration: %r", ident, exc_info=True) - return - self.log.info("registration::unregister_engine(%r)", eid) - - uuid = self.keytable[eid] - content=dict(id=eid, uuid=uuid) - self.dead_engines.add(uuid) - - self.loop.add_timeout( - self.loop.time() + self.registration_timeout, - lambda : self._handle_stranded_msgs(eid, uuid), - ) - ############## TODO: HANDLE IT ################ - - self._save_engine_state() - - if self.notifier: - self.session.send(self.notifier, "unregistration_notification", content=content) - - def _handle_stranded_msgs(self, eid, uuid): - """Handle messages known to be on an engine when the engine unregisters. - - It is possible that this will fire prematurely - that is, an engine will - go down after completing a result, and the client will be notified - that the result failed and later receive the actual result. - """ - - outstanding = self.queues[eid] - - for msg_id in outstanding: - self.pending.remove(msg_id) - self.all_completed.add(msg_id) - try: - raise error.EngineError("Engine %r died while running task %r" % (eid, msg_id)) - except: - content = error.wrap_exception() - # build a fake header: - header = {} - header['engine'] = uuid - header['date'] = datetime.now() - rec = dict(result_content=content, result_header=header, result_buffers=[]) - rec['completed'] = header['date'] - rec['engine_uuid'] = uuid - try: - self.db.update_record(msg_id, rec) - except Exception: - self.log.error("DB Error handling stranded msg %r", msg_id, exc_info=True) - - - def finish_registration(self, heart): - """Second half of engine registration, called after our HeartMonitor - has received a beat from the Engine's Heart.""" - try: - ec = self.incoming_registrations.pop(heart) - except KeyError: - self.log.error("registration::tried to finish nonexistant registration", exc_info=True) - return - self.log.info("registration::finished registering engine %i:%s", ec.id, ec.uuid) - if ec.stallback is not None: - self.loop.remove_timeout(ec.stallback) - eid = ec.id - self.ids.add(eid) - self.keytable[eid] = ec.uuid - self.engines[eid] = ec - self.by_ident[cast_bytes(ec.uuid)] = ec.id - self.queues[eid] = list() - self.tasks[eid] = list() - self.completed[eid] = list() - self.hearts[heart] = eid - content = dict(id=eid, uuid=self.engines[eid].uuid) - if self.notifier: - self.session.send(self.notifier, "registration_notification", content=content) - self.log.info("engine::Engine Connected: %i", eid) - - self._save_engine_state() - - def _purge_stalled_registration(self, heart): - if heart in self.incoming_registrations: - ec = self.incoming_registrations.pop(heart) - self.log.info("registration::purging stalled registration: %i", ec.id) - else: - pass - - #------------------------------------------------------------------------- - # Engine State - #------------------------------------------------------------------------- - - - def _cleanup_engine_state_file(self): - """cleanup engine state mapping""" - - if os.path.exists(self.engine_state_file): - self.log.debug("cleaning up engine state: %s", self.engine_state_file) - try: - os.remove(self.engine_state_file) - except IOError: - self.log.error("Couldn't cleanup file: %s", self.engine_state_file, exc_info=True) - - - def _save_engine_state(self): - """save engine mapping to JSON file""" - if not self.engine_state_file: - return - self.log.debug("save engine state to %s" % self.engine_state_file) - state = {} - engines = {} - for eid, ec in iteritems(self.engines): - if ec.uuid not in self.dead_engines: - engines[eid] = ec.uuid - - state['engines'] = engines - - state['next_id'] = self._idcounter - - with open(self.engine_state_file, 'w') as f: - json.dump(state, f) - - - def _load_engine_state(self): - """load engine mapping from JSON file""" - if not os.path.exists(self.engine_state_file): - return - - self.log.info("loading engine state from %s" % self.engine_state_file) - - with open(self.engine_state_file) as f: - state = json.load(f) - - save_notifier = self.notifier - self.notifier = None - for eid, uuid in iteritems(state['engines']): - heart = uuid.encode('ascii') - # start with this heart as current and beating: - self.heartmonitor.responses.add(heart) - self.heartmonitor.hearts.add(heart) - - self.incoming_registrations[heart] = EngineConnector(id=int(eid), uuid=uuid) - self.finish_registration(heart) - - self.notifier = save_notifier - - self._idcounter = state['next_id'] - - #------------------------------------------------------------------------- - # Client Requests - #------------------------------------------------------------------------- - - def shutdown_request(self, client_id, msg): - """handle shutdown request.""" - self.session.send(self.query, 'shutdown_reply', content={'status': 'ok'}, ident=client_id) - # also notify other clients of shutdown - self.session.send(self.notifier, 'shutdown_notice', content={'status': 'ok'}) - self.loop.add_timeout(self.loop.time() + 1, self._shutdown) - - def _shutdown(self): - self.log.info("hub::hub shutting down.") - time.sleep(0.1) - sys.exit(0) - - - def check_load(self, client_id, msg): - content = msg['content'] - try: - targets = content['targets'] - targets = self._validate_targets(targets) - except: - content = error.wrap_exception() - self.session.send(self.query, "hub_error", - content=content, ident=client_id) - return - - content = dict(status='ok') - # loads = {} - for t in targets: - content[bytes(t)] = len(self.queues[t])+len(self.tasks[t]) - self.session.send(self.query, "load_reply", content=content, ident=client_id) - - - def queue_status(self, client_id, msg): - """Return the Queue status of one or more targets. - - If verbose, return the msg_ids, else return len of each type. - - Keys: - - * queue (pending MUX jobs) - * tasks (pending Task jobs) - * completed (finished jobs from both queues) - """ - content = msg['content'] - targets = content['targets'] - try: - targets = self._validate_targets(targets) - except: - content = error.wrap_exception() - self.session.send(self.query, "hub_error", - content=content, ident=client_id) - return - verbose = content.get('verbose', False) - content = dict(status='ok') - for t in targets: - queue = self.queues[t] - completed = self.completed[t] - tasks = self.tasks[t] - if not verbose: - queue = len(queue) - completed = len(completed) - tasks = len(tasks) - content[str(t)] = {'queue': queue, 'completed': completed , 'tasks': tasks} - content['unassigned'] = list(self.unassigned) if verbose else len(self.unassigned) - # print (content) - self.session.send(self.query, "queue_reply", content=content, ident=client_id) - - def purge_results(self, client_id, msg): - """Purge results from memory. This method is more valuable before we move - to a DB based message storage mechanism.""" - content = msg['content'] - self.log.info("Dropping records with %s", content) - msg_ids = content.get('msg_ids', []) - reply = dict(status='ok') - if msg_ids == 'all': - try: - self.db.drop_matching_records(dict(completed={'$ne':None})) - except Exception: - reply = error.wrap_exception() - self.log.exception("Error dropping records") - else: - pending = [m for m in msg_ids if (m in self.pending)] - if pending: - try: - raise IndexError("msg pending: %r" % pending[0]) - except: - reply = error.wrap_exception() - self.log.exception("Error dropping records") - else: - try: - self.db.drop_matching_records(dict(msg_id={'$in':msg_ids})) - except Exception: - reply = error.wrap_exception() - self.log.exception("Error dropping records") - - if reply['status'] == 'ok': - eids = content.get('engine_ids', []) - for eid in eids: - if eid not in self.engines: - try: - raise IndexError("No such engine: %i" % eid) - except: - reply = error.wrap_exception() - self.log.exception("Error dropping records") - break - uid = self.engines[eid].uuid - try: - self.db.drop_matching_records(dict(engine_uuid=uid, completed={'$ne':None})) - except Exception: - reply = error.wrap_exception() - self.log.exception("Error dropping records") - break - - self.session.send(self.query, 'purge_reply', content=reply, ident=client_id) - - def resubmit_task(self, client_id, msg): - """Resubmit one or more tasks.""" - def finish(reply): - self.session.send(self.query, 'resubmit_reply', content=reply, ident=client_id) - - content = msg['content'] - msg_ids = content['msg_ids'] - reply = dict(status='ok') - try: - records = self.db.find_records({'msg_id' : {'$in' : msg_ids}}, keys=[ - 'header', 'content', 'buffers']) - except Exception: - self.log.error('db::db error finding tasks to resubmit', exc_info=True) - return finish(error.wrap_exception()) - - # validate msg_ids - found_ids = [ rec['msg_id'] for rec in records ] - pending_ids = [ msg_id for msg_id in found_ids if msg_id in self.pending ] - if len(records) > len(msg_ids): - try: - raise RuntimeError("DB appears to be in an inconsistent state." - "More matching records were found than should exist") - except Exception: - self.log.exception("Failed to resubmit task") - return finish(error.wrap_exception()) - elif len(records) < len(msg_ids): - missing = [ m for m in msg_ids if m not in found_ids ] - try: - raise KeyError("No such msg(s): %r" % missing) - except KeyError: - self.log.exception("Failed to resubmit task") - return finish(error.wrap_exception()) - elif pending_ids: - pass - # no need to raise on resubmit of pending task, now that we - # resubmit under new ID, but do we want to raise anyway? - # msg_id = invalid_ids[0] - # try: - # raise ValueError("Task(s) %r appears to be inflight" % ) - # except Exception: - # return finish(error.wrap_exception()) - - # mapping of original IDs to resubmitted IDs - resubmitted = {} - - # send the messages - for rec in records: - header = rec['header'] - msg = self.session.msg(header['msg_type'], parent=header) - msg_id = msg['msg_id'] - msg['content'] = rec['content'] - - # use the old header, but update msg_id and timestamp - fresh = msg['header'] - header['msg_id'] = fresh['msg_id'] - header['date'] = fresh['date'] - msg['header'] = header - - self.session.send(self.resubmit, msg, buffers=rec['buffers']) - - resubmitted[rec['msg_id']] = msg_id - self.pending.add(msg_id) - msg['buffers'] = rec['buffers'] - try: - self.db.add_record(msg_id, init_record(msg)) - except Exception: - self.log.error("db::DB Error updating record: %s", msg_id, exc_info=True) - return finish(error.wrap_exception()) - - finish(dict(status='ok', resubmitted=resubmitted)) - - # store the new IDs in the Task DB - for msg_id, resubmit_id in iteritems(resubmitted): - try: - self.db.update_record(msg_id, {'resubmitted' : resubmit_id}) - except Exception: - self.log.error("db::DB Error updating record: %s", msg_id, exc_info=True) - - - def _extract_record(self, rec): - """decompose a TaskRecord dict into subsection of reply for get_result""" - io_dict = {} - for key in ('execute_input', 'execute_result', 'error', 'stdout', 'stderr'): - io_dict[key] = rec[key] - content = { - 'header': rec['header'], - 'metadata': rec['metadata'], - 'result_metadata': rec['result_metadata'], - 'result_header' : rec['result_header'], - 'result_content': rec['result_content'], - 'received' : rec['received'], - 'io' : io_dict, - } - if rec['result_buffers']: - buffers = list(map(bytes, rec['result_buffers'])) - else: - buffers = [] - - return content, buffers - - def get_results(self, client_id, msg): - """Get the result of 1 or more messages.""" - content = msg['content'] - msg_ids = sorted(set(content['msg_ids'])) - statusonly = content.get('status_only', False) - pending = [] - completed = [] - content = dict(status='ok') - content['pending'] = pending - content['completed'] = completed - buffers = [] - if not statusonly: - try: - matches = self.db.find_records(dict(msg_id={'$in':msg_ids})) - # turn match list into dict, for faster lookup - records = {} - for rec in matches: - records[rec['msg_id']] = rec - except Exception: - content = error.wrap_exception() - self.log.exception("Failed to get results") - self.session.send(self.query, "result_reply", content=content, - parent=msg, ident=client_id) - return - else: - records = {} - for msg_id in msg_ids: - if msg_id in self.pending: - pending.append(msg_id) - elif msg_id in self.all_completed: - completed.append(msg_id) - if not statusonly: - c,bufs = self._extract_record(records[msg_id]) - content[msg_id] = c - buffers.extend(bufs) - elif msg_id in records: - if rec['completed']: - completed.append(msg_id) - c,bufs = self._extract_record(records[msg_id]) - content[msg_id] = c - buffers.extend(bufs) - else: - pending.append(msg_id) - else: - try: - raise KeyError('No such message: '+msg_id) - except: - content = error.wrap_exception() - break - self.session.send(self.query, "result_reply", content=content, - parent=msg, ident=client_id, - buffers=buffers) - - def get_history(self, client_id, msg): - """Get a list of all msg_ids in our DB records""" - try: - msg_ids = self.db.get_history() - except Exception as e: - content = error.wrap_exception() - self.log.exception("Failed to get history") - else: - content = dict(status='ok', history=msg_ids) - - self.session.send(self.query, "history_reply", content=content, - parent=msg, ident=client_id) - - def db_query(self, client_id, msg): - """Perform a raw query on the task record database.""" - content = msg['content'] - query = extract_dates(content.get('query', {})) - keys = content.get('keys', None) - buffers = [] - empty = list() - try: - records = self.db.find_records(query, keys) - except Exception as e: - content = error.wrap_exception() - self.log.exception("DB query failed") - else: - # extract buffers from reply content: - if keys is not None: - buffer_lens = [] if 'buffers' in keys else None - result_buffer_lens = [] if 'result_buffers' in keys else None - else: - buffer_lens = None - result_buffer_lens = None - - for rec in records: - # buffers may be None, so double check - b = rec.pop('buffers', empty) or empty - if buffer_lens is not None: - buffer_lens.append(len(b)) - buffers.extend(b) - rb = rec.pop('result_buffers', empty) or empty - if result_buffer_lens is not None: - result_buffer_lens.append(len(rb)) - buffers.extend(rb) - content = dict(status='ok', records=records, buffer_lens=buffer_lens, - result_buffer_lens=result_buffer_lens) - # self.log.debug (content) - self.session.send(self.query, "db_reply", content=content, - parent=msg, ident=client_id, - buffers=buffers) - diff --git a/ipython_parallel/controller/mongodb.py b/ipython_parallel/controller/mongodb.py deleted file mode 100644 index 23e7178..0000000 --- a/ipython_parallel/controller/mongodb.py +++ /dev/null @@ -1,122 +0,0 @@ -"""A TaskRecord backend using mongodb - -Authors: - -* Min RK -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2010-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -from pymongo import Connection - -# bson.Binary import moved -try: - from bson.binary import Binary -except ImportError: - from bson import Binary - -from IPython.utils.traitlets import Dict, List, Unicode, Instance - -from .dictdb import BaseDB - -#----------------------------------------------------------------------------- -# MongoDB class -#----------------------------------------------------------------------------- - -class MongoDB(BaseDB): - """MongoDB TaskRecord backend.""" - - connection_args = List(config=True, - help="""Positional arguments to be passed to pymongo.Connection. Only - necessary if the default mongodb configuration does not point to your - mongod instance.""") - connection_kwargs = Dict(config=True, - help="""Keyword arguments to be passed to pymongo.Connection. Only - necessary if the default mongodb configuration does not point to your - mongod instance.""" - ) - database = Unicode("ipython-tasks", config=True, - help="""The MongoDB database name to use for storing tasks for this session. If unspecified, - a new database will be created with the Hub's IDENT. Specifying the database will result - in tasks from previous sessions being available via Clients' db_query and - get_result methods.""") - - _connection = Instance(Connection, allow_none=True) # pymongo connection - - def __init__(self, **kwargs): - super(MongoDB, self).__init__(**kwargs) - if self._connection is None: - self._connection = Connection(*self.connection_args, **self.connection_kwargs) - if not self.database: - self.database = self.session - self._db = self._connection[self.database] - self._records = self._db['task_records'] - self._records.ensure_index('msg_id', unique=True) - self._records.ensure_index('submitted') # for sorting history - # for rec in self._records.find - - def _binary_buffers(self, rec): - for key in ('buffers', 'result_buffers'): - if rec.get(key, None): - rec[key] = list(map(Binary, rec[key])) - return rec - - def add_record(self, msg_id, rec): - """Add a new Task Record, by msg_id.""" - # print rec - rec = self._binary_buffers(rec) - self._records.insert(rec) - - def get_record(self, msg_id): - """Get a specific Task Record, by msg_id.""" - r = self._records.find_one({'msg_id': msg_id}) - if not r: - # r will be '' if nothing is found - raise KeyError(msg_id) - return r - - def update_record(self, msg_id, rec): - """Update the data in an existing record.""" - rec = self._binary_buffers(rec) - - self._records.update({'msg_id':msg_id}, {'$set': rec}) - - def drop_matching_records(self, check): - """Remove a record from the DB.""" - self._records.remove(check) - - def drop_record(self, msg_id): - """Remove a record from the DB.""" - self._records.remove({'msg_id':msg_id}) - - def find_records(self, check, keys=None): - """Find records matching a query dict, optionally extracting subset of keys. - - Returns list of matching records. - - Parameters - ---------- - - check: dict - mongodb-style query argument - keys: list of strs [optional] - if specified, the subset of keys to extract. msg_id will *always* be - included. - """ - if keys and 'msg_id' not in keys: - keys.append('msg_id') - matches = list(self._records.find(check,keys)) - for rec in matches: - rec.pop('_id') - return matches - - def get_history(self): - """get all msg_ids, ordered by time submitted.""" - cursor = self._records.find({},{'msg_id':1}).sort('submitted') - return [ rec['msg_id'] for rec in cursor ] - - diff --git a/ipython_parallel/controller/scheduler.py b/ipython_parallel/controller/scheduler.py deleted file mode 100644 index c2c168f..0000000 --- a/ipython_parallel/controller/scheduler.py +++ /dev/null @@ -1,849 +0,0 @@ -"""The Python scheduler for rich scheduling. - -The Pure ZMQ scheduler does not allow routing schemes other than LRU, -nor does it check msg_id DAG dependencies. For those, a slightly slower -Python Scheduler exists. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import logging -import sys -import time - -from collections import deque -from datetime import datetime -from random import randint, random -from types import FunctionType - -try: - import numpy -except ImportError: - numpy = None - -import zmq -from zmq.eventloop import ioloop, zmqstream - -# local imports -from decorator import decorator -from IPython.config.application import Application -from IPython.config.loader import Config -from IPython.utils.traitlets import Instance, Dict, List, Set, Integer, Enum, CBytes -from IPython.utils.py3compat import cast_bytes - -from ipython_parallel import error, util -from ipython_parallel.factory import SessionFactory -from ipython_parallel.util import connect_logger, local_logger - -from .dependency import Dependency - -@decorator -def logged(f,self,*args,**kwargs): - # print ("#--------------------") - self.log.debug("scheduler::%s(*%s,**%s)", f.__name__, args, kwargs) - # print ("#--") - return f(self,*args, **kwargs) - -#---------------------------------------------------------------------- -# Chooser functions -#---------------------------------------------------------------------- - -def plainrandom(loads): - """Plain random pick.""" - n = len(loads) - return randint(0,n-1) - -def lru(loads): - """Always pick the front of the line. - - The content of `loads` is ignored. - - Assumes LRU ordering of loads, with oldest first. - """ - return 0 - -def twobin(loads): - """Pick two at random, use the LRU of the two. - - The content of loads is ignored. - - Assumes LRU ordering of loads, with oldest first. - """ - n = len(loads) - a = randint(0,n-1) - b = randint(0,n-1) - return min(a,b) - -def weighted(loads): - """Pick two at random using inverse load as weight. - - Return the less loaded of the two. - """ - # weight 0 a million times more than 1: - weights = 1./(1e-6+numpy.array(loads)) - sums = weights.cumsum() - t = sums[-1] - x = random()*t - y = random()*t - idx = 0 - idy = 0 - while sums[idx] < x: - idx += 1 - while sums[idy] < y: - idy += 1 - if weights[idy] > weights[idx]: - return idy - else: - return idx - -def leastload(loads): - """Always choose the lowest load. - - If the lowest load occurs more than once, the first - occurance will be used. If loads has LRU ordering, this means - the LRU of those with the lowest load is chosen. - """ - return loads.index(min(loads)) - -#--------------------------------------------------------------------- -# Classes -#--------------------------------------------------------------------- - - -# store empty default dependency: -MET = Dependency([]) - - -class Job(object): - """Simple container for a job""" - def __init__(self, msg_id, raw_msg, idents, msg, header, metadata, - targets, after, follow, timeout): - self.msg_id = msg_id - self.raw_msg = raw_msg - self.idents = idents - self.msg = msg - self.header = header - self.metadata = metadata - self.targets = targets - self.after = after - self.follow = follow - self.timeout = timeout - - self.removed = False # used for lazy-delete from sorted queue - self.timestamp = time.time() - self.timeout_id = 0 - self.blacklist = set() - - def __lt__(self, other): - return self.timestamp < other.timestamp - - def __cmp__(self, other): - return cmp(self.timestamp, other.timestamp) - - @property - def dependents(self): - return self.follow.union(self.after) - - -class TaskScheduler(SessionFactory): - """Python TaskScheduler object. - - This is the simplest object that supports msg_id based - DAG dependencies. *Only* task msg_ids are checked, not - msg_ids of jobs submitted via the MUX queue. - - """ - - hwm = Integer(1, config=True, - help="""specify the High Water Mark (HWM) for the downstream - socket in the Task scheduler. This is the maximum number - of allowed outstanding tasks on each engine. - - The default (1) means that only one task can be outstanding on each - engine. Setting TaskScheduler.hwm=0 means there is no limit, and the - engines continue to be assigned tasks while they are working, - effectively hiding network latency behind computation, but can result - in an imbalance of work when submitting many heterogenous tasks all at - once. Any positive value greater than one is a compromise between the - two. - - """ - ) - scheme_name = Enum(('leastload', 'pure', 'lru', 'plainrandom', 'weighted', 'twobin'), - 'leastload', config=True, -help="""select the task scheduler scheme [default: Python LRU] - Options are: 'pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'""" - ) - def _scheme_name_changed(self, old, new): - self.log.debug("Using scheme %r"%new) - self.scheme = globals()[new] - - # input arguments: - scheme = Instance(FunctionType) # function for determining the destination - def _scheme_default(self): - return leastload - client_stream = Instance(zmqstream.ZMQStream, allow_none=True) # client-facing stream - engine_stream = Instance(zmqstream.ZMQStream, allow_none=True) # engine-facing stream - notifier_stream = Instance(zmqstream.ZMQStream, allow_none=True) # hub-facing sub stream - mon_stream = Instance(zmqstream.ZMQStream, allow_none=True) # hub-facing pub stream - query_stream = Instance(zmqstream.ZMQStream, allow_none=True) # hub-facing DEALER stream - - # internals: - queue = Instance(deque) # sorted list of Jobs - def _queue_default(self): - return deque() - queue_map = Dict() # dict by msg_id of Jobs (for O(1) access to the Queue) - graph = Dict() # dict by msg_id of [ msg_ids that depend on key ] - retries = Dict() # dict by msg_id of retries remaining (non-neg ints) - # waiting = List() # list of msg_ids ready to run, but haven't due to HWM - pending = Dict() # dict by engine_uuid of submitted tasks - completed = Dict() # dict by engine_uuid of completed tasks - failed = Dict() # dict by engine_uuid of failed tasks - destinations = Dict() # dict by msg_id of engine_uuids where jobs ran (reverse of completed+failed) - clients = Dict() # dict by msg_id for who submitted the task - targets = List() # list of target IDENTs - loads = List() # list of engine loads - # full = Set() # set of IDENTs that have HWM outstanding tasks - all_completed = Set() # set of all completed tasks - all_failed = Set() # set of all failed tasks - all_done = Set() # set of all finished tasks=union(completed,failed) - all_ids = Set() # set of all submitted task IDs - - ident = CBytes() # ZMQ identity. This should just be self.session.session - # but ensure Bytes - def _ident_default(self): - return self.session.bsession - - def start(self): - self.query_stream.on_recv(self.dispatch_query_reply) - self.session.send(self.query_stream, "connection_request", {}) - - self.engine_stream.on_recv(self.dispatch_result, copy=False) - self.client_stream.on_recv(self.dispatch_submission, copy=False) - - self._notification_handlers = dict( - registration_notification = self._register_engine, - unregistration_notification = self._unregister_engine - ) - self.notifier_stream.on_recv(self.dispatch_notification) - self.log.info("Scheduler started [%s]" % self.scheme_name) - - def resume_receiving(self): - """Resume accepting jobs.""" - self.client_stream.on_recv(self.dispatch_submission, copy=False) - - def stop_receiving(self): - """Stop accepting jobs while there are no engines. - Leave them in the ZMQ queue.""" - self.client_stream.on_recv(None) - - #----------------------------------------------------------------------- - # [Un]Registration Handling - #----------------------------------------------------------------------- - - - def dispatch_query_reply(self, msg): - """handle reply to our initial connection request""" - try: - idents,msg = self.session.feed_identities(msg) - except ValueError: - self.log.warn("task::Invalid Message: %r",msg) - return - try: - msg = self.session.deserialize(msg) - except ValueError: - self.log.warn("task::Unauthorized message from: %r"%idents) - return - - content = msg['content'] - for uuid in content.get('engines', {}).values(): - self._register_engine(cast_bytes(uuid)) - - - @util.log_errors - def dispatch_notification(self, msg): - """dispatch register/unregister events.""" - try: - idents,msg = self.session.feed_identities(msg) - except ValueError: - self.log.warn("task::Invalid Message: %r",msg) - return - try: - msg = self.session.deserialize(msg) - except ValueError: - self.log.warn("task::Unauthorized message from: %r"%idents) - return - - msg_type = msg['header']['msg_type'] - - handler = self._notification_handlers.get(msg_type, None) - if handler is None: - self.log.error("Unhandled message type: %r"%msg_type) - else: - try: - handler(cast_bytes(msg['content']['uuid'])) - except Exception: - self.log.error("task::Invalid notification msg: %r", msg, exc_info=True) - - def _register_engine(self, uid): - """New engine with ident `uid` became available.""" - # head of the line: - self.targets.insert(0,uid) - self.loads.insert(0,0) - - # initialize sets - self.completed[uid] = set() - self.failed[uid] = set() - self.pending[uid] = {} - - # rescan the graph: - self.update_graph(None) - - def _unregister_engine(self, uid): - """Existing engine with ident `uid` became unavailable.""" - if len(self.targets) == 1: - # this was our only engine - pass - - # handle any potentially finished tasks: - self.engine_stream.flush() - - # don't pop destinations, because they might be used later - # map(self.destinations.pop, self.completed.pop(uid)) - # map(self.destinations.pop, self.failed.pop(uid)) - - # prevent this engine from receiving work - idx = self.targets.index(uid) - self.targets.pop(idx) - self.loads.pop(idx) - - # wait 5 seconds before cleaning up pending jobs, since the results might - # still be incoming - if self.pending[uid]: - self.loop.add_timeout(self.loop.time() + 5, - lambda : self.handle_stranded_tasks(uid), - ) - else: - self.completed.pop(uid) - self.failed.pop(uid) - - - def handle_stranded_tasks(self, engine): - """Deal with jobs resident in an engine that died.""" - lost = self.pending[engine] - for msg_id in lost.keys(): - if msg_id not in self.pending[engine]: - # prevent double-handling of messages - continue - - raw_msg = lost[msg_id].raw_msg - idents,msg = self.session.feed_identities(raw_msg, copy=False) - parent = self.session.unpack(msg[1].bytes) - idents = [engine, idents[0]] - - # build fake error reply - try: - raise error.EngineError("Engine %r died while running task %r"%(engine, msg_id)) - except: - content = error.wrap_exception() - # build fake metadata - md = dict( - status=u'error', - engine=engine.decode('ascii'), - date=datetime.now(), - ) - msg = self.session.msg('apply_reply', content, parent=parent, metadata=md) - raw_reply = list(map(zmq.Message, self.session.serialize(msg, ident=idents))) - # and dispatch it - self.dispatch_result(raw_reply) - - # finally scrub completed/failed lists - self.completed.pop(engine) - self.failed.pop(engine) - - - #----------------------------------------------------------------------- - # Job Submission - #----------------------------------------------------------------------- - - - @util.log_errors - def dispatch_submission(self, raw_msg): - """Dispatch job submission to appropriate handlers.""" - # ensure targets up to date: - self.notifier_stream.flush() - try: - idents, msg = self.session.feed_identities(raw_msg, copy=False) - msg = self.session.deserialize(msg, content=False, copy=False) - except Exception: - self.log.error("task::Invaid task msg: %r"%raw_msg, exc_info=True) - return - - - # send to monitor - self.mon_stream.send_multipart([b'intask']+raw_msg, copy=False) - - header = msg['header'] - md = msg['metadata'] - msg_id = header['msg_id'] - self.all_ids.add(msg_id) - - # get targets as a set of bytes objects - # from a list of unicode objects - targets = md.get('targets', []) - targets = set(map(cast_bytes, targets)) - - retries = md.get('retries', 0) - self.retries[msg_id] = retries - - # time dependencies - after = md.get('after', None) - if after: - after = Dependency(after) - if after.all: - if after.success: - after = Dependency(after.difference(self.all_completed), - success=after.success, - failure=after.failure, - all=after.all, - ) - if after.failure: - after = Dependency(after.difference(self.all_failed), - success=after.success, - failure=after.failure, - all=after.all, - ) - if after.check(self.all_completed, self.all_failed): - # recast as empty set, if `after` already met, - # to prevent unnecessary set comparisons - after = MET - else: - after = MET - - # location dependencies - follow = Dependency(md.get('follow', [])) - - timeout = md.get('timeout', None) - if timeout: - timeout = float(timeout) - - job = Job(msg_id=msg_id, raw_msg=raw_msg, idents=idents, msg=msg, - header=header, targets=targets, after=after, follow=follow, - timeout=timeout, metadata=md, - ) - # validate and reduce dependencies: - for dep in after,follow: - if not dep: # empty dependency - continue - # check valid: - if msg_id in dep or dep.difference(self.all_ids): - self.queue_map[msg_id] = job - return self.fail_unreachable(msg_id, error.InvalidDependency) - # check if unreachable: - if dep.unreachable(self.all_completed, self.all_failed): - self.queue_map[msg_id] = job - return self.fail_unreachable(msg_id) - - if after.check(self.all_completed, self.all_failed): - # time deps already met, try to run - if not self.maybe_run(job): - # can't run yet - if msg_id not in self.all_failed: - # could have failed as unreachable - self.save_unmet(job) - else: - self.save_unmet(job) - - def job_timeout(self, job, timeout_id): - """callback for a job's timeout. - - The job may or may not have been run at this point. - """ - if job.timeout_id != timeout_id: - # not the most recent call - return - now = time.time() - if job.timeout >= (now + 1): - self.log.warn("task %s timeout fired prematurely: %s > %s", - job.msg_id, job.timeout, now - ) - if job.msg_id in self.queue_map: - # still waiting, but ran out of time - self.log.info("task %r timed out", job.msg_id) - self.fail_unreachable(job.msg_id, error.TaskTimeout) - - def fail_unreachable(self, msg_id, why=error.ImpossibleDependency): - """a task has become unreachable, send a reply with an ImpossibleDependency - error.""" - if msg_id not in self.queue_map: - self.log.error("task %r already failed!", msg_id) - return - job = self.queue_map.pop(msg_id) - # lazy-delete from the queue - job.removed = True - for mid in job.dependents: - if mid in self.graph: - self.graph[mid].remove(msg_id) - - try: - raise why() - except: - content = error.wrap_exception() - self.log.debug("task %r failing as unreachable with: %s", msg_id, content['ename']) - - self.all_done.add(msg_id) - self.all_failed.add(msg_id) - - msg = self.session.send(self.client_stream, 'apply_reply', content, - parent=job.header, ident=job.idents) - self.session.send(self.mon_stream, msg, ident=[b'outtask']+job.idents) - - self.update_graph(msg_id, success=False) - - def available_engines(self): - """return a list of available engine indices based on HWM""" - if not self.hwm: - return list(range(len(self.targets))) - available = [] - for idx in range(len(self.targets)): - if self.loads[idx] < self.hwm: - available.append(idx) - return available - - def maybe_run(self, job): - """check location dependencies, and run if they are met.""" - msg_id = job.msg_id - self.log.debug("Attempting to assign task %s", msg_id) - available = self.available_engines() - if not available: - # no engines, definitely can't run - return False - - if job.follow or job.targets or job.blacklist or self.hwm: - # we need a can_run filter - def can_run(idx): - # check hwm - if self.hwm and self.loads[idx] == self.hwm: - return False - target = self.targets[idx] - # check blacklist - if target in job.blacklist: - return False - # check targets - if job.targets and target not in job.targets: - return False - # check follow - return job.follow.check(self.completed[target], self.failed[target]) - - indices = list(filter(can_run, available)) - - if not indices: - # couldn't run - if job.follow.all: - # check follow for impossibility - dests = set() - relevant = set() - if job.follow.success: - relevant = self.all_completed - if job.follow.failure: - relevant = relevant.union(self.all_failed) - for m in job.follow.intersection(relevant): - dests.add(self.destinations[m]) - if len(dests) > 1: - self.queue_map[msg_id] = job - self.fail_unreachable(msg_id) - return False - if job.targets: - # check blacklist+targets for impossibility - job.targets.difference_update(job.blacklist) - if not job.targets or not job.targets.intersection(self.targets): - self.queue_map[msg_id] = job - self.fail_unreachable(msg_id) - return False - return False - else: - indices = None - - self.submit_task(job, indices) - return True - - def save_unmet(self, job): - """Save a message for later submission when its dependencies are met.""" - msg_id = job.msg_id - self.log.debug("Adding task %s to the queue", msg_id) - self.queue_map[msg_id] = job - self.queue.append(job) - # track the ids in follow or after, but not those already finished - for dep_id in job.after.union(job.follow).difference(self.all_done): - if dep_id not in self.graph: - self.graph[dep_id] = set() - self.graph[dep_id].add(msg_id) - - # schedule timeout callback - if job.timeout: - timeout_id = job.timeout_id = job.timeout_id + 1 - self.loop.add_timeout(time.time() + job.timeout, - lambda : self.job_timeout(job, timeout_id) - ) - - - def submit_task(self, job, indices=None): - """Submit a task to any of a subset of our targets.""" - if indices: - loads = [self.loads[i] for i in indices] - else: - loads = self.loads - idx = self.scheme(loads) - if indices: - idx = indices[idx] - target = self.targets[idx] - # print (target, map(str, msg[:3])) - # send job to the engine - self.engine_stream.send(target, flags=zmq.SNDMORE, copy=False) - self.engine_stream.send_multipart(job.raw_msg, copy=False) - # update load - self.add_job(idx) - self.pending[target][job.msg_id] = job - # notify Hub - content = dict(msg_id=job.msg_id, engine_id=target.decode('ascii')) - self.session.send(self.mon_stream, 'task_destination', content=content, - ident=[b'tracktask',self.ident]) - - - #----------------------------------------------------------------------- - # Result Handling - #----------------------------------------------------------------------- - - - @util.log_errors - def dispatch_result(self, raw_msg): - """dispatch method for result replies""" - try: - idents,msg = self.session.feed_identities(raw_msg, copy=False) - msg = self.session.deserialize(msg, content=False, copy=False) - engine = idents[0] - try: - idx = self.targets.index(engine) - except ValueError: - pass # skip load-update for dead engines - else: - self.finish_job(idx) - except Exception: - self.log.error("task::Invalid result: %r", raw_msg, exc_info=True) - return - - md = msg['metadata'] - parent = msg['parent_header'] - if md.get('dependencies_met', True): - success = (md['status'] == 'ok') - msg_id = parent['msg_id'] - retries = self.retries[msg_id] - if not success and retries > 0: - # failed - self.retries[msg_id] = retries - 1 - self.handle_unmet_dependency(idents, parent) - else: - del self.retries[msg_id] - # relay to client and update graph - self.handle_result(idents, parent, raw_msg, success) - # send to Hub monitor - self.mon_stream.send_multipart([b'outtask']+raw_msg, copy=False) - else: - self.handle_unmet_dependency(idents, parent) - - def handle_result(self, idents, parent, raw_msg, success=True): - """handle a real task result, either success or failure""" - # first, relay result to client - engine = idents[0] - client = idents[1] - # swap_ids for ROUTER-ROUTER mirror - raw_msg[:2] = [client,engine] - # print (map(str, raw_msg[:4])) - self.client_stream.send_multipart(raw_msg, copy=False) - # now, update our data structures - msg_id = parent['msg_id'] - self.pending[engine].pop(msg_id) - if success: - self.completed[engine].add(msg_id) - self.all_completed.add(msg_id) - else: - self.failed[engine].add(msg_id) - self.all_failed.add(msg_id) - self.all_done.add(msg_id) - self.destinations[msg_id] = engine - - self.update_graph(msg_id, success) - - def handle_unmet_dependency(self, idents, parent): - """handle an unmet dependency""" - engine = idents[0] - msg_id = parent['msg_id'] - - job = self.pending[engine].pop(msg_id) - job.blacklist.add(engine) - - if job.blacklist == job.targets: - self.queue_map[msg_id] = job - self.fail_unreachable(msg_id) - elif not self.maybe_run(job): - # resubmit failed - if msg_id not in self.all_failed: - # put it back in our dependency tree - self.save_unmet(job) - - if self.hwm: - try: - idx = self.targets.index(engine) - except ValueError: - pass # skip load-update for dead engines - else: - if self.loads[idx] == self.hwm-1: - self.update_graph(None) - - def update_graph(self, dep_id=None, success=True): - """dep_id just finished. Update our dependency - graph and submit any jobs that just became runnable. - - Called with dep_id=None to update entire graph for hwm, but without finishing a task. - """ - # print ("\n\n***********") - # pprint (dep_id) - # pprint (self.graph) - # pprint (self.queue_map) - # pprint (self.all_completed) - # pprint (self.all_failed) - # print ("\n\n***********\n\n") - # update any jobs that depended on the dependency - msg_ids = self.graph.pop(dep_id, []) - - # recheck *all* jobs if - # a) we have HWM and an engine just become no longer full - # or b) dep_id was given as None - - if dep_id is None or self.hwm and any( [ load==self.hwm-1 for load in self.loads ]): - jobs = self.queue - using_queue = True - else: - using_queue = False - jobs = deque(sorted( self.queue_map[msg_id] for msg_id in msg_ids )) - - to_restore = [] - while jobs: - job = jobs.popleft() - if job.removed: - continue - msg_id = job.msg_id - - put_it_back = True - - if job.after.unreachable(self.all_completed, self.all_failed)\ - or job.follow.unreachable(self.all_completed, self.all_failed): - self.fail_unreachable(msg_id) - put_it_back = False - - elif job.after.check(self.all_completed, self.all_failed): # time deps met, maybe run - if self.maybe_run(job): - put_it_back = False - self.queue_map.pop(msg_id) - for mid in job.dependents: - if mid in self.graph: - self.graph[mid].remove(msg_id) - - # abort the loop if we just filled up all of our engines. - # avoids an O(N) operation in situation of full queue, - # where graph update is triggered as soon as an engine becomes - # non-full, and all tasks after the first are checked, - # even though they can't run. - if not self.available_engines(): - break - - if using_queue and put_it_back: - # popped a job from the queue but it neither ran nor failed, - # so we need to put it back when we are done - # make sure to_restore preserves the same ordering - to_restore.append(job) - - # put back any tasks we popped but didn't run - if using_queue: - self.queue.extendleft(to_restore) - - #---------------------------------------------------------------------- - # methods to be overridden by subclasses - #---------------------------------------------------------------------- - - def add_job(self, idx): - """Called after self.targets[idx] just got the job with header. - Override with subclasses. The default ordering is simple LRU. - The default loads are the number of outstanding jobs.""" - self.loads[idx] += 1 - for lis in (self.targets, self.loads): - lis.append(lis.pop(idx)) - - - def finish_job(self, idx): - """Called after self.targets[idx] just finished a job. - Override with subclasses.""" - self.loads[idx] -= 1 - - - -def launch_scheduler(in_addr, out_addr, mon_addr, not_addr, reg_addr, config=None, - logname='root', log_url=None, loglevel=logging.DEBUG, - identity=b'task', in_thread=False): - - ZMQStream = zmqstream.ZMQStream - - if config: - # unwrap dict back into Config - config = Config(config) - - if in_thread: - # use instance() to get the same Context/Loop as our parent - ctx = zmq.Context.instance() - loop = ioloop.IOLoop.instance() - else: - # in a process, don't use instance() - # for safety with multiprocessing - ctx = zmq.Context() - loop = ioloop.IOLoop() - ins = ZMQStream(ctx.socket(zmq.ROUTER),loop) - util.set_hwm(ins, 0) - ins.setsockopt(zmq.IDENTITY, identity + b'_in') - ins.bind(in_addr) - - outs = ZMQStream(ctx.socket(zmq.ROUTER),loop) - util.set_hwm(outs, 0) - outs.setsockopt(zmq.IDENTITY, identity + b'_out') - outs.bind(out_addr) - mons = zmqstream.ZMQStream(ctx.socket(zmq.PUB),loop) - util.set_hwm(mons, 0) - mons.connect(mon_addr) - nots = zmqstream.ZMQStream(ctx.socket(zmq.SUB),loop) - nots.setsockopt(zmq.SUBSCRIBE, b'') - nots.connect(not_addr) - - querys = ZMQStream(ctx.socket(zmq.DEALER),loop) - querys.connect(reg_addr) - - # setup logging. - if in_thread: - log = Application.instance().log - else: - if log_url: - log = connect_logger(logname, ctx, log_url, root="scheduler", loglevel=loglevel) - else: - log = local_logger(logname, loglevel) - - scheduler = TaskScheduler(client_stream=ins, engine_stream=outs, - mon_stream=mons, notifier_stream=nots, - query_stream=querys, - loop=loop, log=log, - config=config) - scheduler.start() - if not in_thread: - try: - loop.start() - except KeyboardInterrupt: - scheduler.log.critical("Interrupted, exiting...") - diff --git a/ipython_parallel/controller/sqlitedb.py b/ipython_parallel/controller/sqlitedb.py deleted file mode 100644 index 3febb9c..0000000 --- a/ipython_parallel/controller/sqlitedb.py +++ /dev/null @@ -1,414 +0,0 @@ -"""A TaskRecord backend using sqlite3""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import json -import os -try: - import cPickle as pickle -except ImportError: - import pickle -from datetime import datetime - -try: - import sqlite3 -except ImportError: - sqlite3 = None - -from zmq.eventloop import ioloop - -from IPython.utils.traitlets import Unicode, Instance, List, Dict -from .dictdb import BaseDB -from jupyter_client.jsonutil import date_default, extract_dates, squash_dates -from IPython.utils.py3compat import iteritems - -#----------------------------------------------------------------------------- -# SQLite operators, adapters, and converters -#----------------------------------------------------------------------------- - -try: - buffer -except NameError: - # py3k - buffer = memoryview - -operators = { - '$lt' : "<", - '$gt' : ">", - # null is handled weird with ==,!= - '$eq' : "=", - '$ne' : "!=", - '$lte': "<=", - '$gte': ">=", - '$in' : ('=', ' OR '), - '$nin': ('!=', ' AND '), - # '$all': None, - # '$mod': None, - # '$exists' : None -} -null_operators = { -'=' : "IS NULL", -'!=' : "IS NOT NULL", -} - -def _adapt_dict(d): - return json.dumps(d, default=date_default) - -def _convert_dict(ds): - if ds is None: - return ds - else: - if isinstance(ds, bytes): - # If I understand the sqlite doc correctly, this will always be utf8 - ds = ds.decode('utf8') - return extract_dates(json.loads(ds)) - -def _adapt_bufs(bufs): - # this is *horrible* - # copy buffers into single list and pickle it: - if bufs and isinstance(bufs[0], (bytes, buffer)): - return sqlite3.Binary(pickle.dumps(list(map(bytes, bufs)),-1)) - elif bufs: - return bufs - else: - return None - -def _convert_bufs(bs): - if bs is None: - return [] - else: - return pickle.loads(bytes(bs)) - -#----------------------------------------------------------------------------- -# SQLiteDB class -#----------------------------------------------------------------------------- - -class SQLiteDB(BaseDB): - """SQLite3 TaskRecord backend.""" - - filename = Unicode('tasks.db', config=True, - help="""The filename of the sqlite task database. [default: 'tasks.db']""") - location = Unicode('', config=True, - help="""The directory containing the sqlite task database. The default - is to use the cluster_dir location.""") - table = Unicode("ipython-tasks", config=True, - help="""The SQLite Table to use for storing tasks for this session. If unspecified, - a new table will be created with the Hub's IDENT. Specifying the table will result - in tasks from previous sessions being available via Clients' db_query and - get_result methods.""") - - if sqlite3 is not None: - _db = Instance('sqlite3.Connection', allow_none=True) - else: - _db = None - # the ordered list of column names - _keys = List(['msg_id' , - 'header' , - 'metadata', - 'content', - 'buffers', - 'submitted', - 'client_uuid' , - 'engine_uuid' , - 'started', - 'completed', - 'resubmitted', - 'received', - 'result_header' , - 'result_metadata', - 'result_content' , - 'result_buffers' , - 'queue' , - 'execute_input' , - 'execute_result', - 'error', - 'stdout', - 'stderr', - ]) - # sqlite datatypes for checking that db is current format - _types = Dict({'msg_id' : 'text' , - 'header' : 'dict text', - 'metadata' : 'dict text', - 'content' : 'dict text', - 'buffers' : 'bufs blob', - 'submitted' : 'timestamp', - 'client_uuid' : 'text', - 'engine_uuid' : 'text', - 'started' : 'timestamp', - 'completed' : 'timestamp', - 'resubmitted' : 'text', - 'received' : 'timestamp', - 'result_header' : 'dict text', - 'result_metadata' : 'dict text', - 'result_content' : 'dict text', - 'result_buffers' : 'bufs blob', - 'queue' : 'text', - 'execute_input' : 'text', - 'execute_result' : 'text', - 'error' : 'text', - 'stdout' : 'text', - 'stderr' : 'text', - }) - - def __init__(self, **kwargs): - super(SQLiteDB, self).__init__(**kwargs) - if sqlite3 is None: - raise ImportError("SQLiteDB requires sqlite3") - if not self.table: - # use session, and prefix _, since starting with # is illegal - self.table = '_'+self.session.replace('-','_') - if not self.location: - # get current profile - from IPython.core.application import BaseIPythonApplication - if BaseIPythonApplication.initialized(): - app = BaseIPythonApplication.instance() - if app.profile_dir is not None: - self.location = app.profile_dir.location - else: - self.location = u'.' - else: - self.location = u'.' - self._init_db() - - # register db commit as 2s periodic callback - # to prevent clogging pipes - # assumes we are being run in a zmq ioloop app - loop = ioloop.IOLoop.instance() - pc = ioloop.PeriodicCallback(self._db.commit, 2000, loop) - pc.start() - - def _defaults(self, keys=None): - """create an empty record""" - d = {} - keys = self._keys if keys is None else keys - for key in keys: - d[key] = None - return d - - def _check_table(self): - """Ensure that an incorrect table doesn't exist - - If a bad (old) table does exist, return False - """ - cursor = self._db.execute("PRAGMA table_info('%s')"%self.table) - lines = cursor.fetchall() - if not lines: - # table does not exist - return True - types = {} - keys = [] - for line in lines: - keys.append(line[1]) - types[line[1]] = line[2] - if self._keys != keys: - # key mismatch - self.log.warn('keys mismatch') - return False - for key in self._keys: - if types[key] != self._types[key]: - self.log.warn( - 'type mismatch: %s: %s != %s'%(key,types[key],self._types[key]) - ) - return False - return True - - def _init_db(self): - """Connect to the database and get new session number.""" - # register adapters - sqlite3.register_adapter(dict, _adapt_dict) - sqlite3.register_converter('dict', _convert_dict) - sqlite3.register_adapter(list, _adapt_bufs) - sqlite3.register_converter('bufs', _convert_bufs) - # connect to the db - dbfile = os.path.join(self.location, self.filename) - self._db = sqlite3.connect(dbfile, detect_types=sqlite3.PARSE_DECLTYPES, - # isolation_level = None)#, - cached_statements=64) - # print dir(self._db) - first_table = previous_table = self.table - i=0 - while not self._check_table(): - i+=1 - self.table = first_table+'_%i'%i - self.log.warn( - "Table %s exists and doesn't match db format, trying %s"% - (previous_table, self.table) - ) - previous_table = self.table - - self._db.execute("""CREATE TABLE IF NOT EXISTS '%s' - (msg_id text PRIMARY KEY, - header dict text, - metadata dict text, - content dict text, - buffers bufs blob, - submitted timestamp, - client_uuid text, - engine_uuid text, - started timestamp, - completed timestamp, - resubmitted text, - received timestamp, - result_header dict text, - result_metadata dict text, - result_content dict text, - result_buffers bufs blob, - queue text, - execute_input text, - execute_result text, - error text, - stdout text, - stderr text) - """%self.table) - self._db.commit() - - def _dict_to_list(self, d): - """turn a mongodb-style record dict into a list.""" - - return [ d[key] for key in self._keys ] - - def _list_to_dict(self, line, keys=None): - """Inverse of dict_to_list""" - keys = self._keys if keys is None else keys - d = self._defaults(keys) - for key,value in zip(keys, line): - d[key] = value - - return d - - def _render_expression(self, check): - """Turn a mongodb-style search dict into an SQL query.""" - expressions = [] - args = [] - - skeys = set(check.keys()) - skeys.difference_update(set(self._keys)) - skeys.difference_update(set(['buffers', 'result_buffers'])) - if skeys: - raise KeyError("Illegal testing key(s): %s"%skeys) - - for name,sub_check in iteritems(check): - if isinstance(sub_check, dict): - for test,value in iteritems(sub_check): - try: - op = operators[test] - except KeyError: - raise KeyError("Unsupported operator: %r"%test) - if isinstance(op, tuple): - op, join = op - - if value is None and op in null_operators: - expr = "%s %s" % (name, null_operators[op]) - else: - expr = "%s %s ?"%(name, op) - if isinstance(value, (tuple,list)): - if op in null_operators and any([v is None for v in value]): - # equality tests don't work with NULL - raise ValueError("Cannot use %r test with NULL values on SQLite backend"%test) - expr = '( %s )'%( join.join([expr]*len(value)) ) - args.extend(value) - else: - args.append(value) - expressions.append(expr) - else: - # it's an equality check - if sub_check is None: - expressions.append("%s IS NULL" % name) - else: - expressions.append("%s = ?"%name) - args.append(sub_check) - - expr = " AND ".join(expressions) - return expr, args - - def add_record(self, msg_id, rec): - """Add a new Task Record, by msg_id.""" - d = self._defaults() - d.update(rec) - d['msg_id'] = msg_id - line = self._dict_to_list(d) - tups = '(%s)'%(','.join(['?']*len(line))) - self._db.execute("INSERT INTO '%s' VALUES %s"%(self.table, tups), line) - # self._db.commit() - - def get_record(self, msg_id): - """Get a specific Task Record, by msg_id.""" - cursor = self._db.execute("""SELECT * FROM '%s' WHERE msg_id==?"""%self.table, (msg_id,)) - line = cursor.fetchone() - if line is None: - raise KeyError("No such msg: %r"%msg_id) - return self._list_to_dict(line) - - def update_record(self, msg_id, rec): - """Update the data in an existing record.""" - query = "UPDATE '%s' SET "%self.table - sets = [] - keys = sorted(rec.keys()) - values = [] - for key in keys: - sets.append('%s = ?'%key) - values.append(rec[key]) - query += ', '.join(sets) - query += ' WHERE msg_id == ?' - values.append(msg_id) - self._db.execute(query, values) - # self._db.commit() - - def drop_record(self, msg_id): - """Remove a record from the DB.""" - self._db.execute("""DELETE FROM '%s' WHERE msg_id==?"""%self.table, (msg_id,)) - # self._db.commit() - - def drop_matching_records(self, check): - """Remove a record from the DB.""" - expr,args = self._render_expression(check) - query = "DELETE FROM '%s' WHERE %s"%(self.table, expr) - self._db.execute(query,args) - # self._db.commit() - - def find_records(self, check, keys=None): - """Find records matching a query dict, optionally extracting subset of keys. - - Returns list of matching records. - - Parameters - ---------- - - check: dict - mongodb-style query argument - keys: list of strs [optional] - if specified, the subset of keys to extract. msg_id will *always* be - included. - """ - if keys: - bad_keys = [ key for key in keys if key not in self._keys ] - if bad_keys: - raise KeyError("Bad record key(s): %s"%bad_keys) - - if keys: - # ensure msg_id is present and first: - if 'msg_id' in keys: - keys.remove('msg_id') - keys.insert(0, 'msg_id') - req = ', '.join(keys) - else: - req = '*' - expr,args = self._render_expression(check) - query = """SELECT %s FROM '%s' WHERE %s"""%(req, self.table, expr) - cursor = self._db.execute(query, args) - matches = cursor.fetchall() - records = [] - for line in matches: - rec = self._list_to_dict(line, keys) - records.append(rec) - return records - - def get_history(self): - """get all msg_ids, ordered by time submitted.""" - query = """SELECT msg_id FROM '%s' ORDER by submitted ASC"""%self.table - cursor = self._db.execute(query) - # will be a list of length 1 tuples - return [ tup[0] for tup in cursor.fetchall()] - -__all__ = ['SQLiteDB'] diff --git a/ipython_parallel/engine/__init__.py b/ipython_parallel/engine/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/ipython_parallel/engine/__init__.py +++ /dev/null diff --git a/ipython_parallel/engine/__main__.py b/ipython_parallel/engine/__main__.py deleted file mode 100644 index 23de0ee..0000000 --- a/ipython_parallel/engine/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(): - from ipython_parallel.apps import ipengineapp as app - app.launch_new_instance() - -if __name__ == '__main__': - main() diff --git a/ipython_parallel/engine/engine.py b/ipython_parallel/engine/engine.py deleted file mode 100644 index 0a21a89..0000000 --- a/ipython_parallel/engine/engine.py +++ /dev/null @@ -1,301 +0,0 @@ -"""A simple engine that talks to a controller over 0MQ. -it handles registration, etc. and launches a kernel -connected to the Controller's Schedulers. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import sys -import time -from getpass import getpass - -import zmq -from zmq.eventloop import ioloop, zmqstream - -from IPython.utils.localinterfaces import localhost -from IPython.utils.traitlets import ( - Instance, Dict, Integer, Type, Float, Unicode, CBytes, Bool -) -from IPython.utils.py3compat import cast_bytes - -from ipython_parallel.controller.heartmonitor import Heart -from ipython_parallel.factory import RegistrationFactory -from ipython_parallel.util import disambiguate_url - -from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel -from IPython.kernel.zmq.kernelapp import IPKernelApp - -class EngineFactory(RegistrationFactory): - """IPython engine""" - - # configurables: - out_stream_factory=Type('IPython.kernel.zmq.iostream.OutStream', config=True, - help="""The OutStream for handling stdout/err. - Typically 'IPython.kernel.zmq.iostream.OutStream'""") - display_hook_factory=Type('IPython.kernel.zmq.displayhook.ZMQDisplayHook', config=True, - help="""The class for handling displayhook. - Typically 'IPython.kernel.zmq.displayhook.ZMQDisplayHook'""") - location=Unicode(config=True, - help="""The location (an IP address) of the controller. This is - used for disambiguating URLs, to determine whether - loopback should be used to connect or the public address.""") - timeout=Float(5.0, config=True, - help="""The time (in seconds) to wait for the Controller to respond - to registration requests before giving up.""") - max_heartbeat_misses=Integer(50, config=True, - help="""The maximum number of times a check for the heartbeat ping of a - controller can be missed before shutting down the engine. - - If set to 0, the check is disabled.""") - sshserver=Unicode(config=True, - help="""The SSH server to use for tunneling connections to the Controller.""") - sshkey=Unicode(config=True, - help="""The SSH private key file to use when tunneling connections to the Controller.""") - paramiko=Bool(sys.platform == 'win32', config=True, - help="""Whether to use paramiko instead of openssh for tunnels.""") - - @property - def tunnel_mod(self): - from zmq.ssh import tunnel - return tunnel - - - # not configurable: - connection_info = Dict() - user_ns = Dict() - id = Integer(allow_none=True) - registrar = Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True) - kernel = Instance(Kernel, allow_none=True) - hb_check_period=Integer() - - # States for the heartbeat monitoring - # Initial values for monitored and pinged must satisfy "monitored > pinged == False" so that - # during the first check no "missed" ping is reported. Must be floats for Python 3 compatibility. - _hb_last_pinged = 0.0 - _hb_last_monitored = 0.0 - _hb_missed_beats = 0 - # The zmq Stream which receives the pings from the Heart - _hb_listener = None - - bident = CBytes() - ident = Unicode() - def _ident_changed(self, name, old, new): - self.bident = cast_bytes(new) - using_ssh=Bool(False) - - - def __init__(self, **kwargs): - super(EngineFactory, self).__init__(**kwargs) - self.ident = self.session.session - - def init_connector(self): - """construct connection function, which handles tunnels.""" - self.using_ssh = bool(self.sshkey or self.sshserver) - - if self.sshkey and not self.sshserver: - # We are using ssh directly to the controller, tunneling localhost to localhost - self.sshserver = self.url.split('://')[1].split(':')[0] - - if self.using_ssh: - if self.tunnel_mod.try_passwordless_ssh(self.sshserver, self.sshkey, self.paramiko): - password=False - else: - password = getpass("SSH Password for %s: "%self.sshserver) - else: - password = False - - def connect(s, url): - url = disambiguate_url(url, self.location) - if self.using_ssh: - self.log.debug("Tunneling connection to %s via %s", url, self.sshserver) - return self.tunnel_mod.tunnel_connection(s, url, self.sshserver, - keyfile=self.sshkey, paramiko=self.paramiko, - password=password, - ) - else: - return s.connect(url) - - def maybe_tunnel(url): - """like connect, but don't complete the connection (for use by heartbeat)""" - url = disambiguate_url(url, self.location) - if self.using_ssh: - self.log.debug("Tunneling connection to %s via %s", url, self.sshserver) - url, tunnelobj = self.tunnel_mod.open_tunnel(url, self.sshserver, - keyfile=self.sshkey, paramiko=self.paramiko, - password=password, - ) - return str(url) - return connect, maybe_tunnel - - def register(self): - """send the registration_request""" - - self.log.info("Registering with controller at %s"%self.url) - ctx = self.context - connect,maybe_tunnel = self.init_connector() - reg = ctx.socket(zmq.DEALER) - reg.setsockopt(zmq.IDENTITY, self.bident) - connect(reg, self.url) - self.registrar = zmqstream.ZMQStream(reg, self.loop) - - - content = dict(uuid=self.ident) - self.registrar.on_recv(lambda msg: self.complete_registration(msg, connect, maybe_tunnel)) - # print (self.session.key) - self.session.send(self.registrar, "registration_request", content=content) - - def _report_ping(self, msg): - """Callback for when the heartmonitor.Heart receives a ping""" - #self.log.debug("Received a ping: %s", msg) - self._hb_last_pinged = time.time() - - def complete_registration(self, msg, connect, maybe_tunnel): - # print msg - self.loop.remove_timeout(self._abort_timeout) - ctx = self.context - loop = self.loop - identity = self.bident - idents,msg = self.session.feed_identities(msg) - msg = self.session.deserialize(msg) - content = msg['content'] - info = self.connection_info - - def url(key): - """get zmq url for given channel""" - return str(info["interface"] + ":%i" % info[key]) - - if content['status'] == 'ok': - self.id = int(content['id']) - - # launch heartbeat - # possibly forward hb ports with tunnels - hb_ping = maybe_tunnel(url('hb_ping')) - hb_pong = maybe_tunnel(url('hb_pong')) - - hb_monitor = None - if self.max_heartbeat_misses > 0: - # Add a monitor socket which will record the last time a ping was seen - mon = self.context.socket(zmq.SUB) - mport = mon.bind_to_random_port('tcp://%s' % localhost()) - mon.setsockopt(zmq.SUBSCRIBE, b"") - self._hb_listener = zmqstream.ZMQStream(mon, self.loop) - self._hb_listener.on_recv(self._report_ping) - - - hb_monitor = "tcp://%s:%i" % (localhost(), mport) - - heart = Heart(hb_ping, hb_pong, hb_monitor , heart_id=identity) - heart.start() - - # create Shell Connections (MUX, Task, etc.): - shell_addrs = url('mux'), url('task') - - # Use only one shell stream for mux and tasks - stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop) - stream.setsockopt(zmq.IDENTITY, identity) - shell_streams = [stream] - for addr in shell_addrs: - connect(stream, addr) - - # control stream: - control_addr = url('control') - control_stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop) - control_stream.setsockopt(zmq.IDENTITY, identity) - connect(control_stream, control_addr) - - # create iopub stream: - iopub_addr = url('iopub') - iopub_socket = ctx.socket(zmq.PUB) - iopub_socket.setsockopt(zmq.IDENTITY, identity) - connect(iopub_socket, iopub_addr) - - # disable history: - self.config.HistoryManager.hist_file = ':memory:' - - # Redirect input streams and set a display hook. - if self.out_stream_factory: - sys.stdout = self.out_stream_factory(self.session, iopub_socket, u'stdout') - sys.stdout.topic = cast_bytes('engine.%i.stdout' % self.id) - sys.stderr = self.out_stream_factory(self.session, iopub_socket, u'stderr') - sys.stderr.topic = cast_bytes('engine.%i.stderr' % self.id) - if self.display_hook_factory: - sys.displayhook = self.display_hook_factory(self.session, iopub_socket) - sys.displayhook.topic = cast_bytes('engine.%i.execute_result' % self.id) - - self.kernel = Kernel(parent=self, int_id=self.id, ident=self.ident, session=self.session, - control_stream=control_stream, shell_streams=shell_streams, iopub_socket=iopub_socket, - loop=loop, user_ns=self.user_ns, log=self.log) - - self.kernel.shell.display_pub.topic = cast_bytes('engine.%i.displaypub' % self.id) - - - # periodically check the heartbeat pings of the controller - # Should be started here and not in "start()" so that the right period can be taken - # from the hubs HeartBeatMonitor.period - if self.max_heartbeat_misses > 0: - # Use a slightly bigger check period than the hub signal period to not warn unnecessary - self.hb_check_period = int(content['hb_period'])+10 - self.log.info("Starting to monitor the heartbeat signal from the hub every %i ms." , self.hb_check_period) - self._hb_reporter = ioloop.PeriodicCallback(self._hb_monitor, self.hb_check_period, self.loop) - self._hb_reporter.start() - else: - self.log.info("Monitoring of the heartbeat signal from the hub is not enabled.") - - - # FIXME: This is a hack until IPKernelApp and IPEngineApp can be fully merged - app = IPKernelApp(parent=self, shell=self.kernel.shell, kernel=self.kernel, log=self.log) - app.init_profile_dir() - app.init_code() - - self.kernel.start() - else: - self.log.fatal("Registration Failed: %s"%msg) - raise Exception("Registration Failed: %s"%msg) - - self.log.info("Completed registration with id %i"%self.id) - - - def abort(self): - self.log.fatal("Registration timed out after %.1f seconds"%self.timeout) - if self.url.startswith('127.'): - self.log.fatal(""" - If the controller and engines are not on the same machine, - you will have to instruct the controller to listen on an external IP (in ipcontroller_config.py): - c.HubFactory.ip='*' # for all interfaces, internal and external - c.HubFactory.ip='192.168.1.101' # or any interface that the engines can see - or tunnel connections via ssh. - """) - self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id)) - time.sleep(1) - sys.exit(255) - - def _hb_monitor(self): - """Callback to monitor the heartbeat from the controller""" - self._hb_listener.flush() - if self._hb_last_monitored > self._hb_last_pinged: - self._hb_missed_beats += 1 - self.log.warn("No heartbeat in the last %s ms (%s time(s) in a row).", self.hb_check_period, self._hb_missed_beats) - else: - #self.log.debug("Heartbeat received (after missing %s beats).", self._hb_missed_beats) - self._hb_missed_beats = 0 - - if self._hb_missed_beats >= self.max_heartbeat_misses: - self.log.fatal("Maximum number of heartbeats misses reached (%s times %s ms), shutting down.", - self.max_heartbeat_misses, self.hb_check_period) - self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id)) - self.loop.stop() - - self._hb_last_monitored = time.time() - - - def start(self): - loop = self.loop - def _start(): - self.register() - self._abort_timeout = loop.add_timeout(loop.time() + self.timeout, self.abort) - self.loop.add_callback(_start) - - diff --git a/ipython_parallel/error.py b/ipython_parallel/error.py deleted file mode 100644 index 6707c08..0000000 --- a/ipython_parallel/error.py +++ /dev/null @@ -1,252 +0,0 @@ -# encoding: utf-8 - -"""Classes and functions for kernel related errors and exceptions. - -Inheritance diagram: - -.. inheritance-diagram:: ipython_parallel.error - :parts: 3 - -Authors: - -* Brian Granger -* Min RK -""" -from __future__ import print_function - -import sys -import traceback - -from IPython.utils.py3compat import unicode_type - -__docformat__ = "restructuredtext en" - -# Tell nose to skip this module -__test__ = {} - -#------------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Error classes -#------------------------------------------------------------------------------- -class IPythonError(Exception): - """Base exception that all of our exceptions inherit from. - - This can be raised by code that doesn't have any more specific - information.""" - - pass - -class KernelError(IPythonError): - pass - -class EngineError(KernelError): - pass - -class NoEnginesRegistered(KernelError): - pass - -class TaskAborted(KernelError): - pass - -class TaskTimeout(KernelError): - pass - -class TimeoutError(KernelError): - pass - -class UnmetDependency(KernelError): - pass - -class ImpossibleDependency(UnmetDependency): - pass - -class DependencyTimeout(ImpossibleDependency): - pass - -class InvalidDependency(ImpossibleDependency): - pass - -class RemoteError(KernelError): - """Error raised elsewhere""" - ename=None - evalue=None - traceback=None - engine_info=None - - def __init__(self, ename, evalue, traceback, engine_info=None): - self.ename=ename - self.evalue=evalue - self.traceback=traceback - self.engine_info=engine_info or {} - self.args=(ename, evalue) - - def __repr__(self): - engineid = self.engine_info.get('engine_id', ' ') - return ""%(engineid, self.ename, self.evalue) - - def __str__(self): - return "%s(%s)" % (self.ename, self.evalue) - - def render_traceback(self): - """render traceback to a list of lines""" - return (self.traceback or "No traceback available").splitlines() - - def _render_traceback_(self): - """Special method for custom tracebacks within IPython. - - This will be called by IPython instead of displaying the local traceback. - - It should return a traceback rendered as a list of lines. - """ - return self.render_traceback() - - def print_traceback(self, excid=None): - """print my traceback""" - print('\n'.join(self.render_traceback())) - - - - -class TaskRejectError(KernelError): - """Exception to raise when a task should be rejected by an engine. - - This exception can be used to allow a task running on an engine to test - if the engine (or the user's namespace on the engine) has the needed - task dependencies. If not, the task should raise this exception. For - the task to be retried on another engine, the task should be created - with the `retries` argument > 1. - - The advantage of this approach over our older properties system is that - tasks have full access to the user's namespace on the engines and the - properties don't have to be managed or tested by the controller. - """ - - -class CompositeError(RemoteError): - """Error for representing possibly multiple errors on engines""" - tb_limit = 4 # limit on how many tracebacks to draw - - def __init__(self, message, elist): - Exception.__init__(self, *(message, elist)) - # Don't use pack_exception because it will conflict with the .message - # attribute that is being deprecated in 2.6 and beyond. - self.msg = message - self.elist = elist - self.args = [ e[0] for e in elist ] - - def _get_engine_str(self, ei): - if not ei: - return '[Engine Exception]' - else: - return '[%s:%s]: ' % (ei['engine_id'], ei['method']) - - def _get_traceback(self, ev): - try: - tb = ev._ipython_traceback_text - except AttributeError: - return 'No traceback available' - else: - return tb - - def __str__(self): - s = str(self.msg) - for en, ev, etb, ei in self.elist[:self.tb_limit]: - engine_str = self._get_engine_str(ei) - s = s + '\n' + engine_str + en + ': ' + str(ev) - if len(self.elist) > self.tb_limit: - s = s + '\n.... %i more exceptions ...' % (len(self.elist) - self.tb_limit) - return s - - def __repr__(self): - return "CompositeError(%i)" % len(self.elist) - - def render_traceback(self, excid=None): - """render one or all of my tracebacks to a list of lines""" - lines = [] - if excid is None: - for (en,ev,etb,ei) in self.elist[:self.tb_limit]: - lines.append(self._get_engine_str(ei)) - lines.extend((etb or 'No traceback available').splitlines()) - lines.append('') - if len(self.elist) > self.tb_limit: - lines.append( - '... %i more exceptions ...' % (len(self.elist) - self.tb_limit) - ) - else: - try: - en,ev,etb,ei = self.elist[excid] - except: - raise IndexError("an exception with index %i does not exist"%excid) - else: - lines.append(self._get_engine_str(ei)) - lines.extend((etb or 'No traceback available').splitlines()) - - return lines - - def print_traceback(self, excid=None): - print('\n'.join(self.render_traceback(excid))) - - def raise_exception(self, excid=0): - try: - en,ev,etb,ei = self.elist[excid] - except: - raise IndexError("an exception with index %i does not exist"%excid) - else: - raise RemoteError(en, ev, etb, ei) - - -def collect_exceptions(rdict_or_list, method='unspecified'): - """check a result dict for errors, and raise CompositeError if any exist. - Passthrough otherwise.""" - elist = [] - if isinstance(rdict_or_list, dict): - rlist = rdict_or_list.values() - else: - rlist = rdict_or_list - for r in rlist: - if isinstance(r, RemoteError): - en, ev, etb, ei = r.ename, r.evalue, r.traceback, r.engine_info - # Sometimes we could have CompositeError in our list. Just take - # the errors out of them and put them in our new list. This - # has the effect of flattening lists of CompositeErrors into one - # CompositeError - if en=='CompositeError': - for e in ev.elist: - elist.append(e) - else: - elist.append((en, ev, etb, ei)) - if len(elist)==0: - return rdict_or_list - else: - msg = "one or more exceptions from call to method: %s" % (method) - # This silliness is needed so the debugger has access to the exception - # instance (e in this case) - try: - raise CompositeError(msg, elist) - except CompositeError as e: - raise e - -def wrap_exception(engine_info={}): - etype, evalue, tb = sys.exc_info() - stb = traceback.format_exception(etype, evalue, tb) - exc_content = { - 'status' : 'error', - 'traceback' : stb, - 'ename' : unicode_type(etype.__name__), - 'evalue' : unicode_type(evalue), - 'engine_info' : engine_info - } - return exc_content - -def unwrap_exception(content): - err = RemoteError(content['ename'], content['evalue'], - ''.join(content['traceback']), - content.get('engine_info', {})) - return err - diff --git a/ipython_parallel/factory.py b/ipython_parallel/factory.py deleted file mode 100644 index 87b0481..0000000 --- a/ipython_parallel/factory.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Base config factories. - -Authors: - -* Min RK -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2010-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -from IPython.utils.localinterfaces import localhost -from IPython.utils.traitlets import Integer, Unicode - -from ipython_parallel.util import select_random_ports -from IPython.kernel.zmq.session import SessionFactory - -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - - -class RegistrationFactory(SessionFactory): - """The Base Configurable for objects that involve registration.""" - - url = Unicode('', config=True, - help="""The 0MQ url used for registration. This sets transport, ip, and port - in one variable. For example: url='tcp://127.0.0.1:12345' or - url='epgm://*:90210'""" - ) # url takes precedence over ip,regport,transport - transport = Unicode('tcp', config=True, - help="""The 0MQ transport for communications. This will likely be - the default of 'tcp', but other values include 'ipc', 'epgm', 'inproc'.""") - ip = Unicode(config=True, - help="""The IP address for registration. This is generally either - '127.0.0.1' for loopback only or '*' for all interfaces. - """) - def _ip_default(self): - return localhost() - regport = Integer(config=True, - help="""The port on which the Hub listens for registration.""") - def _regport_default(self): - return select_random_ports(1)[0] - - def __init__(self, **kwargs): - super(RegistrationFactory, self).__init__(**kwargs) - self._propagate_url() - self._rebuild_url() - self.on_trait_change(self._propagate_url, 'url') - self.on_trait_change(self._rebuild_url, 'ip') - self.on_trait_change(self._rebuild_url, 'transport') - self.on_trait_change(self._rebuild_url, 'regport') - - def _rebuild_url(self): - self.url = "%s://%s:%i"%(self.transport, self.ip, self.regport) - - def _propagate_url(self): - """Ensure self.url contains full transport://interface:port""" - if self.url: - iface = self.url.split('://',1) - if len(iface) == 2: - self.transport,iface = iface - iface = iface.split(':') - self.ip = iface[0] - if iface[1]: - self.regport = int(iface[1]) diff --git a/ipython_parallel/logger.py b/ipython_parallel/logger.py deleted file mode 100644 index f17adbd..0000000 --- a/ipython_parallel/logger.py +++ /dev/null @@ -1,3 +0,0 @@ -if __name__ == '__main__': - from ipython_parallel.apps import iploggerapp as app - app.launch_new_instance() diff --git a/ipython_parallel/tests/__init__.py b/ipython_parallel/tests/__init__.py deleted file mode 100644 index af64368..0000000 --- a/ipython_parallel/tests/__init__.py +++ /dev/null @@ -1,145 +0,0 @@ -"""toplevel setup/teardown for parallel tests.""" -from __future__ import print_function - -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import os -import tempfile -import time -from subprocess import Popen, PIPE, STDOUT - -import nose - -from IPython.utils.path import get_ipython_dir -from ipython_parallel import Client, error -from ipython_parallel.apps.launcher import (LocalProcessLauncher, - ipengine_cmd_argv, - ipcontroller_cmd_argv, - SIGKILL, - ProcessStateError, -) - -# globals -launchers = [] -blackhole = open(os.devnull, 'w') - -# Launcher class -class TestProcessLauncher(LocalProcessLauncher): - """subclass LocalProcessLauncher, to prevent extra sockets and threads being created on Windows""" - def start(self): - if self.state == 'before': - # Store stdout & stderr to show with failing tests. - # This is defined in IPython.testing.iptest - self.process = Popen(self.args, - stdout=nose.iptest_stdstreams_fileno(), stderr=STDOUT, - env=os.environ, - cwd=self.work_dir - ) - self.notify_start(self.process.pid) - self.poll = self.process.poll - else: - s = 'The process was already started and has state: %r' % self.state - raise ProcessStateError(s) - -# nose setup/teardown - -def setup(): - - # show tracebacks for RemoteErrors - class RemoteErrorWithTB(error.RemoteError): - def __str__(self): - s = super(RemoteErrorWithTB, self).__str__() - return '\n'.join([s, self.traceback or '']) - - error.RemoteError = RemoteErrorWithTB - - cluster_dir = os.path.join(get_ipython_dir(), 'profile_iptest') - engine_json = os.path.join(cluster_dir, 'security', 'ipcontroller-engine.json') - client_json = os.path.join(cluster_dir, 'security', 'ipcontroller-client.json') - for json in (engine_json, client_json): - if os.path.exists(json): - os.remove(json) - - cp = TestProcessLauncher() - cp.cmd_and_args = ipcontroller_cmd_argv + \ - ['--profile=iptest', '--log-level=20', '--ping=250', '--dictdb'] - cp.start() - launchers.append(cp) - tic = time.time() - while not os.path.exists(engine_json) or not os.path.exists(client_json): - if cp.poll() is not None: - raise RuntimeError("The test controller exited with status %s" % cp.poll()) - elif time.time()-tic > 15: - raise RuntimeError("Timeout waiting for the test controller to start.") - time.sleep(0.1) - add_engines(1) - -def add_engines(n=1, profile='iptest', total=False): - """add a number of engines to a given profile. - - If total is True, then already running engines are counted, and only - the additional engines necessary (if any) are started. - """ - rc = Client(profile=profile) - base = len(rc) - - if total: - n = max(n - base, 0) - - eps = [] - for i in range(n): - ep = TestProcessLauncher() - ep.cmd_and_args = ipengine_cmd_argv + [ - '--profile=%s' % profile, - '--log-level=50', - '--InteractiveShell.colors=nocolor' - ] - ep.start() - launchers.append(ep) - eps.append(ep) - tic = time.time() - while len(rc) < base+n: - if any([ ep.poll() is not None for ep in eps ]): - raise RuntimeError("A test engine failed to start.") - elif time.time()-tic > 15: - raise RuntimeError("Timeout waiting for engines to connect.") - time.sleep(.1) - rc.spin() - rc.close() - return eps - -def teardown(): - try: - time.sleep(1) - except KeyboardInterrupt: - return - while launchers: - p = launchers.pop() - if p.poll() is None: - try: - p.stop() - except Exception as e: - print(e) - pass - if p.poll() is None: - try: - time.sleep(.25) - except KeyboardInterrupt: - return - if p.poll() is None: - try: - print('cleaning up test process...') - p.signal(SIGKILL) - except: - print("couldn't shutdown process: ", p) - blackhole.close() - diff --git a/ipython_parallel/tests/clienttest.py b/ipython_parallel/tests/clienttest.py deleted file mode 100644 index 579a4de..0000000 --- a/ipython_parallel/tests/clienttest.py +++ /dev/null @@ -1,192 +0,0 @@ -"""base class for parallel client tests - -Authors: - -* Min RK -""" - -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- -from __future__ import print_function - -import sys -import tempfile -import time - -from nose import SkipTest - -import zmq -from zmq.tests import BaseZMQTestCase - -from decorator import decorator - -from ipython_parallel import error -from ipython_parallel import Client - -from ipython_parallel.tests import launchers, add_engines - -# simple tasks for use in apply tests - -def segfault(): - """this will segfault""" - import ctypes - ctypes.memset(-1,0,1) - -def crash(): - """from stdlib crashers in the test suite""" - import types - if sys.platform.startswith('win'): - import ctypes - ctypes.windll.kernel32.SetErrorMode(0x0002); - args = [ 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (), '', '', 1, b''] - if sys.version_info[0] >= 3: - # Python3 adds 'kwonlyargcount' as the second argument to Code - args.insert(1, 0) - - co = types.CodeType(*args) - exec(co) - -def wait(n): - """sleep for a time""" - import time - time.sleep(n) - return n - -def raiser(eclass): - """raise an exception""" - raise eclass() - -def generate_output(): - """function for testing output - - publishes two outputs of each type, and returns - a rich displayable object. - """ - - import sys - from IPython.core.display import display, HTML, Math - - print("stdout") - print("stderr", file=sys.stderr) - - display(HTML("HTML")) - - print("stdout2") - print("stderr2", file=sys.stderr) - - display(Math(r"\alpha=\beta")) - - return Math("42") - -# test decorator for skipping tests when libraries are unavailable -def skip_without(*names): - """skip a test if some names are not importable""" - @decorator - def skip_without_names(f, *args, **kwargs): - """decorator to skip tests in the absence of numpy.""" - for name in names: - try: - __import__(name) - except ImportError: - raise SkipTest - return f(*args, **kwargs) - return skip_without_names - -#------------------------------------------------------------------------------- -# Classes -#------------------------------------------------------------------------------- - - -class ClusterTestCase(BaseZMQTestCase): - timeout = 10 - - def add_engines(self, n=1, block=True): - """add multiple engines to our cluster""" - self.engines.extend(add_engines(n)) - if block: - self.wait_on_engines() - - def minimum_engines(self, n=1, block=True): - """add engines until there are at least n connected""" - self.engines.extend(add_engines(n, total=True)) - if block: - self.wait_on_engines() - - - def wait_on_engines(self, timeout=5): - """wait for our engines to connect.""" - n = len(self.engines)+self.base_engine_count - tic = time.time() - while time.time()-tic < timeout and len(self.client.ids) < n: - time.sleep(0.1) - - assert not len(self.client.ids) < n, "waiting for engines timed out" - - def client_wait(self, client, jobs=None, timeout=-1): - """my wait wrapper, sets a default finite timeout to avoid hangs""" - if timeout < 0: - timeout = self.timeout - return Client.wait(client, jobs, timeout) - - def connect_client(self): - """connect a client with my Context, and track its sockets for cleanup""" - c = Client(profile='iptest', context=self.context) - c.wait = lambda *a, **kw: self.client_wait(c, *a, **kw) - - for name in filter(lambda n:n.endswith('socket'), dir(c)): - s = getattr(c, name) - s.setsockopt(zmq.LINGER, 0) - self.sockets.append(s) - return c - - def assertRaisesRemote(self, etype, f, *args, **kwargs): - try: - try: - f(*args, **kwargs) - except error.CompositeError as e: - e.raise_exception() - except error.RemoteError as e: - self.assertEqual(etype.__name__, e.ename, "Should have raised %r, but raised %r"%(etype.__name__, e.ename)) - else: - self.fail("should have raised a RemoteError") - - def _wait_for(self, f, timeout=10): - """wait for a condition""" - tic = time.time() - while time.time() <= tic + timeout: - if f(): - return - time.sleep(0.1) - self.client.spin() - if not f(): - print("Warning: Awaited condition never arrived") - - def setUp(self): - BaseZMQTestCase.setUp(self) - self.client = self.connect_client() - # start every test with clean engine namespaces: - self.client.clear(block=True) - self.base_engine_count=len(self.client.ids) - self.engines=[] - - def tearDown(self): - # self.client.clear(block=True) - # close fds: - for e in filter(lambda e: e.poll() is not None, launchers): - launchers.remove(e) - - # allow flushing of incoming messages to prevent crash on socket close - self.client.wait(timeout=2) - # time.sleep(2) - self.client.spin() - self.client.close() - BaseZMQTestCase.tearDown(self) - # this will be redundant when pyzmq merges PR #88 - # self.context.term() - # print tempfile.TemporaryFile().fileno(), - # sys.stdout.flush() - diff --git a/ipython_parallel/tests/test_asyncresult.py b/ipython_parallel/tests/test_asyncresult.py deleted file mode 100644 index 8848571..0000000 --- a/ipython_parallel/tests/test_asyncresult.py +++ /dev/null @@ -1,342 +0,0 @@ -"""Tests for asyncresult.py""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import time - -import nose.tools as nt - -from IPython.utils.io import capture_output - -from ipython_parallel.error import TimeoutError -from ipython_parallel import error, Client -from ipython_parallel.tests import add_engines -from .clienttest import ClusterTestCase -from IPython.utils.py3compat import iteritems - -def setup(): - add_engines(2, total=True) - -def wait(n): - import time - time.sleep(n) - return n - -def echo(x): - return x - -class AsyncResultTest(ClusterTestCase): - - def test_single_result_view(self): - """various one-target views get the right value for single_result""" - eid = self.client.ids[-1] - ar = self.client[eid].apply_async(lambda : 42) - self.assertEqual(ar.get(), 42) - ar = self.client[[eid]].apply_async(lambda : 42) - self.assertEqual(ar.get(), [42]) - ar = self.client[-1:].apply_async(lambda : 42) - self.assertEqual(ar.get(), [42]) - - def test_get_after_done(self): - ar = self.client[-1].apply_async(lambda : 42) - ar.wait() - self.assertTrue(ar.ready()) - self.assertEqual(ar.get(), 42) - self.assertEqual(ar.get(), 42) - - def test_get_before_done(self): - ar = self.client[-1].apply_async(wait, 0.1) - self.assertRaises(TimeoutError, ar.get, 0) - ar.wait(0) - self.assertFalse(ar.ready()) - self.assertEqual(ar.get(), 0.1) - - def test_get_after_error(self): - ar = self.client[-1].apply_async(lambda : 1/0) - ar.wait(10) - self.assertRaisesRemote(ZeroDivisionError, ar.get) - self.assertRaisesRemote(ZeroDivisionError, ar.get) - self.assertRaisesRemote(ZeroDivisionError, ar.get_dict) - - def test_get_dict(self): - n = len(self.client) - ar = self.client[:].apply_async(lambda : 5) - self.assertEqual(ar.get(), [5]*n) - d = ar.get_dict() - self.assertEqual(sorted(d.keys()), sorted(self.client.ids)) - for eid,r in iteritems(d): - self.assertEqual(r, 5) - - def test_get_dict_single(self): - view = self.client[-1] - for v in (list(range(5)), 5, ('abc', 'def'), 'string'): - ar = view.apply_async(echo, v) - self.assertEqual(ar.get(), v) - d = ar.get_dict() - self.assertEqual(d, {view.targets : v}) - - def test_get_dict_bad(self): - ar = self.client[:].apply_async(lambda : 5) - ar2 = self.client[:].apply_async(lambda : 5) - ar = self.client.get_result(ar.msg_ids + ar2.msg_ids) - self.assertRaises(ValueError, ar.get_dict) - - def test_list_amr(self): - ar = self.client.load_balanced_view().map_async(wait, [0.1]*5) - rlist = list(ar) - - def test_getattr(self): - ar = self.client[:].apply_async(wait, 0.5) - self.assertEqual(ar.engine_id, [None] * len(ar)) - self.assertRaises(AttributeError, lambda : ar._foo) - self.assertRaises(AttributeError, lambda : ar.__length_hint__()) - self.assertRaises(AttributeError, lambda : ar.foo) - self.assertFalse(hasattr(ar, '__length_hint__')) - self.assertFalse(hasattr(ar, 'foo')) - self.assertTrue(hasattr(ar, 'engine_id')) - ar.get(5) - self.assertRaises(AttributeError, lambda : ar._foo) - self.assertRaises(AttributeError, lambda : ar.__length_hint__()) - self.assertRaises(AttributeError, lambda : ar.foo) - self.assertTrue(isinstance(ar.engine_id, list)) - self.assertEqual(ar.engine_id, ar['engine_id']) - self.assertFalse(hasattr(ar, '__length_hint__')) - self.assertFalse(hasattr(ar, 'foo')) - self.assertTrue(hasattr(ar, 'engine_id')) - - def test_getitem(self): - ar = self.client[:].apply_async(wait, 0.5) - self.assertEqual(ar['engine_id'], [None] * len(ar)) - self.assertRaises(KeyError, lambda : ar['foo']) - ar.get(5) - self.assertRaises(KeyError, lambda : ar['foo']) - self.assertTrue(isinstance(ar['engine_id'], list)) - self.assertEqual(ar.engine_id, ar['engine_id']) - - def test_single_result(self): - ar = self.client[-1].apply_async(wait, 0.5) - self.assertRaises(KeyError, lambda : ar['foo']) - self.assertEqual(ar['engine_id'], None) - self.assertTrue(ar.get(5) == 0.5) - self.assertTrue(isinstance(ar['engine_id'], int)) - self.assertTrue(isinstance(ar.engine_id, int)) - self.assertEqual(ar.engine_id, ar['engine_id']) - - def test_abort(self): - e = self.client[-1] - ar = e.execute('import time; time.sleep(1)', block=False) - ar2 = e.apply_async(lambda : 2) - ar2.abort() - self.assertRaises(error.TaskAborted, ar2.get) - ar.get() - - def test_len(self): - v = self.client.load_balanced_view() - ar = v.map_async(lambda x: x, list(range(10))) - self.assertEqual(len(ar), 10) - ar = v.apply_async(lambda x: x, list(range(10))) - self.assertEqual(len(ar), 1) - ar = self.client[:].apply_async(lambda x: x, list(range(10))) - self.assertEqual(len(ar), len(self.client.ids)) - - def test_wall_time_single(self): - v = self.client.load_balanced_view() - ar = v.apply_async(time.sleep, 0.25) - self.assertRaises(TimeoutError, getattr, ar, 'wall_time') - ar.get(2) - self.assertTrue(ar.wall_time < 1.) - self.assertTrue(ar.wall_time > 0.2) - - def test_wall_time_multi(self): - self.minimum_engines(4) - v = self.client[:] - ar = v.apply_async(time.sleep, 0.25) - self.assertRaises(TimeoutError, getattr, ar, 'wall_time') - ar.get(2) - self.assertTrue(ar.wall_time < 1.) - self.assertTrue(ar.wall_time > 0.2) - - def test_serial_time_single(self): - v = self.client.load_balanced_view() - ar = v.apply_async(time.sleep, 0.25) - self.assertRaises(TimeoutError, getattr, ar, 'serial_time') - ar.get(2) - self.assertTrue(ar.serial_time < 1.) - self.assertTrue(ar.serial_time > 0.2) - - def test_serial_time_multi(self): - self.minimum_engines(4) - v = self.client[:] - ar = v.apply_async(time.sleep, 0.25) - self.assertRaises(TimeoutError, getattr, ar, 'serial_time') - ar.get(2) - self.assertTrue(ar.serial_time < 2.) - self.assertTrue(ar.serial_time > 0.8) - - def test_elapsed_single(self): - v = self.client.load_balanced_view() - ar = v.apply_async(time.sleep, 0.25) - while not ar.ready(): - time.sleep(0.01) - self.assertTrue(ar.elapsed < 1) - self.assertTrue(ar.elapsed < 1) - ar.get(2) - - def test_elapsed_multi(self): - v = self.client[:] - ar = v.apply_async(time.sleep, 0.25) - while not ar.ready(): - time.sleep(0.01) - self.assertLess(ar.elapsed, 1) - self.assertLess(ar.elapsed, 1) - ar.get(2) - - def test_hubresult_timestamps(self): - self.minimum_engines(4) - v = self.client[:] - ar = v.apply_async(time.sleep, 0.25) - ar.get(2) - rc2 = Client(profile='iptest') - # must have try/finally to close second Client, otherwise - # will have dangling sockets causing problems - try: - time.sleep(0.25) - hr = rc2.get_result(ar.msg_ids) - self.assertTrue(hr.elapsed > 0., "got bad elapsed: %s" % hr.elapsed) - hr.get(1) - self.assertTrue(hr.wall_time < ar.wall_time + 0.2, "got bad wall_time: %s > %s" % (hr.wall_time, ar.wall_time)) - self.assertEqual(hr.serial_time, ar.serial_time) - finally: - rc2.close() - - def test_display_empty_streams_single(self): - """empty stdout/err are not displayed (single result)""" - self.minimum_engines(1) - - v = self.client[-1] - ar = v.execute("print (5555)") - ar.get(5) - with capture_output() as io: - ar.display_outputs() - self.assertEqual(io.stderr, '') - self.assertEqual('5555\n', io.stdout) - - ar = v.execute("a=5") - ar.get(5) - with capture_output() as io: - ar.display_outputs() - self.assertEqual(io.stderr, '') - self.assertEqual(io.stdout, '') - - def test_display_empty_streams_type(self): - """empty stdout/err are not displayed (groupby type)""" - self.minimum_engines(1) - - v = self.client[:] - ar = v.execute("print (5555)") - ar.get(5) - with capture_output() as io: - ar.display_outputs() - self.assertEqual(io.stderr, '') - self.assertEqual(io.stdout.count('5555'), len(v), io.stdout) - self.assertFalse('\n\n' in io.stdout, io.stdout) - self.assertEqual(io.stdout.count('[stdout:'), len(v), io.stdout) - - ar = v.execute("a=5") - ar.get(5) - with capture_output() as io: - ar.display_outputs() - self.assertEqual(io.stderr, '') - self.assertEqual(io.stdout, '') - - def test_display_empty_streams_engine(self): - """empty stdout/err are not displayed (groupby engine)""" - self.minimum_engines(1) - - v = self.client[:] - ar = v.execute("print (5555)") - ar.get(5) - with capture_output() as io: - ar.display_outputs('engine') - self.assertEqual(io.stderr, '') - self.assertEqual(io.stdout.count('5555'), len(v), io.stdout) - self.assertFalse('\n\n' in io.stdout, io.stdout) - self.assertEqual(io.stdout.count('[stdout:'), len(v), io.stdout) - - ar = v.execute("a=5") - ar.get(5) - with capture_output() as io: - ar.display_outputs('engine') - self.assertEqual(io.stderr, '') - self.assertEqual(io.stdout, '') - - def test_await_data(self): - """asking for ar.data flushes outputs""" - self.minimum_engines(1) - - v = self.client[-1] - ar = v.execute('\n'.join([ - "import time", - "from IPython.kernel.zmq.datapub import publish_data", - "for i in range(5):", - " publish_data(dict(i=i))", - " time.sleep(0.1)", - ]), block=False) - found = set() - tic = time.time() - # timeout after 10s - while time.time() <= tic + 10: - if ar.data: - i = ar.data['i'] - found.add(i) - if i == 4: - break - time.sleep(0.05) - - ar.get(5) - nt.assert_in(4, found) - self.assertTrue(len(found) > 1, "should have seen data multiple times, but got: %s" % found) - - def test_not_single_result(self): - save_build = self.client._build_targets - def single_engine(*a, **kw): - idents, targets = save_build(*a, **kw) - return idents[:1], targets[:1] - ids = single_engine('all')[1] - self.client._build_targets = single_engine - for targets in ('all', None, ids): - dv = self.client.direct_view(targets=targets) - ar = dv.apply_async(lambda : 5) - self.assertEqual(ar.get(10), [5]) - self.client._build_targets = save_build - - def test_owner_pop(self): - self.minimum_engines(1) - - view = self.client[-1] - ar = view.apply_async(lambda : 1) - ar.get() - msg_id = ar.msg_ids[0] - self.assertNotIn(msg_id, self.client.results) - self.assertNotIn(msg_id, self.client.metadata) - - def test_non_owner(self): - self.minimum_engines(1) - - view = self.client[-1] - ar = view.apply_async(lambda : 1) - ar.owner = False - ar.get() - msg_id = ar.msg_ids[0] - self.assertIn(msg_id, self.client.results) - self.assertIn(msg_id, self.client.metadata) - ar2 = self.client.get_result(msg_id, owner=True) - self.assertIs(type(ar2), type(ar)) - self.assertTrue(ar2.owner) - self.assertEqual(ar.get(), ar2.get()) - ar2.get() - self.assertNotIn(msg_id, self.client.results) - self.assertNotIn(msg_id, self.client.metadata) - - diff --git a/ipython_parallel/tests/test_client.py b/ipython_parallel/tests/test_client.py deleted file mode 100644 index 3dfcc61..0000000 --- a/ipython_parallel/tests/test_client.py +++ /dev/null @@ -1,550 +0,0 @@ -"""Tests for parallel client.py""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import division - -import time -from datetime import datetime - -import zmq - -from IPython import parallel -from ipython_parallel.client import client as clientmod -from ipython_parallel import error -from ipython_parallel import AsyncResult, AsyncHubResult -from ipython_parallel import LoadBalancedView, DirectView - -from .clienttest import ClusterTestCase, segfault, wait, add_engines - -def setup(): - add_engines(4, total=True) - -class TestClient(ClusterTestCase): - - def test_ids(self): - n = len(self.client.ids) - self.add_engines(2) - self.assertEqual(len(self.client.ids), n+2) - - def test_iter(self): - self.minimum_engines(4) - engine_ids = [ view.targets for view in self.client ] - self.assertEqual(engine_ids, self.client.ids) - - def test_view_indexing(self): - """test index access for views""" - self.minimum_engines(4) - targets = self.client._build_targets('all')[-1] - v = self.client[:] - self.assertEqual(v.targets, targets) - t = self.client.ids[2] - v = self.client[t] - self.assertTrue(isinstance(v, DirectView)) - self.assertEqual(v.targets, t) - t = self.client.ids[2:4] - v = self.client[t] - self.assertTrue(isinstance(v, DirectView)) - self.assertEqual(v.targets, t) - v = self.client[::2] - self.assertTrue(isinstance(v, DirectView)) - self.assertEqual(v.targets, targets[::2]) - v = self.client[1::3] - self.assertTrue(isinstance(v, DirectView)) - self.assertEqual(v.targets, targets[1::3]) - v = self.client[:-3] - self.assertTrue(isinstance(v, DirectView)) - self.assertEqual(v.targets, targets[:-3]) - v = self.client[-1] - self.assertTrue(isinstance(v, DirectView)) - self.assertEqual(v.targets, targets[-1]) - self.assertRaises(TypeError, lambda : self.client[None]) - - def test_lbview_targets(self): - """test load_balanced_view targets""" - v = self.client.load_balanced_view() - self.assertEqual(v.targets, None) - v = self.client.load_balanced_view(-1) - self.assertEqual(v.targets, [self.client.ids[-1]]) - v = self.client.load_balanced_view('all') - self.assertEqual(v.targets, None) - - def test_dview_targets(self): - """test direct_view targets""" - v = self.client.direct_view() - self.assertEqual(v.targets, 'all') - v = self.client.direct_view('all') - self.assertEqual(v.targets, 'all') - v = self.client.direct_view(-1) - self.assertEqual(v.targets, self.client.ids[-1]) - - def test_lazy_all_targets(self): - """test lazy evaluation of rc.direct_view('all')""" - v = self.client.direct_view() - self.assertEqual(v.targets, 'all') - - def double(x): - return x*2 - seq = list(range(100)) - ref = [ double(x) for x in seq ] - - # add some engines, which should be used - self.add_engines(1) - n1 = len(self.client.ids) - - # simple apply - r = v.apply_sync(lambda : 1) - self.assertEqual(r, [1] * n1) - - # map goes through remotefunction - r = v.map_sync(double, seq) - self.assertEqual(r, ref) - - # add a couple more engines, and try again - self.add_engines(2) - n2 = len(self.client.ids) - self.assertNotEqual(n2, n1) - - # apply - r = v.apply_sync(lambda : 1) - self.assertEqual(r, [1] * n2) - - # map - r = v.map_sync(double, seq) - self.assertEqual(r, ref) - - def test_targets(self): - """test various valid targets arguments""" - build = self.client._build_targets - ids = self.client.ids - idents,targets = build(None) - self.assertEqual(ids, targets) - - def test_clear(self): - """test clear behavior""" - self.minimum_engines(2) - v = self.client[:] - v.block=True - v.push(dict(a=5)) - v.pull('a') - id0 = self.client.ids[-1] - self.client.clear(targets=id0, block=True) - a = self.client[:-1].get('a') - self.assertRaisesRemote(NameError, self.client[id0].get, 'a') - self.client.clear(block=True) - for i in self.client.ids: - self.assertRaisesRemote(NameError, self.client[i].get, 'a') - - def test_get_result(self): - """test getting results from the Hub.""" - c = clientmod.Client(profile='iptest') - t = c.ids[-1] - ar = c[t].apply_async(wait, 1) - # give the monitor time to notice the message - time.sleep(.25) - ahr = self.client.get_result(ar.msg_ids[0], owner=False) - self.assertIsInstance(ahr, AsyncHubResult) - self.assertEqual(ahr.get(), ar.get()) - ar2 = self.client.get_result(ar.msg_ids[0]) - self.assertNotIsInstance(ar2, AsyncHubResult) - self.assertEqual(ahr.get(), ar2.get()) - c.close() - - def test_get_execute_result(self): - """test getting execute results from the Hub.""" - c = clientmod.Client(profile='iptest') - t = c.ids[-1] - cell = '\n'.join([ - 'import time', - 'time.sleep(0.25)', - '5' - ]) - ar = c[t].execute("import time; time.sleep(1)", silent=False) - # give the monitor time to notice the message - time.sleep(.25) - ahr = self.client.get_result(ar.msg_ids[0], owner=False) - self.assertIsInstance(ahr, AsyncHubResult) - self.assertEqual(ahr.get().execute_result, ar.get().execute_result) - ar2 = self.client.get_result(ar.msg_ids[0]) - self.assertNotIsInstance(ar2, AsyncHubResult) - self.assertEqual(ahr.get(), ar2.get()) - c.close() - - def test_ids_list(self): - """test client.ids""" - ids = self.client.ids - self.assertEqual(ids, self.client._ids) - self.assertFalse(ids is self.client._ids) - ids.remove(ids[-1]) - self.assertNotEqual(ids, self.client._ids) - - def test_queue_status(self): - ids = self.client.ids - id0 = ids[0] - qs = self.client.queue_status(targets=id0) - self.assertTrue(isinstance(qs, dict)) - self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks']) - allqs = self.client.queue_status() - self.assertTrue(isinstance(allqs, dict)) - intkeys = list(allqs.keys()) - intkeys.remove('unassigned') - print("intkeys", intkeys) - intkeys = sorted(intkeys) - ids = self.client.ids - print("client.ids", ids) - ids = sorted(self.client.ids) - self.assertEqual(intkeys, ids) - unassigned = allqs.pop('unassigned') - for eid,qs in allqs.items(): - self.assertTrue(isinstance(qs, dict)) - self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks']) - - def test_shutdown(self): - ids = self.client.ids - id0 = ids[0] - self.client.shutdown(id0, block=True) - while id0 in self.client.ids: - time.sleep(0.1) - self.client.spin() - - self.assertRaises(IndexError, lambda : self.client[id0]) - - def test_result_status(self): - pass - # to be written - - def test_db_query_dt(self): - """test db query by date""" - hist = self.client.hub_history() - middle = self.client.db_query({'msg_id' : hist[len(hist)//2]})[0] - tic = middle['submitted'] - before = self.client.db_query({'submitted' : {'$lt' : tic}}) - after = self.client.db_query({'submitted' : {'$gte' : tic}}) - self.assertEqual(len(before)+len(after),len(hist)) - for b in before: - self.assertTrue(b['submitted'] < tic) - for a in after: - self.assertTrue(a['submitted'] >= tic) - same = self.client.db_query({'submitted' : tic}) - for s in same: - self.assertTrue(s['submitted'] == tic) - - def test_db_query_keys(self): - """test extracting subset of record keys""" - found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed']) - for rec in found: - self.assertEqual(set(rec.keys()), set(['msg_id', 'submitted', 'completed'])) - - def test_db_query_default_keys(self): - """default db_query excludes buffers""" - found = self.client.db_query({'msg_id': {'$ne' : ''}}) - for rec in found: - keys = set(rec.keys()) - self.assertFalse('buffers' in keys, "'buffers' should not be in: %s" % keys) - self.assertFalse('result_buffers' in keys, "'result_buffers' should not be in: %s" % keys) - - def test_db_query_msg_id(self): - """ensure msg_id is always in db queries""" - found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed']) - for rec in found: - self.assertTrue('msg_id' in rec.keys()) - found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted']) - for rec in found: - self.assertTrue('msg_id' in rec.keys()) - found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['msg_id']) - for rec in found: - self.assertTrue('msg_id' in rec.keys()) - - def test_db_query_get_result(self): - """pop in db_query shouldn't pop from result itself""" - self.client[:].apply_sync(lambda : 1) - found = self.client.db_query({'msg_id': {'$ne' : ''}}) - rc2 = clientmod.Client(profile='iptest') - # If this bug is not fixed, this call will hang: - ar = rc2.get_result(self.client.history[-1]) - ar.wait(2) - self.assertTrue(ar.ready()) - ar.get() - rc2.close() - - def test_db_query_in(self): - """test db query with '$in','$nin' operators""" - hist = self.client.hub_history() - even = hist[::2] - odd = hist[1::2] - recs = self.client.db_query({ 'msg_id' : {'$in' : even}}) - found = [ r['msg_id'] for r in recs ] - self.assertEqual(set(even), set(found)) - recs = self.client.db_query({ 'msg_id' : {'$nin' : even}}) - found = [ r['msg_id'] for r in recs ] - self.assertEqual(set(odd), set(found)) - - def test_hub_history(self): - hist = self.client.hub_history() - recs = self.client.db_query({ 'msg_id' : {"$ne":''}}) - recdict = {} - for rec in recs: - recdict[rec['msg_id']] = rec - - latest = datetime(1984,1,1) - for msg_id in hist: - rec = recdict[msg_id] - newt = rec['submitted'] - self.assertTrue(newt >= latest) - latest = newt - ar = self.client[-1].apply_async(lambda : 1) - ar.get() - time.sleep(0.25) - self.assertEqual(self.client.hub_history()[-1:],ar.msg_ids) - - def _wait_for_idle(self): - """wait for the cluster to become idle, according to the everyone.""" - rc = self.client - - # step 0. wait for local results - # this should be sufficient 99% of the time. - rc.wait(timeout=5) - - # step 1. wait for all requests to be noticed - # timeout 5s, polling every 100ms - msg_ids = set(rc.history) - hub_hist = rc.hub_history() - for i in range(50): - if msg_ids.difference(hub_hist): - time.sleep(0.1) - hub_hist = rc.hub_history() - else: - break - - self.assertEqual(len(msg_ids.difference(hub_hist)), 0) - - # step 2. wait for all requests to be done - # timeout 5s, polling every 100ms - qs = rc.queue_status() - for i in range(50): - if qs['unassigned'] or any(qs[eid]['tasks'] + qs[eid]['queue'] for eid in qs if eid != 'unassigned'): - time.sleep(0.1) - qs = rc.queue_status() - else: - break - - # ensure Hub up to date: - self.assertEqual(qs['unassigned'], 0) - for eid in [ eid for eid in qs if eid != 'unassigned' ]: - self.assertEqual(qs[eid]['tasks'], 0) - self.assertEqual(qs[eid]['queue'], 0) - - - def test_resubmit(self): - def f(): - import random - return random.random() - v = self.client.load_balanced_view() - ar = v.apply_async(f) - r1 = ar.get(1) - # give the Hub a chance to notice: - self._wait_for_idle() - ahr = self.client.resubmit(ar.msg_ids) - r2 = ahr.get(1) - self.assertFalse(r1 == r2) - - def test_resubmit_chain(self): - """resubmit resubmitted tasks""" - v = self.client.load_balanced_view() - ar = v.apply_async(lambda x: x, 'x'*1024) - ar.get() - self._wait_for_idle() - ars = [ar] - - for i in range(10): - ar = ars[-1] - ar2 = self.client.resubmit(ar.msg_ids) - - [ ar.get() for ar in ars ] - - def test_resubmit_header(self): - """resubmit shouldn't clobber the whole header""" - def f(): - import random - return random.random() - v = self.client.load_balanced_view() - v.retries = 1 - ar = v.apply_async(f) - r1 = ar.get(1) - # give the Hub a chance to notice: - self._wait_for_idle() - ahr = self.client.resubmit(ar.msg_ids) - ahr.get(1) - time.sleep(0.5) - records = self.client.db_query({'msg_id': {'$in': ar.msg_ids + ahr.msg_ids}}, keys='header') - h1,h2 = [ r['header'] for r in records ] - for key in set(h1.keys()).union(set(h2.keys())): - if key in ('msg_id', 'date'): - self.assertNotEqual(h1[key], h2[key]) - else: - self.assertEqual(h1[key], h2[key]) - - def test_resubmit_aborted(self): - def f(): - import random - return random.random() - v = self.client.load_balanced_view() - # restrict to one engine, so we can put a sleep - # ahead of the task, so it will get aborted - eid = self.client.ids[-1] - v.targets = [eid] - sleep = v.apply_async(time.sleep, 0.5) - ar = v.apply_async(f) - ar.abort() - self.assertRaises(error.TaskAborted, ar.get) - # Give the Hub a chance to get up to date: - self._wait_for_idle() - ahr = self.client.resubmit(ar.msg_ids) - r2 = ahr.get(1) - - def test_resubmit_inflight(self): - """resubmit of inflight task""" - v = self.client.load_balanced_view() - ar = v.apply_async(time.sleep,1) - # give the message a chance to arrive - time.sleep(0.2) - ahr = self.client.resubmit(ar.msg_ids) - ar.get(2) - ahr.get(2) - - def test_resubmit_badkey(self): - """ensure KeyError on resubmit of nonexistant task""" - self.assertRaisesRemote(KeyError, self.client.resubmit, ['invalid']) - - def test_purge_hub_results(self): - # ensure there are some tasks - for i in range(5): - self.client[:].apply_sync(lambda : 1) - # Wait for the Hub to realise the result is done: - # This prevents a race condition, where we - # might purge a result the Hub still thinks is pending. - self._wait_for_idle() - rc2 = clientmod.Client(profile='iptest') - hist = self.client.hub_history() - ahr = rc2.get_result([hist[-1]]) - ahr.wait(10) - self.client.purge_hub_results(hist[-1]) - newhist = self.client.hub_history() - self.assertEqual(len(newhist)+1,len(hist)) - rc2.spin() - rc2.close() - - def test_purge_local_results(self): - # ensure there are some tasks - res = [] - for i in range(5): - res.append(self.client[:].apply_async(lambda : 1)) - self._wait_for_idle() - self.client.wait(10) # wait for the results to come back - before = len(self.client.results) - self.assertEqual(len(self.client.metadata),before) - self.client.purge_local_results(res[-1]) - self.assertEqual(len(self.client.results),before-len(res[-1]), msg="Not removed from results") - self.assertEqual(len(self.client.metadata),before-len(res[-1]), msg="Not removed from metadata") - - def test_purge_local_results_outstanding(self): - v = self.client[-1] - ar = v.apply_async(lambda : 1) - msg_id = ar.msg_ids[0] - ar.owner = False - ar.get() - self._wait_for_idle() - ar2 = v.apply_async(time.sleep, 1) - self.assertIn(msg_id, self.client.results) - self.assertIn(msg_id, self.client.metadata) - self.client.purge_local_results(ar) - self.assertNotIn(msg_id, self.client.results) - self.assertNotIn(msg_id, self.client.metadata) - with self.assertRaises(RuntimeError): - self.client.purge_local_results(ar2) - ar2.get() - self.client.purge_local_results(ar2) - - def test_purge_all_local_results_outstanding(self): - v = self.client[-1] - ar = v.apply_async(time.sleep, 1) - with self.assertRaises(RuntimeError): - self.client.purge_local_results('all') - ar.get() - self.client.purge_local_results('all') - - def test_purge_all_hub_results(self): - self.client.purge_hub_results('all') - hist = self.client.hub_history() - self.assertEqual(len(hist), 0) - - def test_purge_all_local_results(self): - self.client.purge_local_results('all') - self.assertEqual(len(self.client.results), 0, msg="Results not empty") - self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty") - - def test_purge_all_results(self): - # ensure there are some tasks - for i in range(5): - self.client[:].apply_sync(lambda : 1) - self.client.wait(10) - self._wait_for_idle() - self.client.purge_results('all') - self.assertEqual(len(self.client.results), 0, msg="Results not empty") - self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty") - hist = self.client.hub_history() - self.assertEqual(len(hist), 0, msg="hub history not empty") - - def test_purge_everything(self): - # ensure there are some tasks - for i in range(5): - self.client[:].apply_sync(lambda : 1) - self.client.wait(10) - self._wait_for_idle() - self.client.purge_everything() - # The client results - self.assertEqual(len(self.client.results), 0, msg="Results not empty") - self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty") - # The client "bookkeeping" - self.assertEqual(len(self.client.session.digest_history), 0, msg="session digest not empty") - self.assertEqual(len(self.client.history), 0, msg="client history not empty") - # the hub results - hist = self.client.hub_history() - self.assertEqual(len(hist), 0, msg="hub history not empty") - - - def test_spin_thread(self): - self.client.spin_thread(0.01) - ar = self.client[-1].apply_async(lambda : 1) - md = self.client.metadata[ar.msg_ids[0]] - # 3s timeout, 100ms poll - for i in range(30): - time.sleep(0.1) - if md['received'] is not None: - break - self.assertIsInstance(md['received'], datetime) - - def test_stop_spin_thread(self): - self.client.spin_thread(0.01) - self.client.stop_spin_thread() - ar = self.client[-1].apply_async(lambda : 1) - md = self.client.metadata[ar.msg_ids[0]] - # 500ms timeout, 100ms poll - for i in range(5): - time.sleep(0.1) - self.assertIsNone(md['received'], None) - - def test_activate(self): - ip = get_ipython() - magics = ip.magics_manager.magics - self.assertTrue('px' in magics['line']) - self.assertTrue('px' in magics['cell']) - v0 = self.client.activate(-1, '0') - self.assertTrue('px0' in magics['line']) - self.assertTrue('px0' in magics['cell']) - self.assertEqual(v0.targets, self.client.ids[-1]) - v0 = self.client.activate('all', 'all') - self.assertTrue('pxall' in magics['line']) - self.assertTrue('pxall' in magics['cell']) - self.assertEqual(v0.targets, 'all') diff --git a/ipython_parallel/tests/test_db.py b/ipython_parallel/tests/test_db.py deleted file mode 100644 index 128c70e..0000000 --- a/ipython_parallel/tests/test_db.py +++ /dev/null @@ -1,314 +0,0 @@ -"""Tests for db backends - -Authors: - -* Min RK -""" - -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -from __future__ import division - -import logging -import os -import tempfile -import time - -from datetime import datetime, timedelta -from unittest import TestCase - -from ipython_parallel import error -from ipython_parallel.controller.dictdb import DictDB -from ipython_parallel.controller.sqlitedb import SQLiteDB -from ipython_parallel.controller.hub import init_record, empty_record - -from IPython.testing import decorators as dec -from IPython.kernel.zmq.session import Session - - -#------------------------------------------------------------------------------- -# TestCases -#------------------------------------------------------------------------------- - - -def setup(): - global temp_db - temp_db = tempfile.NamedTemporaryFile(suffix='.db').name - - -class TaskDBTest: - def setUp(self): - self.session = Session() - self.db = self.create_db() - self.load_records(16) - - def create_db(self): - raise NotImplementedError - - def load_records(self, n=1, buffer_size=100): - """load n records for testing""" - #sleep 1/10 s, to ensure timestamp is different to previous calls - time.sleep(0.1) - msg_ids = [] - for i in range(n): - msg = self.session.msg('apply_request', content=dict(a=5)) - msg['buffers'] = [os.urandom(buffer_size)] - rec = init_record(msg) - msg_id = msg['header']['msg_id'] - msg_ids.append(msg_id) - self.db.add_record(msg_id, rec) - return msg_ids - - def test_add_record(self): - before = self.db.get_history() - self.load_records(5) - after = self.db.get_history() - self.assertEqual(len(after), len(before)+5) - self.assertEqual(after[:-5],before) - - def test_drop_record(self): - msg_id = self.load_records()[-1] - rec = self.db.get_record(msg_id) - self.db.drop_record(msg_id) - self.assertRaises(KeyError,self.db.get_record, msg_id) - - def _round_to_millisecond(self, dt): - """necessary because mongodb rounds microseconds""" - micro = dt.microsecond - extra = int(str(micro)[-3:]) - return dt - timedelta(microseconds=extra) - - def test_update_record(self): - now = self._round_to_millisecond(datetime.now()) - # - msg_id = self.db.get_history()[-1] - rec1 = self.db.get_record(msg_id) - data = {'stdout': 'hello there', 'completed' : now} - self.db.update_record(msg_id, data) - rec2 = self.db.get_record(msg_id) - self.assertEqual(rec2['stdout'], 'hello there') - self.assertEqual(rec2['completed'], now) - rec1.update(data) - self.assertEqual(rec1, rec2) - - # def test_update_record_bad(self): - # """test updating nonexistant records""" - # msg_id = str(uuid.uuid4()) - # data = {'stdout': 'hello there'} - # self.assertRaises(KeyError, self.db.update_record, msg_id, data) - - def test_find_records_dt(self): - """test finding records by date""" - hist = self.db.get_history() - middle = self.db.get_record(hist[len(hist)//2]) - tic = middle['submitted'] - before = self.db.find_records({'submitted' : {'$lt' : tic}}) - after = self.db.find_records({'submitted' : {'$gte' : tic}}) - self.assertEqual(len(before)+len(after),len(hist)) - for b in before: - self.assertTrue(b['submitted'] < tic) - for a in after: - self.assertTrue(a['submitted'] >= tic) - same = self.db.find_records({'submitted' : tic}) - for s in same: - self.assertTrue(s['submitted'] == tic) - - def test_find_records_keys(self): - """test extracting subset of record keys""" - found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed']) - for rec in found: - self.assertEqual(set(rec.keys()), set(['msg_id', 'submitted', 'completed'])) - - def test_find_records_msg_id(self): - """ensure msg_id is always in found records""" - found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed']) - for rec in found: - self.assertTrue('msg_id' in rec.keys()) - found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted']) - for rec in found: - self.assertTrue('msg_id' in rec.keys()) - found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['msg_id']) - for rec in found: - self.assertTrue('msg_id' in rec.keys()) - - def test_find_records_in(self): - """test finding records with '$in','$nin' operators""" - hist = self.db.get_history() - even = hist[::2] - odd = hist[1::2] - recs = self.db.find_records({ 'msg_id' : {'$in' : even}}) - found = [ r['msg_id'] for r in recs ] - self.assertEqual(set(even), set(found)) - recs = self.db.find_records({ 'msg_id' : {'$nin' : even}}) - found = [ r['msg_id'] for r in recs ] - self.assertEqual(set(odd), set(found)) - - def test_get_history(self): - msg_ids = self.db.get_history() - latest = datetime(1984,1,1) - for msg_id in msg_ids: - rec = self.db.get_record(msg_id) - newt = rec['submitted'] - self.assertTrue(newt >= latest) - latest = newt - msg_id = self.load_records(1)[-1] - self.assertEqual(self.db.get_history()[-1],msg_id) - - def test_datetime(self): - """get/set timestamps with datetime objects""" - msg_id = self.db.get_history()[-1] - rec = self.db.get_record(msg_id) - self.assertTrue(isinstance(rec['submitted'], datetime)) - self.db.update_record(msg_id, dict(completed=datetime.now())) - rec = self.db.get_record(msg_id) - self.assertTrue(isinstance(rec['completed'], datetime)) - - def test_drop_matching(self): - msg_ids = self.load_records(10) - query = {'msg_id' : {'$in':msg_ids}} - self.db.drop_matching_records(query) - recs = self.db.find_records(query) - self.assertEqual(len(recs), 0) - - def test_null(self): - """test None comparison queries""" - msg_ids = self.load_records(10) - - query = {'msg_id' : None} - recs = self.db.find_records(query) - self.assertEqual(len(recs), 0) - - query = {'msg_id' : {'$ne' : None}} - recs = self.db.find_records(query) - self.assertTrue(len(recs) >= 10) - - def test_pop_safe_get(self): - """editing query results shouldn't affect record [get]""" - msg_id = self.db.get_history()[-1] - rec = self.db.get_record(msg_id) - rec.pop('buffers') - rec['garbage'] = 'hello' - rec['header']['msg_id'] = 'fubar' - rec2 = self.db.get_record(msg_id) - self.assertTrue('buffers' in rec2) - self.assertFalse('garbage' in rec2) - self.assertEqual(rec2['header']['msg_id'], msg_id) - - def test_pop_safe_find(self): - """editing query results shouldn't affect record [find]""" - msg_id = self.db.get_history()[-1] - rec = self.db.find_records({'msg_id' : msg_id})[0] - rec.pop('buffers') - rec['garbage'] = 'hello' - rec['header']['msg_id'] = 'fubar' - rec2 = self.db.find_records({'msg_id' : msg_id})[0] - self.assertTrue('buffers' in rec2) - self.assertFalse('garbage' in rec2) - self.assertEqual(rec2['header']['msg_id'], msg_id) - - def test_pop_safe_find_keys(self): - """editing query results shouldn't affect record [find+keys]""" - msg_id = self.db.get_history()[-1] - rec = self.db.find_records({'msg_id' : msg_id}, keys=['buffers', 'header'])[0] - rec.pop('buffers') - rec['garbage'] = 'hello' - rec['header']['msg_id'] = 'fubar' - rec2 = self.db.find_records({'msg_id' : msg_id})[0] - self.assertTrue('buffers' in rec2) - self.assertFalse('garbage' in rec2) - self.assertEqual(rec2['header']['msg_id'], msg_id) - - -class TestDictBackend(TaskDBTest, TestCase): - - def create_db(self): - return DictDB() - - def test_cull_count(self): - self.db = self.create_db() # skip the load-records init from setUp - self.db.record_limit = 20 - self.db.cull_fraction = 0.2 - self.load_records(20) - self.assertEqual(len(self.db.get_history()), 20) - self.load_records(1) - # 0.2 * 20 = 4, 21 - 4 = 17 - self.assertEqual(len(self.db.get_history()), 17) - self.load_records(3) - self.assertEqual(len(self.db.get_history()), 20) - self.load_records(1) - self.assertEqual(len(self.db.get_history()), 17) - - for i in range(25): - self.load_records(1) - self.assertTrue(len(self.db.get_history()) >= 17) - self.assertTrue(len(self.db.get_history()) <= 20) - - def test_cull_size(self): - self.db = self.create_db() # skip the load-records init from setUp - self.db.size_limit = 1000 - self.db.cull_fraction = 0.2 - self.load_records(100, buffer_size=10) - self.assertEqual(len(self.db.get_history()), 100) - self.load_records(1, buffer_size=0) - self.assertEqual(len(self.db.get_history()), 101) - self.load_records(1, buffer_size=1) - # 0.2 * 100 = 20, 101 - 20 = 81 - self.assertEqual(len(self.db.get_history()), 81) - - def test_cull_size_drop(self): - """dropping records updates tracked buffer size""" - self.db = self.create_db() # skip the load-records init from setUp - self.db.size_limit = 1000 - self.db.cull_fraction = 0.2 - self.load_records(100, buffer_size=10) - self.assertEqual(len(self.db.get_history()), 100) - self.db.drop_record(self.db.get_history()[-1]) - self.assertEqual(len(self.db.get_history()), 99) - self.load_records(1, buffer_size=5) - self.assertEqual(len(self.db.get_history()), 100) - self.load_records(1, buffer_size=5) - self.assertEqual(len(self.db.get_history()), 101) - self.load_records(1, buffer_size=1) - self.assertEqual(len(self.db.get_history()), 81) - - def test_cull_size_update(self): - """updating records updates tracked buffer size""" - self.db = self.create_db() # skip the load-records init from setUp - self.db.size_limit = 1000 - self.db.cull_fraction = 0.2 - self.load_records(100, buffer_size=10) - self.assertEqual(len(self.db.get_history()), 100) - msg_id = self.db.get_history()[-1] - self.db.update_record(msg_id, dict(result_buffers = [os.urandom(10)], buffers=[])) - self.assertEqual(len(self.db.get_history()), 100) - self.db.update_record(msg_id, dict(result_buffers = [os.urandom(11)], buffers=[])) - self.assertEqual(len(self.db.get_history()), 79) - -class TestSQLiteBackend(TaskDBTest, TestCase): - - @dec.skip_without('sqlite3') - def create_db(self): - location, fname = os.path.split(temp_db) - log = logging.getLogger('test') - log.setLevel(logging.CRITICAL) - return SQLiteDB(location=location, fname=fname, log=log) - - def tearDown(self): - self.db._db.close() - - -def teardown(): - """cleanup task db file after all tests have run""" - try: - os.remove(temp_db) - except: - pass diff --git a/ipython_parallel/tests/test_dependency.py b/ipython_parallel/tests/test_dependency.py deleted file mode 100644 index 39aa436..0000000 --- a/ipython_parallel/tests/test_dependency.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Tests for dependency.py - -Authors: - -* Min RK -""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -# import -import os - -from ipython_kernel.pickleutil import can, uncan - -import ipython_parallel as pmod -from ipython_parallel.util import interactive - -from ipython_parallel.tests import add_engines -from .clienttest import ClusterTestCase - -def setup(): - add_engines(1, total=True) - -@pmod.require('time') -def wait(n): - time.sleep(n) - return n - -@pmod.interactive -def func(x): - return x*x - -mixed = list(map(str, range(10))) -completed = list(map(str, range(0,10,2))) -failed = list(map(str, range(1,10,2))) - -class DependencyTest(ClusterTestCase): - - def setUp(self): - ClusterTestCase.setUp(self) - self.user_ns = {'__builtins__' : __builtins__} - self.view = self.client.load_balanced_view() - self.dview = self.client[-1] - self.succeeded = set(map(str, range(0,25,2))) - self.failed = set(map(str, range(1,25,2))) - - def assertMet(self, dep): - self.assertTrue(dep.check(self.succeeded, self.failed), "Dependency should be met") - - def assertUnmet(self, dep): - self.assertFalse(dep.check(self.succeeded, self.failed), "Dependency should not be met") - - def assertUnreachable(self, dep): - self.assertTrue(dep.unreachable(self.succeeded, self.failed), "Dependency should be unreachable") - - def assertReachable(self, dep): - self.assertFalse(dep.unreachable(self.succeeded, self.failed), "Dependency should be reachable") - - def cancan(self, f): - """decorator to pass through canning into self.user_ns""" - return uncan(can(f), self.user_ns) - - def test_require_imports(self): - """test that @require imports names""" - @self.cancan - @pmod.require('base64') - @interactive - def encode(arg): - return base64.b64encode(arg) - # must pass through canning to properly connect namespaces - self.assertEqual(encode(b'foo'), b'Zm9v') - - def test_success_only(self): - dep = pmod.Dependency(mixed, success=True, failure=False) - self.assertUnmet(dep) - self.assertUnreachable(dep) - dep.all=False - self.assertMet(dep) - self.assertReachable(dep) - dep = pmod.Dependency(completed, success=True, failure=False) - self.assertMet(dep) - self.assertReachable(dep) - dep.all=False - self.assertMet(dep) - self.assertReachable(dep) - - def test_failure_only(self): - dep = pmod.Dependency(mixed, success=False, failure=True) - self.assertUnmet(dep) - self.assertUnreachable(dep) - dep.all=False - self.assertMet(dep) - self.assertReachable(dep) - dep = pmod.Dependency(completed, success=False, failure=True) - self.assertUnmet(dep) - self.assertUnreachable(dep) - dep.all=False - self.assertUnmet(dep) - self.assertUnreachable(dep) - - def test_require_function(self): - - @pmod.interactive - def bar(a): - return func(a) - - @pmod.require(func) - @pmod.interactive - def bar2(a): - return func(a) - - self.client[:].clear() - self.assertRaisesRemote(NameError, self.view.apply_sync, bar, 5) - ar = self.view.apply_async(bar2, 5) - self.assertEqual(ar.get(5), func(5)) - - def test_require_object(self): - - @pmod.require(foo=func) - @pmod.interactive - def bar(a): - return foo(a) - - ar = self.view.apply_async(bar, 5) - self.assertEqual(ar.get(5), func(5)) diff --git a/ipython_parallel/tests/test_launcher.py b/ipython_parallel/tests/test_launcher.py deleted file mode 100644 index a797cd2..0000000 --- a/ipython_parallel/tests/test_launcher.py +++ /dev/null @@ -1,194 +0,0 @@ -"""Tests for launchers - -Doesn't actually start any subprocesses, but goes through the motions of constructing -objects, which should test basic config. - -Authors: - -* Min RK -""" - -#------------------------------------------------------------------------------- -# Copyright (C) 2013 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import logging -import os -import shutil -import sys -import tempfile - -from unittest import TestCase - -from nose import SkipTest - -from IPython.config import Config - -from ipython_parallel.apps import launcher - -from IPython.testing import decorators as dec -from IPython.utils.py3compat import string_types - - -#------------------------------------------------------------------------------- -# TestCase Mixins -#------------------------------------------------------------------------------- - -class LauncherTest: - """Mixin for generic launcher tests""" - def setUp(self): - self.profile_dir = tempfile.mkdtemp(prefix="profile_") - - def tearDown(self): - shutil.rmtree(self.profile_dir) - - @property - def config(self): - return Config() - - def build_launcher(self, **kwargs): - kw = dict( - work_dir=self.profile_dir, - profile_dir=self.profile_dir, - config=self.config, - cluster_id='', - log=logging.getLogger(), - ) - kw.update(kwargs) - return self.launcher_class(**kw) - - def test_profile_dir_arg(self): - launcher = self.build_launcher() - self.assertTrue("--profile-dir" in launcher.cluster_args) - self.assertTrue(self.profile_dir in launcher.cluster_args) - - def test_cluster_id_arg(self): - launcher = self.build_launcher() - self.assertTrue("--cluster-id" in launcher.cluster_args) - idx = launcher.cluster_args.index("--cluster-id") - self.assertEqual(launcher.cluster_args[idx+1], '') - launcher.cluster_id = 'foo' - self.assertEqual(launcher.cluster_args[idx+1], 'foo') - - def test_args(self): - launcher = self.build_launcher() - for arg in launcher.args: - self.assertTrue(isinstance(arg, string_types), str(arg)) - -class BatchTest: - """Tests for batch-system launchers (LSF, SGE, PBS)""" - def test_batch_template(self): - launcher = self.build_launcher() - batch_file = os.path.join(self.profile_dir, launcher.batch_file_name) - self.assertEqual(launcher.batch_file, batch_file) - launcher.write_batch_script(1) - self.assertTrue(os.path.isfile(batch_file)) - -class SSHTest: - """Tests for SSH launchers""" - def test_cluster_id_arg(self): - raise SkipTest("SSH Launchers don't support cluster ID") - - def test_remote_profile_dir(self): - cfg = Config() - launcher_cfg = getattr(cfg, self.launcher_class.__name__) - launcher_cfg.remote_profile_dir = "foo" - launcher = self.build_launcher(config=cfg) - self.assertEqual(launcher.remote_profile_dir, "foo") - - def test_remote_profile_dir_default(self): - launcher = self.build_launcher() - self.assertEqual(launcher.remote_profile_dir, self.profile_dir) - -#------------------------------------------------------------------------------- -# Controller Launcher Tests -#------------------------------------------------------------------------------- - -class ControllerLauncherTest(LauncherTest): - """Tests for Controller Launchers""" - pass - -class TestLocalControllerLauncher(ControllerLauncherTest, TestCase): - launcher_class = launcher.LocalControllerLauncher - -class TestMPIControllerLauncher(ControllerLauncherTest, TestCase): - launcher_class = launcher.MPIControllerLauncher - -class TestPBSControllerLauncher(BatchTest, ControllerLauncherTest, TestCase): - launcher_class = launcher.PBSControllerLauncher - -class TestSGEControllerLauncher(BatchTest, ControllerLauncherTest, TestCase): - launcher_class = launcher.SGEControllerLauncher - -class TestLSFControllerLauncher(BatchTest, ControllerLauncherTest, TestCase): - launcher_class = launcher.LSFControllerLauncher - -class TestHTCondorControllerLauncher(BatchTest, ControllerLauncherTest, TestCase): - launcher_class = launcher.HTCondorControllerLauncher - -class TestSSHControllerLauncher(SSHTest, ControllerLauncherTest, TestCase): - launcher_class = launcher.SSHControllerLauncher - -#------------------------------------------------------------------------------- -# Engine Set Launcher Tests -#------------------------------------------------------------------------------- - -class EngineSetLauncherTest(LauncherTest): - """Tests for EngineSet launchers""" - pass - -class TestLocalEngineSetLauncher(EngineSetLauncherTest, TestCase): - launcher_class = launcher.LocalEngineSetLauncher - -class TestMPIEngineSetLauncher(EngineSetLauncherTest, TestCase): - launcher_class = launcher.MPIEngineSetLauncher - -class TestPBSEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase): - launcher_class = launcher.PBSEngineSetLauncher - -class TestSGEEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase): - launcher_class = launcher.SGEEngineSetLauncher - -class TestLSFEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase): - launcher_class = launcher.LSFEngineSetLauncher - -class TestHTCondorEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase): - launcher_class = launcher.HTCondorEngineSetLauncher - -class TestSSHEngineSetLauncher(EngineSetLauncherTest, TestCase): - launcher_class = launcher.SSHEngineSetLauncher - - def test_cluster_id_arg(self): - raise SkipTest("SSH Launchers don't support cluster ID") - -class TestSSHProxyEngineSetLauncher(SSHTest, LauncherTest, TestCase): - launcher_class = launcher.SSHProxyEngineSetLauncher - -class TestSSHEngineLauncher(SSHTest, LauncherTest, TestCase): - launcher_class = launcher.SSHEngineLauncher - -#------------------------------------------------------------------------------- -# Windows Launcher Tests -#------------------------------------------------------------------------------- - -class WinHPCTest: - """Tests for WinHPC Launchers""" - def test_batch_template(self): - launcher = self.build_launcher() - job_file = os.path.join(self.profile_dir, launcher.job_file_name) - self.assertEqual(launcher.job_file, job_file) - launcher.write_job_file(1) - self.assertTrue(os.path.isfile(job_file)) - -class TestWinHPCControllerLauncher(WinHPCTest, ControllerLauncherTest, TestCase): - launcher_class = launcher.WindowsHPCControllerLauncher - -class TestWinHPCEngineSetLauncher(WinHPCTest, EngineSetLauncherTest, TestCase): - launcher_class = launcher.WindowsHPCEngineSetLauncher diff --git a/ipython_parallel/tests/test_lbview.py b/ipython_parallel/tests/test_lbview.py deleted file mode 100644 index 5608994..0000000 --- a/ipython_parallel/tests/test_lbview.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -"""test LoadBalancedView objects - -Authors: - -* Min RK -""" -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import sys -import time - -import zmq -from nose import SkipTest -from nose.plugins.attrib import attr - -from IPython import parallel as pmod -from ipython_parallel import error - -from ipython_parallel.tests import add_engines - -from .clienttest import ClusterTestCase, crash, wait, skip_without - -def setup(): - add_engines(3, total=True) - -class TestLoadBalancedView(ClusterTestCase): - - def setUp(self): - ClusterTestCase.setUp(self) - self.view = self.client.load_balanced_view() - - @attr('crash') - def test_z_crash_task(self): - """test graceful handling of engine death (balanced)""" - # self.add_engines(1) - ar = self.view.apply_async(crash) - self.assertRaisesRemote(error.EngineError, ar.get, 10) - eid = ar.engine_id - tic = time.time() - while eid in self.client.ids and time.time()-tic < 5: - time.sleep(.01) - self.client.spin() - self.assertFalse(eid in self.client.ids, "Engine should have died") - - def test_map(self): - def f(x): - return x**2 - data = list(range(16)) - r = self.view.map_sync(f, data) - self.assertEqual(r, list(map(f, data))) - - def test_map_generator(self): - def f(x): - return x**2 - - data = list(range(16)) - r = self.view.map_sync(f, iter(data)) - self.assertEqual(r, list(map(f, iter(data)))) - - def test_map_short_first(self): - def f(x,y): - if y is None: - return y - if x is None: - return x - return x*y - data = list(range(10)) - data2 = list(range(4)) - - r = self.view.map_sync(f, data, data2) - self.assertEqual(r, list(map(f, data, data2))) - - def test_map_short_last(self): - def f(x,y): - if y is None: - return y - if x is None: - return x - return x*y - data = list(range(4)) - data2 = list(range(10)) - - r = self.view.map_sync(f, data, data2) - self.assertEqual(r, list(map(f, data, data2))) - - def test_map_unordered(self): - def f(x): - return x**2 - def slow_f(x): - import time - time.sleep(0.05*x) - return x**2 - data = list(range(16,0,-1)) - reference = list(map(f, data)) - - amr = self.view.map_async(slow_f, data, ordered=False) - self.assertTrue(isinstance(amr, pmod.AsyncMapResult)) - # check individual elements, retrieved as they come - # list comprehension uses __iter__ - astheycame = [ r for r in amr ] - # Ensure that at least one result came out of order: - self.assertNotEqual(astheycame, reference, "should not have preserved order") - self.assertEqual(sorted(astheycame, reverse=True), reference, "result corrupted") - - def test_map_ordered(self): - def f(x): - return x**2 - def slow_f(x): - import time - time.sleep(0.05*x) - return x**2 - data = list(range(16,0,-1)) - reference = list(map(f, data)) - - amr = self.view.map_async(slow_f, data) - self.assertTrue(isinstance(amr, pmod.AsyncMapResult)) - # check individual elements, retrieved as they come - # list(amr) uses __iter__ - astheycame = list(amr) - # Ensure that results came in order - self.assertEqual(astheycame, reference) - self.assertEqual(amr.result, reference) - - def test_map_iterable(self): - """test map on iterables (balanced)""" - view = self.view - # 101 is prime, so it won't be evenly distributed - arr = range(101) - # so that it will be an iterator, even in Python 3 - it = iter(arr) - r = view.map_sync(lambda x:x, arr) - self.assertEqual(r, list(arr)) - - - def test_abort(self): - view = self.view - ar = self.client[:].apply_async(time.sleep, .5) - ar = self.client[:].apply_async(time.sleep, .5) - time.sleep(0.2) - ar2 = view.apply_async(lambda : 2) - ar3 = view.apply_async(lambda : 3) - view.abort(ar2) - view.abort(ar3.msg_ids) - self.assertRaises(error.TaskAborted, ar2.get) - self.assertRaises(error.TaskAborted, ar3.get) - - def test_retries(self): - self.minimum_engines(3) - view = self.view - def fail(): - assert False - for r in range(len(self.client)-1): - with view.temp_flags(retries=r): - self.assertRaisesRemote(AssertionError, view.apply_sync, fail) - - with view.temp_flags(retries=len(self.client), timeout=0.1): - self.assertRaisesRemote(error.TaskTimeout, view.apply_sync, fail) - - def test_short_timeout(self): - self.minimum_engines(2) - view = self.view - def fail(): - import time - time.sleep(0.25) - assert False - with view.temp_flags(retries=1, timeout=0.01): - self.assertRaisesRemote(AssertionError, view.apply_sync, fail) - - def test_invalid_dependency(self): - view = self.view - with view.temp_flags(after='12345'): - self.assertRaisesRemote(error.InvalidDependency, view.apply_sync, lambda : 1) - - def test_impossible_dependency(self): - self.minimum_engines(2) - view = self.client.load_balanced_view() - ar1 = view.apply_async(lambda : 1) - ar1.get() - e1 = ar1.engine_id - e2 = e1 - while e2 == e1: - ar2 = view.apply_async(lambda : 1) - ar2.get() - e2 = ar2.engine_id - - with view.temp_flags(follow=[ar1, ar2]): - self.assertRaisesRemote(error.ImpossibleDependency, view.apply_sync, lambda : 1) - - - def test_follow(self): - ar = self.view.apply_async(lambda : 1) - ar.get() - ars = [] - first_id = ar.engine_id - - self.view.follow = ar - for i in range(5): - ars.append(self.view.apply_async(lambda : 1)) - self.view.wait(ars) - for ar in ars: - self.assertEqual(ar.engine_id, first_id) - - def test_after(self): - view = self.view - ar = view.apply_async(time.sleep, 0.5) - with view.temp_flags(after=ar): - ar2 = view.apply_async(lambda : 1) - - ar.wait() - ar2.wait() - self.assertTrue(ar2.started >= ar.completed, "%s not >= %s"%(ar.started, ar.completed)) diff --git a/ipython_parallel/tests/test_magics.py b/ipython_parallel/tests/test_magics.py deleted file mode 100644 index c7fba9a..0000000 --- a/ipython_parallel/tests/test_magics.py +++ /dev/null @@ -1,374 +0,0 @@ -# -*- coding: utf-8 -*- -"""Test Parallel magics - -Authors: - -* Min RK -""" -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import re -import time - - -from IPython.testing import decorators as dec -from IPython.utils.io import capture_output - -from IPython import parallel as pmod -from ipython_parallel import AsyncResult - -from ipython_parallel.tests import add_engines - -from .clienttest import ClusterTestCase, generate_output - -def setup(): - add_engines(3, total=True) - -class TestParallelMagics(ClusterTestCase): - - def test_px_blocking(self): - ip = get_ipython() - v = self.client[-1:] - v.activate() - v.block=True - - ip.magic('px a=5') - self.assertEqual(v['a'], [5]) - ip.magic('px a=10') - self.assertEqual(v['a'], [10]) - # just 'print a' works ~99% of the time, but this ensures that - # the stdout message has arrived when the result is finished: - with capture_output() as io: - ip.magic( - 'px import sys,time;print(a);sys.stdout.flush();time.sleep(0.2)' - ) - self.assertIn('[stdout:', io.stdout) - self.assertNotIn('\n\n', io.stdout) - assert io.stdout.rstrip().endswith('10') - self.assertRaisesRemote(ZeroDivisionError, ip.magic, 'px 1/0') - - def _check_generated_stderr(self, stderr, n): - expected = [ - r'\[stderr:\d+\]', - '^stderr$', - '^stderr2$', - ] * n - - self.assertNotIn('\n\n', stderr) - lines = stderr.splitlines() - self.assertEqual(len(lines), len(expected), stderr) - for line,expect in zip(lines, expected): - if isinstance(expect, str): - expect = [expect] - for ex in expect: - assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line) - - def test_cellpx_block_args(self): - """%%px --[no]block flags work""" - ip = get_ipython() - v = self.client[-1:] - v.activate() - v.block=False - - for block in (True, False): - v.block = block - ip.magic("pxconfig --verbose") - with capture_output(display=False) as io: - ip.run_cell_magic("px", "", "1") - if block: - assert io.stdout.startswith("Parallel"), io.stdout - else: - assert io.stdout.startswith("Async"), io.stdout - - with capture_output(display=False) as io: - ip.run_cell_magic("px", "--block", "1") - assert io.stdout.startswith("Parallel"), io.stdout - - with capture_output(display=False) as io: - ip.run_cell_magic("px", "--noblock", "1") - assert io.stdout.startswith("Async"), io.stdout - - def test_cellpx_groupby_engine(self): - """%%px --group-outputs=engine""" - ip = get_ipython() - v = self.client[:] - v.block = True - v.activate() - - v['generate_output'] = generate_output - - with capture_output(display=False) as io: - ip.run_cell_magic('px', '--group-outputs=engine', 'generate_output()') - - self.assertNotIn('\n\n', io.stdout) - lines = io.stdout.splitlines() - expected = [ - r'\[stdout:\d+\]', - 'stdout', - 'stdout2', - r'\[output:\d+\]', - r'IPython\.core\.display\.HTML', - r'IPython\.core\.display\.Math', - r'Out\[\d+:\d+\]:.*IPython\.core\.display\.Math', - ] * len(v) - - self.assertEqual(len(lines), len(expected), io.stdout) - for line,expect in zip(lines, expected): - if isinstance(expect, str): - expect = [expect] - for ex in expect: - assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line) - - self._check_generated_stderr(io.stderr, len(v)) - - - def test_cellpx_groupby_order(self): - """%%px --group-outputs=order""" - ip = get_ipython() - v = self.client[:] - v.block = True - v.activate() - - v['generate_output'] = generate_output - - with capture_output(display=False) as io: - ip.run_cell_magic('px', '--group-outputs=order', 'generate_output()') - - self.assertNotIn('\n\n', io.stdout) - lines = io.stdout.splitlines() - expected = [] - expected.extend([ - r'\[stdout:\d+\]', - 'stdout', - 'stdout2', - ] * len(v)) - expected.extend([ - r'\[output:\d+\]', - 'IPython.core.display.HTML', - ] * len(v)) - expected.extend([ - r'\[output:\d+\]', - 'IPython.core.display.Math', - ] * len(v)) - expected.extend([ - r'Out\[\d+:\d+\]:.*IPython\.core\.display\.Math' - ] * len(v)) - - self.assertEqual(len(lines), len(expected), io.stdout) - for line,expect in zip(lines, expected): - if isinstance(expect, str): - expect = [expect] - for ex in expect: - assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line) - - self._check_generated_stderr(io.stderr, len(v)) - - def test_cellpx_groupby_type(self): - """%%px --group-outputs=type""" - ip = get_ipython() - v = self.client[:] - v.block = True - v.activate() - - v['generate_output'] = generate_output - - with capture_output(display=False) as io: - ip.run_cell_magic('px', '--group-outputs=type', 'generate_output()') - - self.assertNotIn('\n\n', io.stdout) - lines = io.stdout.splitlines() - - expected = [] - expected.extend([ - r'\[stdout:\d+\]', - 'stdout', - 'stdout2', - ] * len(v)) - expected.extend([ - r'\[output:\d+\]', - r'IPython\.core\.display\.HTML', - r'IPython\.core\.display\.Math', - ] * len(v)) - expected.extend([ - (r'Out\[\d+:\d+\]', r'IPython\.core\.display\.Math') - ] * len(v)) - - self.assertEqual(len(lines), len(expected), io.stdout) - for line,expect in zip(lines, expected): - if isinstance(expect, str): - expect = [expect] - for ex in expect: - assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line) - - self._check_generated_stderr(io.stderr, len(v)) - - - def test_px_nonblocking(self): - ip = get_ipython() - v = self.client[-1:] - v.activate() - v.block=False - - ip.magic('px a=5') - self.assertEqual(v['a'], [5]) - ip.magic('px a=10') - self.assertEqual(v['a'], [10]) - ip.magic('pxconfig --verbose') - with capture_output() as io: - ar = ip.magic('px print (a)') - self.assertIsInstance(ar, AsyncResult) - self.assertIn('Async', io.stdout) - self.assertNotIn('[stdout:', io.stdout) - self.assertNotIn('\n\n', io.stdout) - - ar = ip.magic('px 1/0') - self.assertRaisesRemote(ZeroDivisionError, ar.get) - - def test_autopx_blocking(self): - ip = get_ipython() - v = self.client[-1] - v.activate() - v.block=True - - with capture_output(display=False) as io: - ip.magic('autopx') - ip.run_cell('\n'.join(('a=5','b=12345','c=0'))) - ip.run_cell('b*=2') - ip.run_cell('print (b)') - ip.run_cell('b') - ip.run_cell("b/c") - ip.magic('autopx') - - output = io.stdout - - assert output.startswith('%autopx enabled'), output - assert output.rstrip().endswith('%autopx disabled'), output - self.assertIn('ZeroDivisionError', output) - self.assertIn('\nOut[', output) - self.assertIn(': 24690', output) - ar = v.get_result(-1) - self.assertEqual(v['a'], 5) - self.assertEqual(v['b'], 24690) - self.assertRaisesRemote(ZeroDivisionError, ar.get) - - def test_autopx_nonblocking(self): - ip = get_ipython() - v = self.client[-1] - v.activate() - v.block=False - - with capture_output() as io: - ip.magic('autopx') - ip.run_cell('\n'.join(('a=5','b=10','c=0'))) - ip.run_cell('print (b)') - ip.run_cell('import time; time.sleep(0.1)') - ip.run_cell("b/c") - ip.run_cell('b*=2') - ip.magic('autopx') - - output = io.stdout.rstrip() - - assert output.startswith('%autopx enabled'), output - assert output.endswith('%autopx disabled'), output - self.assertNotIn('ZeroDivisionError', output) - ar = v.get_result(-2) - self.assertRaisesRemote(ZeroDivisionError, ar.get) - # prevent TaskAborted on pulls, due to ZeroDivisionError - time.sleep(0.5) - self.assertEqual(v['a'], 5) - # b*=2 will not fire, due to abort - self.assertEqual(v['b'], 10) - - def test_result(self): - ip = get_ipython() - v = self.client[-1] - v.activate() - data = dict(a=111,b=222) - v.push(data, block=True) - - for name in ('a', 'b'): - ip.magic('px ' + name) - with capture_output(display=False) as io: - ip.magic('pxresult') - self.assertIn(str(data[name]), io.stdout) - - @dec.skipif_not_matplotlib - def test_px_pylab(self): - """%pylab works on engines""" - ip = get_ipython() - v = self.client[-1] - v.block = True - v.activate() - - with capture_output() as io: - ip.magic("px %pylab inline") - - self.assertIn("Populating the interactive namespace from numpy and matplotlib", io.stdout) - - with capture_output(display=False) as io: - ip.magic("px plot(rand(100))") - self.assertIn('Out[', io.stdout) - self.assertIn('matplotlib.lines', io.stdout) - - def test_pxconfig(self): - ip = get_ipython() - rc = self.client - v = rc.activate(-1, '_tst') - self.assertEqual(v.targets, rc.ids[-1]) - ip.magic("%pxconfig_tst -t :") - self.assertEqual(v.targets, rc.ids) - ip.magic("%pxconfig_tst -t ::2") - self.assertEqual(v.targets, rc.ids[::2]) - ip.magic("%pxconfig_tst -t 1::2") - self.assertEqual(v.targets, rc.ids[1::2]) - ip.magic("%pxconfig_tst -t 1") - self.assertEqual(v.targets, 1) - ip.magic("%pxconfig_tst --block") - self.assertEqual(v.block, True) - ip.magic("%pxconfig_tst --noblock") - self.assertEqual(v.block, False) - - def test_cellpx_targets(self): - """%%px --targets doesn't change defaults""" - ip = get_ipython() - rc = self.client - view = rc.activate(rc.ids) - self.assertEqual(view.targets, rc.ids) - ip.magic('pxconfig --verbose') - for cell in ("pass", "1/0"): - with capture_output(display=False) as io: - try: - ip.run_cell_magic("px", "--targets all", cell) - except pmod.RemoteError: - pass - self.assertIn('engine(s): all', io.stdout) - self.assertEqual(view.targets, rc.ids) - - - def test_cellpx_block(self): - """%%px --block doesn't change default""" - ip = get_ipython() - rc = self.client - view = rc.activate(rc.ids) - view.block = False - self.assertEqual(view.targets, rc.ids) - ip.magic('pxconfig --verbose') - for cell in ("pass", "1/0"): - with capture_output(display=False) as io: - try: - ip.run_cell_magic("px", "--block", cell) - except pmod.RemoteError: - pass - self.assertNotIn('Async', io.stdout) - self.assertEqual(view.block, False) - - diff --git a/ipython_parallel/tests/test_mongodb.py b/ipython_parallel/tests/test_mongodb.py deleted file mode 100644 index 80aacd7..0000000 --- a/ipython_parallel/tests/test_mongodb.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Tests for mongodb backend - -Authors: - -* Min RK -""" - -#------------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- - -import os - -from unittest import TestCase - -from nose import SkipTest - -from pymongo import Connection -from ipython_parallel.controller.mongodb import MongoDB - -from . import test_db - -conn_kwargs = {} -if 'DB_IP' in os.environ: - conn_kwargs['host'] = os.environ['DB_IP'] -if 'DBA_MONGODB_ADMIN_URI' in os.environ: - # On ShiningPanda, we need a username and password to connect. They are - # passed in a mongodb:// URI. - conn_kwargs['host'] = os.environ['DBA_MONGODB_ADMIN_URI'] -if 'DB_PORT' in os.environ: - conn_kwargs['port'] = int(os.environ['DB_PORT']) - -try: - c = Connection(**conn_kwargs) -except Exception: - c=None - -class TestMongoBackend(test_db.TaskDBTest, TestCase): - """MongoDB backend tests""" - - def create_db(self): - try: - return MongoDB(database='iptestdb', _connection=c) - except Exception: - raise SkipTest("Couldn't connect to mongodb") - -def teardown(self): - if c is not None: - c.drop_database('iptestdb') diff --git a/ipython_parallel/tests/test_view.py b/ipython_parallel/tests/test_view.py deleted file mode 100644 index 27e0882..0000000 --- a/ipython_parallel/tests/test_view.py +++ /dev/null @@ -1,843 +0,0 @@ -# -*- coding: utf-8 -*- -"""test View objects""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import base64 -import sys -import platform -import time -from collections import namedtuple -from tempfile import NamedTemporaryFile - -import zmq -from nose.plugins.attrib import attr - -from IPython.testing import decorators as dec -from IPython.utils.io import capture_output -from IPython.utils.py3compat import unicode_type - -from IPython import parallel as pmod -from ipython_parallel import error -from ipython_parallel import AsyncResult, AsyncHubResult, AsyncMapResult -from ipython_parallel.util import interactive - -from ipython_parallel.tests import add_engines - -from .clienttest import ClusterTestCase, crash, wait, skip_without - -def setup(): - add_engines(3, total=True) - -point = namedtuple("point", "x y") - -class TestView(ClusterTestCase): - - def setUp(self): - # On Win XP, wait for resource cleanup, else parallel test group fails - if platform.system() == "Windows" and platform.win32_ver()[0] == "XP": - # 1 sec fails. 1.5 sec seems ok. Using 2 sec for margin of safety - time.sleep(2) - super(TestView, self).setUp() - - @attr('crash') - def test_z_crash_mux(self): - """test graceful handling of engine death (direct)""" - # self.add_engines(1) - eid = self.client.ids[-1] - ar = self.client[eid].apply_async(crash) - self.assertRaisesRemote(error.EngineError, ar.get, 10) - eid = ar.engine_id - tic = time.time() - while eid in self.client.ids and time.time()-tic < 5: - time.sleep(.01) - self.client.spin() - self.assertFalse(eid in self.client.ids, "Engine should have died") - - def test_push_pull(self): - """test pushing and pulling""" - data = dict(a=10, b=1.05, c=list(range(10)), d={'e':(1,2),'f':'hi'}) - t = self.client.ids[-1] - v = self.client[t] - push = v.push - pull = v.pull - v.block=True - nengines = len(self.client) - push({'data':data}) - d = pull('data') - self.assertEqual(d, data) - self.client[:].push({'data':data}) - d = self.client[:].pull('data', block=True) - self.assertEqual(d, nengines*[data]) - ar = push({'data':data}, block=False) - self.assertTrue(isinstance(ar, AsyncResult)) - r = ar.get() - ar = self.client[:].pull('data', block=False) - self.assertTrue(isinstance(ar, AsyncResult)) - r = ar.get() - self.assertEqual(r, nengines*[data]) - self.client[:].push(dict(a=10,b=20)) - r = self.client[:].pull(('a','b'), block=True) - self.assertEqual(r, nengines*[[10,20]]) - - def test_push_pull_function(self): - "test pushing and pulling functions" - def testf(x): - return 2.0*x - - t = self.client.ids[-1] - v = self.client[t] - v.block=True - push = v.push - pull = v.pull - execute = v.execute - push({'testf':testf}) - r = pull('testf') - self.assertEqual(r(1.0), testf(1.0)) - execute('r = testf(10)') - r = pull('r') - self.assertEqual(r, testf(10)) - ar = self.client[:].push({'testf':testf}, block=False) - ar.get() - ar = self.client[:].pull('testf', block=False) - rlist = ar.get() - for r in rlist: - self.assertEqual(r(1.0), testf(1.0)) - execute("def g(x): return x*x") - r = pull(('testf','g')) - self.assertEqual((r[0](10),r[1](10)), (testf(10), 100)) - - def test_push_function_globals(self): - """test that pushed functions have access to globals""" - @interactive - def geta(): - return a - # self.add_engines(1) - v = self.client[-1] - v.block=True - v['f'] = geta - self.assertRaisesRemote(NameError, v.execute, 'b=f()') - v.execute('a=5') - v.execute('b=f()') - self.assertEqual(v['b'], 5) - - def test_push_function_defaults(self): - """test that pushed functions preserve default args""" - def echo(a=10): - return a - v = self.client[-1] - v.block=True - v['f'] = echo - v.execute('b=f()') - self.assertEqual(v['b'], 10) - - def test_get_result(self): - """test getting results from the Hub.""" - c = pmod.Client(profile='iptest') - # self.add_engines(1) - t = c.ids[-1] - v = c[t] - v2 = self.client[t] - ar = v.apply_async(wait, 1) - # give the monitor time to notice the message - time.sleep(.25) - ahr = v2.get_result(ar.msg_ids[0], owner=False) - self.assertIsInstance(ahr, AsyncHubResult) - self.assertEqual(ahr.get(), ar.get()) - ar2 = v2.get_result(ar.msg_ids[0]) - self.assertNotIsInstance(ar2, AsyncHubResult) - self.assertEqual(ahr.get(), ar2.get()) - c.spin() - c.close() - - def test_run_newline(self): - """test that run appends newline to files""" - with NamedTemporaryFile('w', delete=False) as f: - f.write("""def g(): - return 5 - """) - v = self.client[-1] - v.run(f.name, block=True) - self.assertEqual(v.apply_sync(lambda f: f(), pmod.Reference('g')), 5) - - def test_apply_tracked(self): - """test tracking for apply""" - # self.add_engines(1) - t = self.client.ids[-1] - v = self.client[t] - v.block=False - def echo(n=1024*1024, **kwargs): - with v.temp_flags(**kwargs): - return v.apply(lambda x: x, 'x'*n) - ar = echo(1, track=False) - self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker)) - self.assertTrue(ar.sent) - ar = echo(track=True) - self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker)) - self.assertEqual(ar.sent, ar._tracker.done) - ar._tracker.wait() - self.assertTrue(ar.sent) - - def test_push_tracked(self): - t = self.client.ids[-1] - ns = dict(x='x'*1024*1024) - v = self.client[t] - ar = v.push(ns, block=False, track=False) - self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker)) - self.assertTrue(ar.sent) - - ar = v.push(ns, block=False, track=True) - self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker)) - ar._tracker.wait() - self.assertEqual(ar.sent, ar._tracker.done) - self.assertTrue(ar.sent) - ar.get() - - def test_scatter_tracked(self): - t = self.client.ids - x='x'*1024*1024 - ar = self.client[t].scatter('x', x, block=False, track=False) - self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker)) - self.assertTrue(ar.sent) - - ar = self.client[t].scatter('x', x, block=False, track=True) - self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker)) - self.assertEqual(ar.sent, ar._tracker.done) - ar._tracker.wait() - self.assertTrue(ar.sent) - ar.get() - - def test_remote_reference(self): - v = self.client[-1] - v['a'] = 123 - ra = pmod.Reference('a') - b = v.apply_sync(lambda x: x, ra) - self.assertEqual(b, 123) - - - def test_scatter_gather(self): - view = self.client[:] - seq1 = list(range(16)) - view.scatter('a', seq1) - seq2 = view.gather('a', block=True) - self.assertEqual(seq2, seq1) - self.assertRaisesRemote(NameError, view.gather, 'asdf', block=True) - - @skip_without('numpy') - def test_scatter_gather_numpy(self): - import numpy - from numpy.testing.utils import assert_array_equal - view = self.client[:] - a = numpy.arange(64) - view.scatter('a', a, block=True) - b = view.gather('a', block=True) - assert_array_equal(b, a) - - def test_scatter_gather_lazy(self): - """scatter/gather with targets='all'""" - view = self.client.direct_view(targets='all') - x = list(range(64)) - view.scatter('x', x) - gathered = view.gather('x', block=True) - self.assertEqual(gathered, x) - - - @dec.known_failure_py3 - @skip_without('numpy') - def test_push_numpy_nocopy(self): - import numpy - view = self.client[:] - a = numpy.arange(64) - view['A'] = a - @interactive - def check_writeable(x): - return x.flags.writeable - - for flag in view.apply_sync(check_writeable, pmod.Reference('A')): - self.assertFalse(flag, "array is writeable, push shouldn't have pickled it") - - view.push(dict(B=a)) - for flag in view.apply_sync(check_writeable, pmod.Reference('B')): - self.assertFalse(flag, "array is writeable, push shouldn't have pickled it") - - @skip_without('numpy') - def test_apply_numpy(self): - """view.apply(f, ndarray)""" - import numpy - from numpy.testing.utils import assert_array_equal - - A = numpy.random.random((100,100)) - view = self.client[-1] - for dt in [ 'int32', 'uint8', 'float32', 'float64' ]: - B = A.astype(dt) - C = view.apply_sync(lambda x:x, B) - assert_array_equal(B,C) - - @skip_without('numpy') - def test_apply_numpy_object_dtype(self): - """view.apply(f, ndarray) with dtype=object""" - import numpy - from numpy.testing.utils import assert_array_equal - view = self.client[-1] - - A = numpy.array([dict(a=5)]) - B = view.apply_sync(lambda x:x, A) - assert_array_equal(A,B) - - A = numpy.array([(0, dict(b=10))], dtype=[('i', int), ('o', object)]) - B = view.apply_sync(lambda x:x, A) - assert_array_equal(A,B) - - @skip_without('numpy') - def test_push_pull_recarray(self): - """push/pull recarrays""" - import numpy - from numpy.testing.utils import assert_array_equal - - view = self.client[-1] - - R = numpy.array([ - (1, 'hi', 0.), - (2**30, 'there', 2.5), - (-99999, 'world', -12345.6789), - ], [('n', int), ('s', '|S10'), ('f', float)]) - - view['RR'] = R - R2 = view['RR'] - - r_dtype, r_shape = view.apply_sync(interactive(lambda : (RR.dtype, RR.shape))) - self.assertEqual(r_dtype, R.dtype) - self.assertEqual(r_shape, R.shape) - self.assertEqual(R2.dtype, R.dtype) - self.assertEqual(R2.shape, R.shape) - assert_array_equal(R2, R) - - @skip_without('pandas') - def test_push_pull_timeseries(self): - """push/pull pandas.TimeSeries""" - import pandas - - ts = pandas.TimeSeries(list(range(10))) - - view = self.client[-1] - - view.push(dict(ts=ts), block=True) - rts = view['ts'] - - self.assertEqual(type(rts), type(ts)) - self.assertTrue((ts == rts).all()) - - def test_map(self): - view = self.client[:] - def f(x): - return x**2 - data = list(range(16)) - r = view.map_sync(f, data) - self.assertEqual(r, list(map(f, data))) - - def test_map_empty_sequence(self): - view = self.client[:] - r = view.map_sync(lambda x: x, []) - self.assertEqual(r, []) - - def test_map_iterable(self): - """test map on iterables (direct)""" - view = self.client[:] - # 101 is prime, so it won't be evenly distributed - arr = range(101) - # ensure it will be an iterator, even in Python 3 - it = iter(arr) - r = view.map_sync(lambda x: x, it) - self.assertEqual(r, list(arr)) - - @skip_without('numpy') - def test_map_numpy(self): - """test map on numpy arrays (direct)""" - import numpy - from numpy.testing.utils import assert_array_equal - - view = self.client[:] - # 101 is prime, so it won't be evenly distributed - arr = numpy.arange(101) - r = view.map_sync(lambda x: x, arr) - assert_array_equal(r, arr) - - def test_scatter_gather_nonblocking(self): - data = list(range(16)) - view = self.client[:] - view.scatter('a', data, block=False) - ar = view.gather('a', block=False) - self.assertEqual(ar.get(), data) - - @skip_without('numpy') - def test_scatter_gather_numpy_nonblocking(self): - import numpy - from numpy.testing.utils import assert_array_equal - a = numpy.arange(64) - view = self.client[:] - ar = view.scatter('a', a, block=False) - self.assertTrue(isinstance(ar, AsyncResult)) - amr = view.gather('a', block=False) - self.assertTrue(isinstance(amr, AsyncMapResult)) - assert_array_equal(amr.get(), a) - - def test_execute(self): - view = self.client[:] - # self.client.debug=True - execute = view.execute - ar = execute('c=30', block=False) - self.assertTrue(isinstance(ar, AsyncResult)) - ar = execute('d=[0,1,2]', block=False) - self.client.wait(ar, 1) - self.assertEqual(len(ar.get()), len(self.client)) - for c in view['c']: - self.assertEqual(c, 30) - - def test_abort(self): - view = self.client[-1] - ar = view.execute('import time; time.sleep(1)', block=False) - ar2 = view.apply_async(lambda : 2) - ar3 = view.apply_async(lambda : 3) - view.abort(ar2) - view.abort(ar3.msg_ids) - self.assertRaises(error.TaskAborted, ar2.get) - self.assertRaises(error.TaskAborted, ar3.get) - - def test_abort_all(self): - """view.abort() aborts all outstanding tasks""" - view = self.client[-1] - ars = [ view.apply_async(time.sleep, 0.25) for i in range(10) ] - view.abort() - view.wait(timeout=5) - for ar in ars[5:]: - self.assertRaises(error.TaskAborted, ar.get) - - def test_temp_flags(self): - view = self.client[-1] - view.block=True - with view.temp_flags(block=False): - self.assertFalse(view.block) - self.assertTrue(view.block) - - @dec.known_failure_py3 - def test_importer(self): - view = self.client[-1] - view.clear(block=True) - with view.importer: - import re - - @interactive - def findall(pat, s): - # this globals() step isn't necessary in real code - # only to prevent a closure in the test - re = globals()['re'] - return re.findall(pat, s) - - self.assertEqual(view.apply_sync(findall, '\w+', 'hello world'), 'hello world'.split()) - - def test_unicode_execute(self): - """test executing unicode strings""" - v = self.client[-1] - v.block=True - if sys.version_info[0] >= 3: - code="a='é'" - else: - code=u"a=u'é'" - v.execute(code) - self.assertEqual(v['a'], u'é') - - def test_unicode_apply_result(self): - """test unicode apply results""" - v = self.client[-1] - r = v.apply_sync(lambda : u'é') - self.assertEqual(r, u'é') - - def test_unicode_apply_arg(self): - """test passing unicode arguments to apply""" - v = self.client[-1] - - @interactive - def check_unicode(a, check): - assert not isinstance(a, bytes), "%r is bytes, not unicode"%a - assert isinstance(check, bytes), "%r is not bytes"%check - assert a.encode('utf8') == check, "%s != %s"%(a,check) - - for s in [ u'é', u'ßø®∫',u'asdf' ]: - try: - v.apply_sync(check_unicode, s, s.encode('utf8')) - except error.RemoteError as e: - if e.ename == 'AssertionError': - self.fail(e.evalue) - else: - raise e - - def test_map_reference(self): - """view.map(, *seqs) should work""" - v = self.client[:] - v.scatter('n', self.client.ids, flatten=True) - v.execute("f = lambda x,y: x*y") - rf = pmod.Reference('f') - nlist = list(range(10)) - mlist = nlist[::-1] - expected = [ m*n for m,n in zip(mlist, nlist) ] - result = v.map_sync(rf, mlist, nlist) - self.assertEqual(result, expected) - - def test_apply_reference(self): - """view.apply(, *args) should work""" - v = self.client[:] - v.scatter('n', self.client.ids, flatten=True) - v.execute("f = lambda x: n*x") - rf = pmod.Reference('f') - result = v.apply_sync(rf, 5) - expected = [ 5*id for id in self.client.ids ] - self.assertEqual(result, expected) - - def test_eval_reference(self): - v = self.client[self.client.ids[0]] - v['g'] = list(range(5)) - rg = pmod.Reference('g[0]') - echo = lambda x:x - self.assertEqual(v.apply_sync(echo, rg), 0) - - def test_reference_nameerror(self): - v = self.client[self.client.ids[0]] - r = pmod.Reference('elvis_has_left') - echo = lambda x:x - self.assertRaisesRemote(NameError, v.apply_sync, echo, r) - - def test_single_engine_map(self): - e0 = self.client[self.client.ids[0]] - r = list(range(5)) - check = [ -1*i for i in r ] - result = e0.map_sync(lambda x: -1*x, r) - self.assertEqual(result, check) - - def test_len(self): - """len(view) makes sense""" - e0 = self.client[self.client.ids[0]] - self.assertEqual(len(e0), 1) - v = self.client[:] - self.assertEqual(len(v), len(self.client.ids)) - v = self.client.direct_view('all') - self.assertEqual(len(v), len(self.client.ids)) - v = self.client[:2] - self.assertEqual(len(v), 2) - v = self.client[:1] - self.assertEqual(len(v), 1) - v = self.client.load_balanced_view() - self.assertEqual(len(v), len(self.client.ids)) - - - # begin execute tests - - def test_execute_reply(self): - e0 = self.client[self.client.ids[0]] - e0.block = True - ar = e0.execute("5", silent=False) - er = ar.get() - self.assertEqual(str(er), "" % er.execution_count) - self.assertEqual(er.execute_result['data']['text/plain'], '5') - - def test_execute_reply_rich(self): - e0 = self.client[self.client.ids[0]] - e0.block = True - e0.execute("from IPython.display import Image, HTML") - ar = e0.execute("Image(data=b'garbage', format='png', width=10)", silent=False) - er = ar.get() - b64data = base64.encodestring(b'garbage').decode('ascii') - self.assertEqual(er._repr_png_(), (b64data, dict(width=10))) - ar = e0.execute("HTML('bold')", silent=False) - er = ar.get() - self.assertEqual(er._repr_html_(), "bold") - - def test_execute_reply_stdout(self): - e0 = self.client[self.client.ids[0]] - e0.block = True - ar = e0.execute("print (5)", silent=False) - er = ar.get() - self.assertEqual(er.stdout.strip(), '5') - - def test_execute_result(self): - """execute triggers execute_result with silent=False""" - view = self.client[:] - ar = view.execute("5", silent=False, block=True) - - expected = [{'text/plain' : '5'}] * len(view) - mimes = [ out['data'] for out in ar.execute_result ] - self.assertEqual(mimes, expected) - - def test_execute_silent(self): - """execute does not trigger execute_result with silent=True""" - view = self.client[:] - ar = view.execute("5", block=True) - expected = [None] * len(view) - self.assertEqual(ar.execute_result, expected) - - def test_execute_magic(self): - """execute accepts IPython commands""" - view = self.client[:] - view.execute("a = 5") - ar = view.execute("%whos", block=True) - # this will raise, if that failed - ar.get(5) - for stdout in ar.stdout: - lines = stdout.splitlines() - self.assertEqual(lines[0].split(), ['Variable', 'Type', 'Data/Info']) - found = False - for line in lines[2:]: - split = line.split() - if split == ['a', 'int', '5']: - found = True - break - self.assertTrue(found, "whos output wrong: %s" % stdout) - - def test_execute_displaypub(self): - """execute tracks display_pub output""" - view = self.client[:] - view.execute("from IPython.core.display import *") - ar = view.execute("[ display(i) for i in range(5) ]", block=True) - - expected = [ {u'text/plain' : unicode_type(j)} for j in range(5) ] - for outputs in ar.outputs: - mimes = [ out['data'] for out in outputs ] - self.assertEqual(mimes, expected) - - def test_apply_displaypub(self): - """apply tracks display_pub output""" - view = self.client[:] - view.execute("from IPython.core.display import *") - - @interactive - def publish(): - [ display(i) for i in range(5) ] - - ar = view.apply_async(publish) - ar.get(5) - expected = [ {u'text/plain' : unicode_type(j)} for j in range(5) ] - for outputs in ar.outputs: - mimes = [ out['data'] for out in outputs ] - self.assertEqual(mimes, expected) - - def test_execute_raises(self): - """exceptions in execute requests raise appropriately""" - view = self.client[-1] - ar = view.execute("1/0") - self.assertRaisesRemote(ZeroDivisionError, ar.get, 2) - - def test_remoteerror_render_exception(self): - """RemoteErrors get nice tracebacks""" - view = self.client[-1] - ar = view.execute("1/0") - ip = get_ipython() - ip.user_ns['ar'] = ar - with capture_output() as io: - ip.run_cell("ar.get(2)") - - self.assertTrue('ZeroDivisionError' in io.stdout, io.stdout) - - def test_compositeerror_render_exception(self): - """CompositeErrors get nice tracebacks""" - view = self.client[:] - ar = view.execute("1/0") - ip = get_ipython() - ip.user_ns['ar'] = ar - - with capture_output() as io: - ip.run_cell("ar.get(2)") - - count = min(error.CompositeError.tb_limit, len(view)) - - self.assertEqual(io.stdout.count('ZeroDivisionError'), count * 2, io.stdout) - self.assertEqual(io.stdout.count('by zero'), count, io.stdout) - self.assertEqual(io.stdout.count(':execute'), count, io.stdout) - - def test_compositeerror_truncate(self): - """Truncate CompositeErrors with many exceptions""" - view = self.client[:] - msg_ids = [] - for i in range(10): - ar = view.execute("1/0") - msg_ids.extend(ar.msg_ids) - - ar = self.client.get_result(msg_ids) - try: - ar.get() - except error.CompositeError as _e: - e = _e - else: - self.fail("Should have raised CompositeError") - - lines = e.render_traceback() - with capture_output() as io: - e.print_traceback() - - self.assertTrue("more exceptions" in lines[-1]) - count = e.tb_limit - - self.assertEqual(io.stdout.count('ZeroDivisionError'), 2 * count, io.stdout) - self.assertEqual(io.stdout.count('by zero'), count, io.stdout) - self.assertEqual(io.stdout.count(':execute'), count, io.stdout) - - @dec.skipif_not_matplotlib - def test_magic_pylab(self): - """%pylab works on engines""" - view = self.client[-1] - ar = view.execute("%pylab inline") - # at least check if this raised: - reply = ar.get(5) - # include imports, in case user config - ar = view.execute("plot(rand(100))", silent=False) - reply = ar.get(5) - self.assertEqual(len(reply.outputs), 1) - output = reply.outputs[0] - self.assertTrue("data" in output) - data = output['data'] - self.assertTrue("image/png" in data) - - def test_func_default_func(self): - """interactively defined function as apply func default""" - def foo(): - return 'foo' - - def bar(f=foo): - return f() - - view = self.client[-1] - ar = view.apply_async(bar) - r = ar.get(10) - self.assertEqual(r, 'foo') - def test_data_pub_single(self): - view = self.client[-1] - ar = view.execute('\n'.join([ - 'from IPython.kernel.zmq.datapub import publish_data', - 'for i in range(5):', - ' publish_data(dict(i=i))' - ]), block=False) - self.assertTrue(isinstance(ar.data, dict)) - ar.get(5) - self.assertEqual(ar.data, dict(i=4)) - - def test_data_pub(self): - view = self.client[:] - ar = view.execute('\n'.join([ - 'from IPython.kernel.zmq.datapub import publish_data', - 'for i in range(5):', - ' publish_data(dict(i=i))' - ]), block=False) - self.assertTrue(all(isinstance(d, dict) for d in ar.data)) - ar.get(5) - self.assertEqual(ar.data, [dict(i=4)] * len(ar)) - - def test_can_list_arg(self): - """args in lists are canned""" - view = self.client[-1] - view['a'] = 128 - rA = pmod.Reference('a') - ar = view.apply_async(lambda x: x, [rA]) - r = ar.get(5) - self.assertEqual(r, [128]) - - def test_can_dict_arg(self): - """args in dicts are canned""" - view = self.client[-1] - view['a'] = 128 - rA = pmod.Reference('a') - ar = view.apply_async(lambda x: x, dict(foo=rA)) - r = ar.get(5) - self.assertEqual(r, dict(foo=128)) - - def test_can_list_kwarg(self): - """kwargs in lists are canned""" - view = self.client[-1] - view['a'] = 128 - rA = pmod.Reference('a') - ar = view.apply_async(lambda x=5: x, x=[rA]) - r = ar.get(5) - self.assertEqual(r, [128]) - - def test_can_dict_kwarg(self): - """kwargs in dicts are canned""" - view = self.client[-1] - view['a'] = 128 - rA = pmod.Reference('a') - ar = view.apply_async(lambda x=5: x, dict(foo=rA)) - r = ar.get(5) - self.assertEqual(r, dict(foo=128)) - - def test_map_ref(self): - """view.map works with references""" - view = self.client[:] - ranks = sorted(self.client.ids) - view.scatter('rank', ranks, flatten=True) - rrank = pmod.Reference('rank') - - amr = view.map_async(lambda x: x*2, [rrank] * len(view)) - drank = amr.get(5) - self.assertEqual(drank, [ r*2 for r in ranks ]) - - def test_nested_getitem_setitem(self): - """get and set with view['a.b']""" - view = self.client[-1] - view.execute('\n'.join([ - 'class A(object): pass', - 'a = A()', - 'a.b = 128', - ]), block=True) - ra = pmod.Reference('a') - - r = view.apply_sync(lambda x: x.b, ra) - self.assertEqual(r, 128) - self.assertEqual(view['a.b'], 128) - - view['a.b'] = 0 - - r = view.apply_sync(lambda x: x.b, ra) - self.assertEqual(r, 0) - self.assertEqual(view['a.b'], 0) - - def test_return_namedtuple(self): - def namedtuplify(x, y): - from ipython_parallel.tests.test_view import point - return point(x, y) - - view = self.client[-1] - p = view.apply_sync(namedtuplify, 1, 2) - self.assertEqual(p.x, 1) - self.assertEqual(p.y, 2) - - def test_apply_namedtuple(self): - def echoxy(p): - return p.y, p.x - - view = self.client[-1] - tup = view.apply_sync(echoxy, point(1, 2)) - self.assertEqual(tup, (2,1)) - - def test_sync_imports(self): - view = self.client[-1] - with capture_output() as io: - with view.sync_imports(): - import IPython - self.assertIn("IPython", io.stdout) - - @interactive - def find_ipython(): - return 'IPython' in globals() - - assert view.apply_sync(find_ipython) - - def test_sync_imports_quiet(self): - view = self.client[-1] - with capture_output() as io: - with view.sync_imports(quiet=True): - import IPython - self.assertEqual(io.stdout, '') - - @interactive - def find_ipython(): - return 'IPython' in globals() - - assert view.apply_sync(find_ipython) - diff --git a/ipython_parallel/util.py b/ipython_parallel/util.py deleted file mode 100644 index 3596edf..0000000 --- a/ipython_parallel/util.py +++ /dev/null @@ -1,389 +0,0 @@ -"""Some generic utilities for dealing with classes, urls, and serialization.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import logging -import os -import re -import stat -import socket -import sys -import warnings -from signal import signal, SIGINT, SIGABRT, SIGTERM -try: - from signal import SIGKILL -except ImportError: - SIGKILL=None -from types import FunctionType - -try: - import cPickle - pickle = cPickle -except: - cPickle = None - import pickle - -import zmq -from zmq.log import handlers - -from IPython.utils.log import get_logger -from decorator import decorator - -from IPython.config.application import Application -from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips -from IPython.utils.py3compat import string_types, iteritems, itervalues -from IPython.kernel.zmq.log import EnginePUBHandler - - -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - -class Namespace(dict): - """Subclass of dict for attribute access to keys.""" - - def __getattr__(self, key): - """getattr aliased to getitem""" - if key in self: - return self[key] - else: - raise NameError(key) - - def __setattr__(self, key, value): - """setattr aliased to setitem, with strict""" - if hasattr(dict, key): - raise KeyError("Cannot override dict keys %r"%key) - self[key] = value - - -class ReverseDict(dict): - """simple double-keyed subset of dict methods.""" - - def __init__(self, *args, **kwargs): - dict.__init__(self, *args, **kwargs) - self._reverse = dict() - for key, value in iteritems(self): - self._reverse[value] = key - - def __getitem__(self, key): - try: - return dict.__getitem__(self, key) - except KeyError: - return self._reverse[key] - - def __setitem__(self, key, value): - if key in self._reverse: - raise KeyError("Can't have key %r on both sides!"%key) - dict.__setitem__(self, key, value) - self._reverse[value] = key - - def pop(self, key): - value = dict.pop(self, key) - self._reverse.pop(value) - return value - - def get(self, key, default=None): - try: - return self[key] - except KeyError: - return default - -#----------------------------------------------------------------------------- -# Functions -#----------------------------------------------------------------------------- - -@decorator -def log_errors(f, self, *args, **kwargs): - """decorator to log unhandled exceptions raised in a method. - - For use wrapping on_recv callbacks, so that exceptions - do not cause the stream to be closed. - """ - try: - return f(self, *args, **kwargs) - except Exception: - self.log.error("Uncaught exception in %r" % f, exc_info=True) - - -def is_url(url): - """boolean check for whether a string is a zmq url""" - if '://' not in url: - return False - proto, addr = url.split('://', 1) - if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']: - return False - return True - -def validate_url(url): - """validate a url for zeromq""" - if not isinstance(url, string_types): - raise TypeError("url must be a string, not %r"%type(url)) - url = url.lower() - - proto_addr = url.split('://') - assert len(proto_addr) == 2, 'Invalid url: %r'%url - proto, addr = proto_addr - assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto - - # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391 - # author: Remi Sabourin - pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$') - - if proto == 'tcp': - lis = addr.split(':') - assert len(lis) == 2, 'Invalid url: %r'%url - addr,s_port = lis - try: - port = int(s_port) - except ValueError: - raise AssertionError("Invalid port %r in url: %r"%(port, url)) - - assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url - - else: - # only validate tcp urls currently - pass - - return True - - -def validate_url_container(container): - """validate a potentially nested collection of urls.""" - if isinstance(container, string_types): - url = container - return validate_url(url) - elif isinstance(container, dict): - container = itervalues(container) - - for element in container: - validate_url_container(element) - - -def split_url(url): - """split a zmq url (tcp://ip:port) into ('tcp','ip','port').""" - proto_addr = url.split('://') - assert len(proto_addr) == 2, 'Invalid url: %r'%url - proto, addr = proto_addr - lis = addr.split(':') - assert len(lis) == 2, 'Invalid url: %r'%url - addr,s_port = lis - return proto,addr,s_port - - -def disambiguate_ip_address(ip, location=None): - """turn multi-ip interfaces '0.0.0.0' and '*' into a connectable address - - Explicit IP addresses are returned unmodified. - - Parameters - ---------- - - ip : IP address - An IP address, or the special values 0.0.0.0, or * - location: IP address, optional - A public IP of the target machine. - If location is an IP of the current machine, - localhost will be returned, - otherwise location will be returned. - """ - if ip in {'0.0.0.0', '*'}: - if not location: - # unspecified location, localhost is the only choice - ip = localhost() - elif is_public_ip(location): - # location is a public IP on this machine, use localhost - ip = localhost() - elif not public_ips(): - # this machine's public IPs cannot be determined, - # assume `location` is not this machine - warnings.warn("IPython could not determine public IPs", RuntimeWarning) - ip = location - else: - # location is not this machine, do not use loopback - ip = location - return ip - - -def disambiguate_url(url, location=None): - """turn multi-ip interfaces '0.0.0.0' and '*' into connectable - ones, based on the location (default interpretation is localhost). - - This is for zeromq urls, such as ``tcp://*:10101``. - """ - try: - proto,ip,port = split_url(url) - except AssertionError: - # probably not tcp url; could be ipc, etc. - return url - - ip = disambiguate_ip_address(ip,location) - - return "%s://%s:%s"%(proto,ip,port) - - -#-------------------------------------------------------------------------- -# helpers for implementing old MEC API via view.apply -#-------------------------------------------------------------------------- - -def interactive(f): - """decorator for making functions appear as interactively defined. - This results in the function being linked to the user_ns as globals() - instead of the module globals(). - """ - - # build new FunctionType, so it can have the right globals - # interactive functions never have closures, that's kind of the point - if isinstance(f, FunctionType): - mainmod = __import__('__main__') - f = FunctionType(f.__code__, mainmod.__dict__, - f.__name__, f.__defaults__, - ) - # associate with __main__ for uncanning - f.__module__ = '__main__' - return f - -@interactive -def _push(**ns): - """helper method for implementing `client.push` via `client.apply`""" - user_ns = globals() - tmp = '_IP_PUSH_TMP_' - while tmp in user_ns: - tmp = tmp + '_' - try: - for name, value in ns.items(): - user_ns[tmp] = value - exec("%s = %s" % (name, tmp), user_ns) - finally: - user_ns.pop(tmp, None) - -@interactive -def _pull(keys): - """helper method for implementing `client.pull` via `client.apply`""" - if isinstance(keys, (list,tuple, set)): - return [eval(key, globals()) for key in keys] - else: - return eval(keys, globals()) - -@interactive -def _execute(code): - """helper method for implementing `client.execute` via `client.apply`""" - exec(code, globals()) - -#-------------------------------------------------------------------------- -# extra process management utilities -#-------------------------------------------------------------------------- - -_random_ports = set() - -def select_random_ports(n): - """Selects and return n random ports that are available.""" - ports = [] - for i in range(n): - sock = socket.socket() - sock.bind(('', 0)) - while sock.getsockname()[1] in _random_ports: - sock.close() - sock = socket.socket() - sock.bind(('', 0)) - ports.append(sock) - for i, sock in enumerate(ports): - port = sock.getsockname()[1] - sock.close() - ports[i] = port - _random_ports.add(port) - return ports - -def signal_children(children): - """Relay interupt/term signals to children, for more solid process cleanup.""" - def terminate_children(sig, frame): - log = get_logger() - log.critical("Got signal %i, terminating children..."%sig) - for child in children: - child.terminate() - - sys.exit(sig != SIGINT) - # sys.exit(sig) - for sig in (SIGINT, SIGABRT, SIGTERM): - signal(sig, terminate_children) - -def generate_exec_key(keyfile): - import uuid - newkey = str(uuid.uuid4()) - with open(keyfile, 'w') as f: - # f.write('ipython-key ') - f.write(newkey+'\n') - # set user-only RW permissions (0600) - # this will have no effect on Windows - os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR) - - -def integer_loglevel(loglevel): - try: - loglevel = int(loglevel) - except ValueError: - if isinstance(loglevel, str): - loglevel = getattr(logging, loglevel) - return loglevel - -def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG): - logger = logging.getLogger(logname) - if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]): - # don't add a second PUBHandler - return - loglevel = integer_loglevel(loglevel) - lsock = context.socket(zmq.PUB) - lsock.connect(iface) - handler = handlers.PUBHandler(lsock) - handler.setLevel(loglevel) - handler.root_topic = root - logger.addHandler(handler) - logger.setLevel(loglevel) - -def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG): - logger = logging.getLogger() - if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]): - # don't add a second PUBHandler - return - loglevel = integer_loglevel(loglevel) - lsock = context.socket(zmq.PUB) - lsock.connect(iface) - handler = EnginePUBHandler(engine, lsock) - handler.setLevel(loglevel) - logger.addHandler(handler) - logger.setLevel(loglevel) - return logger - -def local_logger(logname, loglevel=logging.DEBUG): - loglevel = integer_loglevel(loglevel) - logger = logging.getLogger(logname) - if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]): - # don't add a second StreamHandler - return - handler = logging.StreamHandler() - handler.setLevel(loglevel) - formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s", - datefmt="%Y-%m-%d %H:%M:%S") - handler.setFormatter(formatter) - - logger.addHandler(handler) - logger.setLevel(loglevel) - return logger - -def set_hwm(sock, hwm=0): - """set zmq High Water Mark on a socket - - in a way that always works for various pyzmq / libzmq versions. - """ - import zmq - - for key in ('HWM', 'SNDHWM', 'RCVHWM'): - opt = getattr(zmq, key, None) - if opt is None: - continue - try: - sock.setsockopt(opt, hwm) - except zmq.ZMQError: - pass - - diff --git a/requirements.txt b/requirements.txt index 1967c41..8ed0fa7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ -e git+https://github.com/jupyter/jupyter_nbformat.git#egg=jupyter_nbformat -e git+https://github.com/jupyter/jupyter_client.git#egg=jupyter_client -e git+https://github.com/ipython/ipython_kernel.git#egg=ipython_kernel +-e git+https://github.com/ipython/ipython_parallel.git#egg=ipython_parallel diff --git a/scripts/ipython_win_post_install.py b/scripts/ipython_win_post_install.py index bd7e3b1..a9af1b6 100755 --- a/scripts/ipython_win_post_install.py +++ b/scripts/ipython_win_post_install.py @@ -80,9 +80,6 @@ def install(): programs = [ 'ipython', 'iptest', - 'ipcontroller', - 'ipengine', - 'ipcluster', ] programs = [suffix(p) for p in programs] scripts = pjoin(sys.prefix, 'scripts') @@ -105,10 +102,6 @@ def install(): arguments(scripts, 'ipython'), iconpath) mkshortcut(python, 'IPython (pylab mode)', ip_start_menu, arguments(scripts, 'ipython', '--pylab'), iconpath) - mkshortcut(python, 'IPython Controller', ip_start_menu, - arguments(scripts, 'ipcontroller'), iconpath) - mkshortcut(python, 'IPython Engine', ip_start_menu, - arguments(scripts, 'ipengine'), iconpath) mkshortcut(pythonw, 'IPython Qt Console', ip_start_menu, arguments(scripts, 'ipython', 'qtconsole'), iconpath) diff --git a/setup.py b/setup.py index c47a27d..c951b32 100755 --- a/setup.py +++ b/setup.py @@ -145,28 +145,9 @@ if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'): # List of things to be updated. Each entry is a triplet of args for # target_update() to_update = [ - # FIXME - Disabled for now: we need to redo an automatic way - # of generating the magic info inside the rst. - #('docs/magic.tex', - #['IPython/Magic.py'], - #"cd doc && ./update_magic.sh" ), - - ('docs/man/ipcluster.1.gz', - ['docs/man/ipcluster.1'], - 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'), - - ('docs/man/ipcontroller.1.gz', - ['docs/man/ipcontroller.1'], - 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'), - - ('docs/man/ipengine.1.gz', - ['docs/man/ipengine.1'], - 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'), - ('docs/man/ipython.1.gz', ['docs/man/ipython.1'], 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'), - ] @@ -266,7 +247,7 @@ setuptools_extra_args = {} pyzmq = 'pyzmq>=13' extras_require = dict( - parallel = [pyzmq], + parallel = ['ipython_parallel'], qtconsole = [pyzmq, 'pygments'], doc = ['Sphinx>=1.1', 'numpydoc'], test = ['nose>=0.10.1', 'requests'], diff --git a/setupbase.py b/setupbase.py index e93181f..9517dfa 100644 --- a/setupbase.py +++ b/setupbase.py @@ -375,9 +375,6 @@ def find_entry_points(): """ ep = [ 'ipython%s = IPython:start_ipython', - 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance', - 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance', - 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance', 'iptest%s = IPython.testing.iptestcontroller:main', ] suffix = str(sys.version_info[0])