diff --git a/docs/examples/Tutorial.ipynb b/docs/examples/Tutorial.ipynb index 56407f4f4b5..ce7b8b9748a 100644 --- a/docs/examples/Tutorial.ipynb +++ b/docs/examples/Tutorial.ipynb @@ -5,12 +5,7 @@ "metadata": {}, "source": [ "# QCoDeS tutorial \n", - "This notebook was made to accompany 160703_qcodes_tutorial.pptx but should serve as a self standing example. \n", - "\n", - "The tutorial is aimed at explaining basic qcodes use. \n", - "\n", - "\n", - "\n" + "Basic overview of QCoDeS" ] }, { @@ -21,26 +16,15 @@ "1. Start up an interactive python session (e.g. using jupyter) \n", "2. import desired modules \n", "3. instantiate required instruments \n", - "4. experiment! \n", - "\n", - "Note that step 4 (experiment) often involves defining custom loops, plotting and analyzing data, and testing new instruments. \n", - "**The user *is* the programmer. **\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Import desired modules (including qcodes as qc) " + "4. experiment! " ] }, { "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": false }, "outputs": [ { @@ -386,15 +370,7 @@ "from pprint import pprint\n", "import time\n", "import numpy as np\n", - "\n", - "\n", - "import qcodes as qc\n", - "\n", - "qc.set_mp_method('spawn') # force Windows behavior on mac\n", - "\n", - "# this makes a widget in the corner of the window to show and control\n", - "# subprocesses and any output they would print to the terminal\n", - "qc.show_subprocess_widget()" + "import qcodes as qc" ] }, { @@ -428,12 +404,7 @@ "\n", "# it's nice to have the key parameters be part of the global namespace\n", "# that way they're objects that we can easily set, get, and slice\n", - "c0, c1, c2, vsd = gates.chan0, gates.chan1, gates.chan2, source.amplitude\n", - "\n", - "# once we have implemented a monitor, defining a station will start a\n", - "# DataServer process, and you would see it in the subprocess widget,\n", - "# or via active_children() as here:\n", - "# qc.active_children()" + "c0, c1, c2, vsd = gates.chan0, gates.chan1, gates.chan2, source.amplitude" ] }, { @@ -480,7 +451,8 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": true }, "outputs": [ { @@ -488,12 +460,12 @@ "output_type": "stream", "text": [ "DataSet:\n", - " mode = DataMode.PULL_FROM_SERVER\n", - " location = 'data/2016-07-12/#003_testsweep_22-48-51'\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2016-09-29/#028_{name}_15-13-21'\n", " | | | \n", - " Setpoint | gates_chan0_set | chan0 | (401,)\n", - " Measured | meter_amplitude | amplitude | (401,)\n", - "started at 2016-07-12 22:48:51\n" + " Setpoint | gates_chan0_set | chan0 | (201,)\n", + " Measured | meter_amplitude | amplitude | (201,)\n", + "started at 2016-09-29 15:13:40\n" ] } ], @@ -502,11 +474,11 @@ "# running this notebook over and over won't result in extra files.\n", "# If you leave these out, you get a new timestamped DataSet each time.\n", "\n", - "# data = qc.Loop(c0.sweep(-20,20,0.1), delay=0.003).each(meter.amplitude).run(location='testsweep', overwrite=True)\n", - "data = qc.Loop(c0.sweep(-20,20,0.1), delay=0.003).each(meter.amplitude).run(name='testsweep')\n", - "\n", - "# There should be two extra processes running, DataServer and a sweep \n", - "# this should be visible in the subproces_widget or by calling qc.active_children()" + "loop = qc.Loop(c0.sweep(0,20,0.1), delay=0.001).each(meter.amplitude)\n", + "data = loop.get_data_set(data_manager=False)\n", + "plot = qc.QtPlot()\n", + "plot.add(data.meter_amplitude)\n", + "_ = loop.with_bg_task(plot.update, 0.0005).run(name='testsweep',background=False)" ] }, { @@ -517,34 +489,7 @@ "Notice the **\"DataSet\"**. \n", "A loop returns a dataset. \n", "The representation of the dataset shows what arrays it contains and where it is saved. \n", - "The dataset initially starts out empty (filled with NAN's) and get's filled while the Loop get's executed. \n", - "\n", - "By calling data.sync() the copy in the notebook get's synchronized with the copy on the dataserver\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false, - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# manually bring the data into the main process and display it as numbers\n", - "data.sync()\n", - "# data.arrays " + "The dataset initially starts out empty (filled with NAN's) and get's filled while the Loop get's executed. " ] }, { @@ -553,13 +498,12 @@ "source": [ "Once the measurement is done, take a look at the file in finder/explorer (the dataset.location should give you the relative path). \n", "Note also the snapshot that captures the settings of all instruments at the start of the Loop. \n", - "\n", "This metadata is also accesible from the dataset and captures a snapshot of each instrument listed in the station. " ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -575,20 +519,23 @@ " 'instrument_name': 'meter',\n", " 'label': 'IDN',\n", " 'name': 'IDN',\n", - " 'ts': '2016-07-12 22:48:50',\n", + " 'ts': '2016-09-29 15:13:21',\n", " 'units': '',\n", - " 'value': {'firmware': None, 'model': None, 'serial': None, 'vendor': None}},\n", + " 'value': {'firmware': None,\n", + " 'model': 'MockMeter',\n", + " 'serial': 'meter',\n", + " 'vendor': None}},\n", " 'amplitude': {'__class__': 'qcodes.instrument.parameter.StandardParameter',\n", " 'instrument': 'toymodel.MockMeter',\n", " 'instrument_name': 'meter',\n", " 'label': 'Current (nA)',\n", " 'name': 'amplitude',\n", - " 'ts': '2016-07-12 22:48:50',\n", + " 'ts': '2016-09-29 15:13:40',\n", " 'units': '',\n", " 'value': 0.117}}}" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -597,220 +544,21 @@ "meter.snapshot()" ] }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# Commented out to keep the notebook readable, feel free to uncomment and see what comes out\n", - "# pprint(data.metadata)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plotting the loop \n", - "Because the dataset contains all the required meta-data plotting is trivial. \n", - "I consider it good practice to reuse the plot monitor but that's a personal style thing. \n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false, - "scrolled": false - }, - "outputs": [], - "source": [ - "# live-updating plot, that syncs the data and stops updating when it's finished\n", - "\n", - "# QCodes supports both matplotlib and pyqtgraph for plotting. \n", - "# for a comparison see http://pyqtgraph.org/ (actually not that biased)\n", - "# plot = qc.MatPlot(data.meter_amplitude)\n", - "\n", - "\n", - "# I consider it good practice to instantiate the plotting monitor once \n", - "# and then keep reusing it. \n", - "plotQ = qc.QtPlot()\n", - "plotQ.add(data.meter_amplitude)\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# It may be that live plotting is not working on your system (see #259)\n", - "# Below is a snippet that serves as a workaround, this will be removed upon closing #259\n", - "\n", - "# while data.sync():\n", - "# plotQ.update()\n", - "# plotQ.update()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Example: multiple 2D measurements " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false, - "scrolled": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet:\n", - " mode = DataMode.PULL_FROM_SERVER\n", - " location = 'data/2016-07-12/#004_2D_test_22-48-54'\n", - " | | | \n", - " Setpoint | gates_chan1_set | chan1 | (30,)\n", - " Setpoint | gates_chan0_set | chan0 | (30, 54)\n", - " Measured | meter_amplitude_0 | amplitude | (30, 54)\n", - " Measured | meter_amplitude_3 | amplitude | (30, 54)\n", - "started at 2016-07-12 22:48:55\n" - ] - } - ], - "source": [ - "data2 = qc.Loop(c1[-15:15:1], 0.01).loop(c0[-15:12:.5], 0.001).each(\n", - " meter.amplitude, # first measurement, at c2=0 -> amplitude_0 bcs it's action 0\n", - " qc.Task(c2.set, 1), # action 1 -> c2.set(1)\n", - " qc.Wait(0.001),\n", - " meter.amplitude, # second measurement, at c2=1 -> amplitude_4 bcs it's action 4\n", - " qc.Task(c2.set, 0)\n", - " ).run(name='2D_test')\n", - "\n", - "# use the subplot and add features of qc.MatPlot\n", - "# plot2 = qc.MatPlot(data2.meter_amplitude_0, cmap=plt.cm.hot, figsize=(12, 4.5), subplots=(1, 2))\n", - "# plot2.add(data2.meter_amplitude_3, cmap=plt.cm.hot, subplot=2)\n", - "\n", - "# the equivalent in QtPlot\n", - "# by clearing the old window it can be reused\n", - "plotQ.clear() \n", - "plotQ.add(data2.meter_amplitude_0, figsize=(1200, 500))\n", - "plotQ.add(data2.meter_amplitude_3, subplot=2) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Example same outer loop, different inner loop " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Waiting for the previous background Loop to finish...\n", - "...done. Starting data/2016-07-12/#005_TwoD_different_inner_test_22-49-11\n", - "DataSet:\n", - " mode = DataMode.PULL_FROM_SERVER\n", - " location = 'data/2016-07-12/#005_TwoD_different_inner_test_22-49-11'\n", - " | | | \n", - " Setpoint | gates_chan1_set | chan1 | (30,)\n", - " Measured | meter_amplitude_2 | amplitude | (30,)\n", - " Setpoint | gates_chan0_set | chan0 | (30, 30)\n", - " Measured | meter_amplitude_3_0 | amplitude | (30, 30)\n", - " Setpoint | gates_chan2_set | chan2 | (30, 100)\n", - " Measured | meter_amplitude_5_0 | amplitude | (30, 100)\n", - " Measured | meter_avg_amplitude | avg_amplitude | (30,)\n", - "started at 2016-07-12 22:49:12\n" - ] - } - ], - "source": [ - "data3 = qc.Loop(c1[-15:15:1], 0.1).each(\n", - " qc.Task(c0.set, -10),\n", - " qc.Task(c2.set, 0),\n", - " # a 1D measurement\n", - " meter.amplitude,\n", - " # a 2D sweep, .each is actually unnecessary bcs this is the default measurement\n", - " qc.Loop(c0[-15:15:1], 0.001).each(meter.amplitude),\n", - " qc.Task(c0.set, -10),\n", - " # a 2D sweep with the same outer but different inner loop\n", - " qc.Loop(c2[-10:10:0.2], 0.001).each(meter.amplitude),\n", - " AverageGetter(meter.amplitude, c2[-10:10:0.2], 0.001)\n", - ").run(name='TwoD_different_inner_test')\n", - "\n", - "# several plots updating simultaneously\n", - "# plot3 = qc.MatPlot(data3.meter_amplitude_3_0, cmap=plt.cm.hot)\n", - "# plot3b = qc.MatPlot(data3.meter_amplitude_5_0, cmap=plt.cm.hot, figsize=(12, 4.5), subplots=(1,2))\n", - "# plot3b.add(data3.meter_avg_amplitude, subplot=2)\n", - "plotQ.clear()\n", - "plotQ.add(data3.meter_amplitude_3_0)\n", - "plotQ.add(data3.meter_amplitude_5_0, cmap='viridis', subplot=2)\n", - "plotQ.add(data3.meter_avg_amplitude, subplot=3)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Example 2D scan and average" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "scrolled": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Waiting for the previous background Loop to finish...\n" - ] - } - ], - "source": [ - "# An example of a parameter that returns several values of different dimension\n", - "# This produces the last two arrays from data3, but only takes the data once.\n", - "data4 = qc.Loop(c1[-15:15:1], 0.01).each(\n", - " AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)\n", - ").run(name='TwoD_average_test')\n", + "## Plotting the loop II\n", "\n", - "# plot4 = qc.MatPlot(data4.meter_amplitude, cmap=plt.cm.hot, subplots=(1,2), figsize=(12, 4.5))\n", - "# plot4.add(data4.meter_avg_amplitude, subplot=2)\n", - "\n", - "plotQ.clear()\n", - "plotQ.add(data4.meter_amplitude, figsize=(1200, 500), cmap='viridis')\n", - "plotQ.add(data4.meter_avg_amplitude, subplot=2)" + "QCodes supports both matplotlib inline plotting and pyqtgraph for plotting. \n", + "For a comparison see http://pyqtgraph.org/ (actually not that biased)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Data analysis, loading old data " + "The same API works for plotting a measured dataset or an old dataset." ] }, { @@ -823,60 +571,1812 @@ " being acquired.\n", "- `new_data` to make an empty data set to be populated with new\n", " measurements or simulation data. `new_data` is called internally by\n", - " `Loop.run()` so is also generally not needed directly.\n", - "\n", - "If you omit `location`, or if `location` matches the data set currently\n", - "being acquired, `load_data` and subsequent calls to `data_set.sync()`\n", - "will pull from the `DataServer` (`DataMode.PULL_FROM_SERVER`).\n", - "Otherwise `load_data` and `data_set.sync()` will read from disk\n", - "(`DataMode.LOCAL`).\n", - "\n", - "Note that a `DataServer` is, at least for now, local to one parent\n", - "process / notebook, so if you open a separate notebook for analysis, even\n", - "your live data will be pulled from disk." + " `Loop.run()` so is also generally not needed directly." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# Use the location of a file created earlier this session\n", - "location = data3.location \n", - "print(location)\n", - "data = qc.load_data(location=location)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "data" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": false }, - "outputs": [], - "source": [ - "plotQ.clear()\n", - "plotQ.add(data.meter_avg_amplitude)" + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2016-09-29/#029_{name}_15-13-41'\n", + " | | | \n", + " Setpoint | gates_chan1_set | chan1 | (30,)\n", + " Setpoint | gates_chan0_set | chan0 | (30, 54)\n", + " Measured | meter_amplitude_0 | amplitude | (30, 54)\n", + " Measured | meter_amplitude_3 | amplitude | (30, 54)\n", + "started at 2016-09-29 15:13:57\n" + ] + } + ], + "source": [ + "plot = qc.MatPlot(data.meter_amplitude_0, cmap=plt.cm.hot, figsize=(12, 4.5), subplots=(1, 2))\n", + "plot.add(data.meter_amplitude_3, cmap=plt.cm.hot, subplot=2)\n", + "data2 = loop.with_bg_task(plot.update, 0.0005).run(name='2D_test',background=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The equivalent in QtPlot" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2016-09-29/#030_{name}_15-13-57'\n", + " | | | \n", + " Setpoint | gates_chan1_set | chan1 | (30,)\n", + " Setpoint | gates_chan0_set | chan0 | (30, 54)\n", + " Measured | meter_amplitude_0 | amplitude | (30, 54)\n", + " Measured | meter_amplitude_3 | amplitude | (30, 54)\n", + "started at 2016-09-29 15:14:23\n" + ] + } + ], + "source": [ + "loop = qc.Loop(c1[-15:15:1], 0.01).loop(c0[-15:12:.5], 0.001).each(\n", + " meter.amplitude, # first measurement, at c2=0 -> amplitude_0 bcs it's action 0\n", + " qc.Task(c2.set, 1), # action 1 -> c2.set(1)\n", + " qc.Wait(0.001),\n", + " meter.amplitude, # second measurement, at c2=1 -> amplitude_4 bcs it's action 4\n", + " qc.Task(c2.set, 0)\n", + " )\n", + "data = loop.get_data_set(data_manager=False)\n", + "\n", + "plotQ = qc.QtPlot()\n", + "plotQ.add(data.meter_amplitude_0, figsize=(1200, 500))\n", + "plotQ.add(data.meter_amplitude_3, subplot=2)\n", + "data2 = loop.with_bg_task(plotQ.update, 0.0005).run(name='2D_test',background=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example same outer loop, different inner loop " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "loop3 = qc.Loop(c1[-15:15:1], 0.1).each(\n", + " qc.Task(c0.set, -10),\n", + " qc.Task(c2.set, 0),\n", + " # a 1D measurement\n", + " meter.amplitude,\n", + " # a 2D sweep, .each is actually unnecessary because this is the default measurement\n", + " qc.Loop(c0[-15:15:1], 0.001).each(meter.amplitude),\n", + " qc.Task(c0.set, -10),\n", + " # a 2D sweep with the same outer but different inner loop\n", + " qc.Loop(c2[-10:10:0.2], 0.001).each(meter.amplitude),\n", + " AverageGetter(meter.amplitude, c2[-10:10:0.2], 0.001)\n", + ")\n", + "data = loop3.get_data_set(data_manager=False) \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### several plots updating simultaneously (Currently broken on matplotlib)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/unga/Hack/qdev/qcodes/plots/pyqtgraph.py:292: UserWarning: nonuniform nested setpoint array passed to pyqtgraph. ignoring, using default scaling.\n", + " 'nonuniform nested setpoint array passed to '\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2016-09-29/#031_{name}_15-14-23'\n", + " | | | \n", + " Setpoint | gates_chan1_set | chan1 | (30,)\n", + " Measured | meter_amplitude_2 | amplitude | (30,)\n", + " Setpoint | gates_chan0_set | chan0 | (30, 30)\n", + " Measured | meter_amplitude_3_0 | amplitude | (30, 30)\n", + " Setpoint | gates_chan2_set | chan2 | (30, 100)\n", + " Measured | meter_amplitude_5_0 | amplitude | (30, 100)\n", + " Measured | meter_avg_amplitude | avg_amplitude | (30,)\n", + "started at 2016-09-29 15:15:07\n" + ] + } + ], + "source": [ + "plotQ = qc.QtPlot()\n", + "plotQ.add(data.meter_amplitude_3_0)\n", + "plotQ.add(data.meter_amplitude_5_0, cmap='viridis', subplot=2)\n", + "plotQ.add(data.meter_avg_amplitude, subplot=3)\n", + "data = loop3.with_bg_task(plotQ.update, 0.0005).run(name='TwoD_different_inner_test', background=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2D scan and average\n", + "\n", + " An example of a parameter that returns several values of different dimension\n", + " This produces the last two arrays from data3, but only takes the data once." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2016-09-29/#032_{name}_15-15-07'\n", + " | | | \n", + " Setpoint | gates_chan1_set | chan1 | (30,)\n", + " Measured | chan2 | chan2 | (30, 100)\n", + " Measured | meter_amplitude | amplitude | (30, 100)\n", + " Measured | meter_avg_amplitude | avg_amplitude | (30,)\n", + "started at 2016-09-29 15:15:28\n" + ] + } + ], + "source": [ + "loop4 = qc.Loop(c1[-15:15:1], 0.01).each(\n", + " AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)\n", + ")\n", + "data4 = loop4.get_data_set(data_manager=False)\n", + "plotQ = qc.QtPlot()\n", + "plotQ.add(data4.meter_amplitude, figsize=(1200, 500), cmap='viridis')\n", + "plotQ.add(data4.meter_avg_amplitude, subplot=2)\n", + "data4 = loop4.with_bg_task(plotQ.update, 0.005).run(name='TwoD_average_test', background=False)" ] } ], "metadata": { + "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python [default]", "language": "python", "name": "python3" }, @@ -890,7 +2390,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.5.2" }, "widgets": { "state": {}, diff --git a/qcodes/data/data_set.py b/qcodes/data/data_set.py index 1185a4b6396..57d65093fef 100644 --- a/qcodes/data/data_set.py +++ b/qcodes/data/data_set.py @@ -271,6 +271,7 @@ def __init__(self, location=None, mode=DataMode.LOCAL, arrays=None, self.write_period = write_period self.last_write = 0 + self.last_store = -1 self.metadata = {} @@ -384,7 +385,9 @@ def sync(self): # version on the DataServer from the main copy) if not self.is_live_mode: # LOCAL DataSet - just read it in - # TODO: compare timestamps to know if we need to read? + # Compare timestamps to avoid overwriting unsaved data + if self.last_store > self.last_write: + return True try: self.read() except IOError: @@ -596,15 +599,19 @@ def store(self, loop_indices, ids_values): # Defers to the copy on the dataserver to call this identical # function self.data_manager.write('store_data', loop_indices, ids_values) - else: + elif self.mode == DataMode.LOCAL: # You will always end up in this block, either in the copy # on the server (if you hit the if statement above) or else here for array_id, value in ids_values.items(): self.arrays[array_id][loop_indices] = value + self.last_store = time.time() if (self.write_period is not None and time.time() > self.last_write + self.write_period): self.write() self.last_write = time.time() + else: # in PULL_FROM_SERVER mode; store() isn't legal + raise RuntimeError('This object is pulling from a DataServer, ' + 'so data insertion is not allowed.') def read(self): """Read the whole DataSet from storage, overwriting the local data.""" diff --git a/qcodes/loops.py b/qcodes/loops.py index c92d37fcbaf..e4b89c3bf79 100644 --- a/qcodes/loops.py +++ b/qcodes/loops.py @@ -175,6 +175,8 @@ def __init__(self, sweep_values, delay=0, station=None, self.nested_loop = None self.actions = None self.then_actions = () + self.bg_task = None + self.bg_min_delay = None self.progress_interval = progress_interval def loop(self, sweep_values, delay=0): @@ -242,7 +244,23 @@ def each(self, *actions): return ActiveLoop(self.sweep_values, self.delay, *actions, then_actions=self.then_actions, station=self.station, - progress_interval=self.progress_interval) + progress_interval=self.progress_interval, + bg_task=self.bg_task, bg_min_delay=self.bg_min_delay) + + def with_bg_task(self, task, min_delay=1): + """ + Attaches a background task to this loop. + + Args: + task: A callable object with no parameters. This object will be + invoked periodically during the measurement loop. + + min_delay (default 1): The minimum number of seconds to wait + between task invocations. Note that the actual time between + task invocations may be much longer than this, as the task is + only run between passes through the loop. + """ + return _attach_bg_task(self, task, min_delay) @staticmethod def validate_actions(*actions): @@ -338,6 +356,15 @@ def _attach_then_actions(loop, actions, overwrite): return loop +def _attach_bg_task(loop, task, min_delay): + """Inner code for both Loop and ActiveLoop.bg_task""" + if loop.bg_task is None: + loop.bg_task = task + loop.bg_min_delay = min_delay + else: + raise RuntimeError('Only one background task is allowed per loop') + + return loop class ActiveLoop(Metadatable): """ @@ -356,7 +383,7 @@ class ActiveLoop(Metadatable): signal_period = 1 def __init__(self, sweep_values, delay, *actions, then_actions=(), - station=None, progress_interval=None): + station=None, progress_interval=None, bg_task=None, bg_min_delay=None): super().__init__() self.sweep_values = sweep_values self.delay = delay @@ -364,6 +391,9 @@ def __init__(self, sweep_values, delay, *actions, then_actions=(), self.progress_interval = progress_interval self.then_actions = then_actions self.station = station + self.bg_task = bg_task + self.bg_min_delay = bg_min_delay + self.data_set = None # compile now, but don't save the results # just used for preemptive error checking @@ -404,6 +434,21 @@ def then(self, *actions, overwrite=False): then_actions=self.then_actions, station=self.station) return _attach_then_actions(loop, actions, overwrite) + def with_bg_task(self, task, min_delay=1): + """ + Attaches a background task to this loop. + + Args: + task: A callable object with no parameters. This object will be + invoked periodically during the measurement loop. + + min_delay (default 1): The minimum number of seconds to wait + between task invocations. Note that the actual time between + task invocations may be much longer than this, as the task is + only run between passes through the loop. + """ + return _attach_bg_task(self, task, min_delay) + def snapshot_base(self, update=False): """Snapshot of this ActiveLoop's definition.""" return { @@ -595,6 +640,45 @@ def _check_signal(self): else: raise ValueError('unknown signal', signal_) + def get_data_set(self, data_manager=None, *args, **kwargs): + """ + Return the data set for this loop. + If no data set has been created yet, a new one will be created and returned. + Note that all arguments are ignored if the data set has already been created. + + data_manager: a DataManager instance (omit to use default, + False to store locally) + + kwargs are passed along to data_set.new_data. The key ones are: + location: the location of the DataSet, a string whose meaning + depends on formatter and io, or False to only keep in memory. + May be a callable to provide automatic locations. If omitted, will + use the default DataSet.location_provider + name: if location is default or another provider function, name is + a string to add to location to make it more readable/meaningful + to users + formatter: knows how to read and write the file format + default can be set in DataSet.default_formatter + io: knows how to connect to the storage (disk vs cloud etc) + write_period: how often to save to storage during the loop. + default 5 sec, use None to write only at the end + + returns: + a DataSet object that we can use to plot + """ + if self.data_set is None: + if data_manager is False: + data_mode = DataMode.LOCAL + else: + data_mode = DataMode.PUSH_TO_SERVER + + data_set = new_data(arrays=self.containers(), mode=data_mode, + data_manager=data_manager, *args, **kwargs) + + self.data_set = data_set + + return self.data_set + def run_temp(self, **kwargs): """ wrapper to run this loop in the foreground as a temporary data set, @@ -652,13 +736,7 @@ def run(self, background=True, use_threads=True, quiet=False, flush=True) prev_loop.join() - if data_manager is False: - data_mode = DataMode.LOCAL - else: - data_mode = DataMode.PUSH_TO_SERVER - - data_set = new_data(arrays=self.containers(), mode=data_mode, - data_manager=data_manager, *args, **kwargs) + data_set = self.get_data_set(data_manager, *args, **kwargs) self.set_common_attrs(data_set=data_set, use_threads=use_threads, signal_queue=self.signal_queue) @@ -710,7 +788,9 @@ def run(self, background=True, use_threads=True, quiet=False, if not quiet: print(repr(self.data_set)) print(datetime.now().strftime('started at %Y-%m-%d %H:%M:%S')) - return self.data_set + ds = self.data_set + self.data_set = None + return ds def _compile_actions(self, actions, action_indices=()): callables = [] @@ -777,12 +857,15 @@ def _run_loop(self, first_delay=0, action_indices=(), callables = self._compile_actions(self.actions, action_indices) t0 = time.time() + last_task = t0 + last_task_failed = False imax = len(self.sweep_values) for i, value in enumerate(self.sweep_values): if self.progress_interval is not None: tprint('loop %s: %d/%d (%.1f [s])' % ( self.sweep_values.name, i, imax, time.time() - t0), dt=self.progress_interval, tag='outerloop') + self.sweep_values.set(value) new_indices = loop_indices + (i,) new_values = current_values + (value,) @@ -806,12 +889,35 @@ def _run_loop(self, first_delay=0, action_indices=(), # after the first setpoint, delay reverts to the loop delay delay = self.delay + + # now check for a background task and execute it if it's + # been long enough since the last time + # don't let exceptions in the background task interrupt + # the loop + # if the background task fails twice consecutively, stop + # executing it + if self.bg_task is not None: + t = time.time() + if t - last_task >= self.bg_min_delay: + try: + self.bg_task() + last_task_failed = False + except Exception: + if last_task_failed: + self.bg_task = None + last_task_failed = True + last_task = t + if self.progress_interval is not None: # final progress note: set dt=-1 so it *always* prints tprint('loop %s DONE: %d/%d (%.1f [s])' % ( self.sweep_values.name, i + 1, imax, time.time() - t0), dt=-1, tag='outerloop') + # run the background task one last time to catch the last setpoint(s) + if self.bg_task is not None: + self.bg_task() + # the loop is finished - run the .then actions for f in self._compile_actions(self.then_actions, ()): f()