From 7afffe73df21e381af3e5d3f5ef58403bc9cf79f Mon Sep 17 00:00:00 2001 From: jlstevens Date: Fri, 31 Mar 2017 01:35:52 +0100 Subject: [PATCH] Removed outdated material from the DynamicMap tutorial --- doc/Tutorials/Dynamic_Map.ipynb | 580 +------------------------------- 1 file changed, 15 insertions(+), 565 deletions(-) diff --git a/doc/Tutorials/Dynamic_Map.ipynb b/doc/Tutorials/Dynamic_Map.ipynb index 9c248409e9..7f9702d436 100644 --- a/doc/Tutorials/Dynamic_Map.ipynb +++ b/doc/Tutorials/Dynamic_Map.ipynb @@ -720,559 +720,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This grid shows a range of frequencies `f` on the x axis, a range of the first phase variable `ph` on the `y` axis, and a range of different `ph2` phases as overlays within each location in the grid. As you can see, these techniques can help you visualize multidimensional parameter spaces compactly and conveniently.\n", - "\n", - "### Open mode " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "``DynamicMap`` also allows unconstrained exploration over unbounded dimensions in 'open' mode. There are two key differences between open mode and bounded mode:\n", - "\n", - "* Instead of a callable, the input to an open ``DynamicMap`` is a generator. Once created, the generator is only used via ``next()``.\n", - "* At least one of the declared key dimensions must have an unbounded range (i.e., with an upper or lower bound not specified).\n", - "* An open mode ``DynamicMap`` can run forever, or until a ``StopIteration`` exception is raised.\n", - "* Open mode ``DynamicMaps`` can be stateful, with an irreversible direction of time.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Infinite generators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our first example will be using an infinite generator which plots the histogram for a given number of random samples drawn from a Gaussian distribution:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def gaussian_histogram(samples, scale):\n", - " frequencies, edges = np.histogram([np.random.normal(scale=scale) \n", - " for i in range(samples)], 20)\n", - " return hv.Histogram(frequencies, edges).relabel('Gaussian distribution')\n", - "\n", - "gaussian_histogram(100,1) + gaussian_histogram(150,1.5) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Lets now use this in the following generator:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def gaussian_sampler(samples=10, delta=10, scale=1.0):\n", - " np.random.seed(1)\n", - " while True:\n", - " yield gaussian_histogram(samples, scale)\n", - " samples+=delta\n", - " \n", - "gaussian_sampler()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which allows us to define the following infinite ``DynamicMap``:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "dmap = hv.DynamicMap(gaussian_sampler(), kdims=['step'])\n", - "dmap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that step is shown as an integer. This is the default behavior and corresponds to the call count (i.e the number of times ``next()`` has been called on the generator. If we want to show the actual number of samples properly, we need our generator to return a (key, element) pair:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def gaussian_sampler_kv(samples=10, delta=10, scale=1.0):\n", - " np.random.seed(1)\n", - " while True:\n", - " yield (samples, gaussian_histogram(samples, scale))\n", - " samples+=delta\n", - " \n", - "hv.DynamicMap(gaussian_sampler_kv(), kdims=['samples'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that if you pause the ``DynamicMap``, you can scrub back to previous frames in the cache. In other words, you can view a limited history of elements already output by the generator, which does *not* re-execute the generator in any way (as it is indeed impossible to rewind generator state). If you have a stateful generator that, say, depends on the current wind speed in Scotland, this history may be misleading, in which case you can simply set the ``cache_size`` parameter to 1." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Multi-dimensional generators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In open mode, elements are naturally serialized by a linear sequence of ``next()`` calls, yet multiple key dimensions can still be defined:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def gaussian_sampler_2D(samples=10, scale=1.0, delta=10):\n", - " np.random.seed(1)\n", - " while True:\n", - " yield ((samples, scale), gaussian_histogram(samples, scale))\n", - " samples=(samples + delta) if scale==2 else samples\n", - " scale = 2 if scale == 1 else 1\n", - " \n", - "dmap = hv.DynamicMap(gaussian_sampler_2D(), kdims=['samples', 'scale'])\n", - "dmap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we bin the histogram for two different scale values. Above we can visualize this linear sequence of ``next()`` calls, but by casting this open map to a ``HoloMap``, we can obtain a multi-dimensional parameter space that we can freely explore:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "hv.HoloMap(dmap)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that if you ran this notebook using `Run All`, only a single frame will be available in the above cell, with no sliders, but if you ran it interactively and viewed a range of values in the previous cell, you'll have multiple sliders in this cell allowing you to explore whatever range of frames is in the cache from the previous cell.\n", - "\n", - "#### Finite generators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Open mode ``DynamicMaps`` are finite and terminate if ``StopIteration`` is raised. This example terminates when the means of two sets of gaussian samples fall within a certain distance of each other:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def sample_distributions(samples=10, delta=50, tol=0.04):\n", - " np.random.seed(42)\n", - " while True:\n", - " gauss1 = np.random.normal(size=samples)\n", - " gauss2 = np.random.normal(size=samples)\n", - " data = (['A']*samples + ['B']*samples, np.hstack([gauss1, gauss2]))\n", - " diff = abs(gauss1.mean() - gauss2.mean())\n", - " if abs(gauss1.mean() - gauss2.mean()) > tol:\n", - " yield ((samples, diff), hv.BoxWhisker(data, kdims=['Group'], vdims=['Value']))\n", - " else:\n", - " raise StopIteration\n", - " samples+=delta" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "dmap = hv.DynamicMap(sample_distributions(), kdims=['samples', '$\\delta$'])\n", - "dmap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now if you are familiar with generators in Python, you might be wondering what happens when a finite generator is exhausted. First we should mention that casting a ``DynamicMap`` to a list is always finite, because ``__iter__`` returns the cache instead of a potentially infinite generator:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "list(dmap) # The cache" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we know this ``DynamicMap`` is finite, we can make sure it is exhausted as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "while True:\n", - " try:\n", - " next(dmap) # Returns Image elements\n", - " except StopIteration:\n", - " print(\"The dynamic map is exhausted.\")\n", - " break" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's have a look at the dynamic map:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "dmap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we are given only the text-based representation, to indicate that the generator is exhausted. However, as the process of iteration has populated the cache, we can still view the output as a ``HoloMap`` using ``hv.HoloMap(dmap)`` as before." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Counter mode and temporal state " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Open mode is intended to use live data streams or ongoing simulations with HoloViews. The ``DynamicMap`` will generate live visualizations for as long as new data is requested. Although this works for simple cases, Python generators have problematic limitations that can be resolved using 'counter' mode.\n", - "\n", - "In this example, let's say we have a simulation or data recording where time increases in integer steps:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def time_gen(time=1):\n", - " while True:\n", - " yield time\n", - " time += 1\n", - " \n", - "time = time_gen()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's create two generators that return Images that are a function of the simulation time. Here, they have identical output except one of the outputs includes additive noise:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "ls = np.linspace(0, 10, 200)\n", - "xx, yy = np.meshgrid(ls, ls)\n", - "\n", - "def cells():\n", - " while True:\n", - " t = next(time)\n", - " arr = np.sin(xx+t)*np.cos(yy+t)\n", - " yield hv.Image(arr)\n", - "\n", - "def cells_noisy():\n", - " while True:\n", - " t = next(time)\n", - " arr = np.sin(xx+t)*np.cos(yy+t)\n", - " yield hv.Image(arr + 0.2*np.random.rand(200,200))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's create a Layout using these two generators:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "hv.DynamicMap(cells(), kdims=['time']) + hv.DynamicMap(cells_noisy(), kdims=['time'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you pause the animation, you'll see that these two outputs are *not* in phase, despite the fact that the generators are defined identically (modulo the additive noise)!\n", - "\n", - "The issue is that generators are used via the ``next()`` interface, and so when either generator is called, the simulation time is increased. In other words, the noisy version in subfigure **B** actually corresponds to a later time than in subfigure **A**.\n", - "\n", - "This is a fundamental issue, as the ``next`` method does not take arguments. What we want is for all the ``DynamicMaps`` presented in a Layout to share a common simulation time, which is only incremented by interaction with the scrubber widget. This is exactly the sort of situation where you want to use counter mode." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Handling time-dependent state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To define a ``DynamicMap`` in counter mode:\n", - "\n", - "* Leave one or more dimensions *unbounded* (as in open mode)\n", - "* Supply a callable (as in bounded mode) that accepts *one* argument\n", - "\n", - "This callable should act in the same way as the generators of open mode, except the output is controlled by the single counter argument." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "ls = np.linspace(0, 10, 200)\n", - "xx, yy = np.meshgrid(ls, ls)\n", - "\n", - "def cells_counter(t):\n", - " arr = np.sin(xx+t)*np.cos(yy+t)\n", - " return hv.Image(arr)\n", - "\n", - "def cells_noisy_counter(t):\n", - " arr = np.sin(xx+t)*np.cos(yy+t)\n", - " return hv.Image(arr + 0.2*np.random.rand(200,200))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now if we supply these functions instead of generators, **A** and **B** will correctly be in phase:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "hv.DynamicMap(cells_counter, kdims=['time']) + hv.DynamicMap(cells_noisy_counter, kdims=['time'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Unfortunately, an integer counter is often too simple to describe simulation time, which may be a float with real-world units. To address this, we can simply return the actual key values we want along the time dimension, just as was demonstrated in open mode using generators:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "ls = np.linspace(0, 10, 200)\n", - "xx, yy = np.meshgrid(ls, ls)\n", - "\n", - "# Example of a global simulation time\n", - "# typical in many applications\n", - "t = 0 \n", - " \n", - "def cells_counter_kv(c):\n", - " global t\n", - " t = 0.1 * c\n", - " arr = np.sin(xx+t)*np.cos(yy+t)\n", - " return (t, hv.Image(arr))\n", - "\n", - "def cells_noisy_counter_kv(c):\n", - " global t\n", - " t = 0.1 * c\n", - " arr = np.sin(xx+t)*np.cos(yy+t)\n", - " return (t, hv.Image(arr + 0.2*np.random.rand(200,200)))\n", - " \n", - "hv.DynamicMap(cells_counter_kv, kdims=['time']) + hv.DynamicMap(cells_noisy_counter_kv, kdims=['time'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "print(\"The global simulation time is now t=%f\" % t)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Ensuring that the HoloViews counter maps to a suitable simulation time is the responsibility of the user. However, once a consistent scheme is configured, the callable in each ``DynamicMap`` can specify the desired simulation time. If the requested simulation time is the same as the current simulation time, nothing needs to happen. Otherwise, the simulator can be run forward by the requested amount. In this way, HoloViews can provide a rich graphical interface for controlling and visualizing an external simulator, with very little code required." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Slicing in open and counter mode" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Slicing open and counter mode ``DynamicMaps`` has the exact same semantics as normal ``HoloMap`` slicing, except now the ``.data`` attribute corresponds to the cache. For instance:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def sine_kv_gen(phase=0, freq=0.5):\n", - " while True:\n", - " yield (phase, hv.Image(np.sin(phase + (freq*x**2+freq*y**2))))\n", - " phase+=0.2\n", - " \n", - "dmap = hv.DynamicMap(sine_kv_gen(), kdims=['phase'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's fill the cache with some elements:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "for i in range(21):\n", - " dmap.next()\n", - " \n", - "print(\"Min key value in cache:%s\\nMax key value in cache:%s\" % (min(dmap.keys()), max(dmap.keys())))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "sliced = dmap[1:3.1]\n", - "print(\"Min key value in cache:%s\\nMax key value in cache:%s\" % (min(sliced.keys()), max(sliced.keys())))" + "This grid shows a range of frequencies `f` on the x axis, a range of the first phase variable `ph` on the `y` axis, and a range of different `ph2` phases as overlays within each location in the grid. As you can see, these techniques can help you visualize multidimensional parameter spaces compactly and conveniently.\n" ] }, { @@ -1297,20 +745,22 @@ }, "outputs": [], "source": [ - "%%opts Image {+axiswise}\n", - "ls = np.linspace(0, 10, 200)\n", - "xx, yy = np.meshgrid(ls, ls)\n", + "# NEEDS UPDATING TO NON-GENERATOR VERSION\n", + "\n", + "# %%opts Image {+axiswise}\n", + "# ls = np.linspace(0, 10, 200)\n", + "# xx, yy = np.meshgrid(ls, ls)\n", "\n", - "def cells(vrange=False):\n", - " \"The range is set on the value dimension when vrange is True \"\n", - " time = time_gen()\n", - " while True:\n", - " t = next(time)\n", - " arr = t*np.sin(xx+t)*np.cos(yy+t)\n", - " vdims=[hv.Dimension('Intensity', range=(0,10))] if vrange else ['Intensity']\n", - " yield hv.Image(arr, vdims=vdims)\n", + "# def cells(vrange=False):\n", + "# \"The range is set on the value dimension when vrange is True \"\n", + "# time = time_gen()\n", + "# while True:\n", + "# t = next(time)\n", + "# arr = t*np.sin(xx+t)*np.cos(yy+t)\n", + "# vdims=[hv.Dimension('Intensity', range=(0,10))] if vrange else ['Intensity']\n", + "# yield hv.Image(arr, vdims=vdims)\n", "\n", - "hv.DynamicMap(cells(vrange=False), kdims=['time']) + hv.DynamicMap(cells(vrange=True), kdims=['time'])" + "# hv.DynamicMap(cells(vrange=False), kdims=['time']) + hv.DynamicMap(cells(vrange=True), kdims=['time'])" ] }, {