diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 63aef9e7e31d..0657ab003c09 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -59,13 +59,18 @@ Setup Running Tests ~~~~~~~~~~~~~ -The core test runner is in ``qcodes/test.py``: +We don't want to reinvent the wheel, and thus use py.test. +It's easy to install: :: - python qcodes/test.py - # optional extra verbosity and fail fast - python qcodes/test.py -v -f + pip install coverage pytest-cov pytest + +Then to test and view the coverage: + +:: + py.test --cov=qcodes --cov-report xml --cov-config=.coveragerc + You can also run single tests with: @@ -78,92 +83,6 @@ You can also run single tests with: # or python -m unittest qcodes.tests.test_metadata.TestMetadatable.test_snapshot -If you run the core test runner, you should see output that looks -something like this: - -:: - - .........***** found one MockMock, testing ***** - ............................................Timing resolution: - startup time: 0.000e+00 - min/med/avg/max dev: 9.260e-07, 9.670e-07, 1.158e-06, 2.109e-03 - async sleep delays: - startup time: 2.069e-04 - min/med/avg/max dev: 3.372e-04, 6.376e-04, 6.337e-04, 1.007e-03 - multiprocessing startup delay and regular sleep delays: - startup time: 1.636e-02 - min/med/avg/max dev: 3.063e-05, 2.300e-04, 2.232e-04, 1.743e-03 - should go to stdout;should go to stderr;.stdout stderr stdout stderr ..[10:44:09.063 A Queue] should get printed - ................................... - ---------------------------------------------------------------------- - Ran 91 tests in 4.192s - - OK - Name Stmts Miss Cover Missing - ---------------------------------------------------------- - data/data_array.py 104 0 100% - data/data_set.py 179 140 22% 38-55, 79-94, 99-104, 123-135, 186-212, 215-221, 224-244, 251-254, 257-264, 272, 280-285, 300-333, 347-353, 360-384, 395-399, 405-407, 414-420, 426-427, 430, 433-438 - data/format.py 225 190 16% 44-55, 61-62, 70, 78-97, 100, 114-148, 157-188, 232, 238, 246, 258-349, 352, 355-358, 361-368, 375-424, 427-441, 444, 447-451 - data/io.py 76 50 34% 71-84, 90-91, 94, 97, 103, 109-110, 119-148, 154-161, 166, 169, 172, 175-179, 182, 185-186 - data/manager.py 124 89 28% 15-20, 31, 34, 48-62, 65-67, 70, 76-77, 80-84, 90-102, 108-110, 117-121, 142-151, 154-182, 185, 188, 207-208, 215-221, 227-229, 237, 243, 249 - instrument/base.py 74 0 100% - instrument/function.py 45 1 98% 77 - instrument/ip.py 20 12 40% 10-16, 19-20, 24-25, 29-38 - instrument/mock.py 63 0 100% - instrument/parameter.py 200 2 99% 467, 470 - instrument/sweep_values.py 107 33 69% 196-207, 220-227, 238-252, 255-277 - instrument/visa.py 36 24 33% 10-25, 28-32, 35-36, 40-41, 47-48, 57-58, 62-64, 68 - loops.py 285 239 16% 65-74, 81-91, 120-122, 133-141, 153-165, 172-173, 188-207, 216-240, 243-313, 316-321, 324-350, 354-362, 371-375, 378-381, 414-454, 457-474, 477-484, 487-491, 510-534, 537-543, 559-561, 564, 577, 580, 590-608, 611-618, 627-628, 631 - station.py 35 24 31% 17-32, 35, 45-50, 60, 67-82, 88 - utils/helpers.py 95 0 100% - utils/metadata.py 13 0 100% - utils/multiprocessing.py 95 2 98% 125, 134 - utils/sync_async.py 114 8 93% 166, 171-173, 176, 180, 184, 189-191 - utils/timing.py 72 0 100% - utils/validators.py 110 0 100% - ---------------------------------------------------------- - TOTAL 2072 814 61% - -The key is ``OK`` in the middle (that means all the tests passed), and -the presence of the coverage report after it. If any tests fail, we do -not show a coverage report, and the end of the output will contain -tracebacks and messages about what failed, for example: - -:: - - ====================================================================== - FAIL: test_sweep_steps_edge_case (tests.test_instrument.TestParameters) - ---------------------------------------------------------------------- - Traceback (most recent call last): - File "/Users/alex/qdev/Qcodes/qcodes/tests/test_instrument.py", line 360, in test_sweep_steps_edge_case - self.check_set_amplitude2('Off', log_count=1, history_count=2) - File "/Users/alex/qdev/Qcodes/qcodes/tests/test_instrument.py", line 345, in check_set_amplitude2 - self.assertTrue(line.startswith('negative delay'), line) - AssertionError: False is not true : cannot sweep amplitude2 from 0.1 to Off - jumping. - - ---------------------------------------------------------------------- - Ran 91 tests in 4.177s - - FAILED (failures=1) - -The coverage report is only useful if you have been adding new code, to -see whether your tests visit all of your code. Look at the file(s) you -have been working on, and ensure that the "missing" section does not -contain the line numbers of any of the blocks you have touched. -Currently the core still has a good deal of untested code - eventually -we will have all of this tested, but for now you can ignore all the rest -of the missing coverage. - -You can also run these tests from inside python. The output is similar -except that a) you don't get coverage reporting, and b) one test has to -be skipped because it does not apply within a notebook, so the output -will end ``OK (skipped=1)``: - -.. code:: python - - import qcodes - qcodes.test_core() # optional verbosity = 1 (default) or 2 - If the tests pass, you should be ready to start developing! To tests actual instruments, first instantiate them in an interactive @@ -314,15 +233,6 @@ and then unit testing should be run on pull-request, using CI. Maybe simplify to a one command that says: if there's enough cover, and all good or fail and where it fails. -- The standard test commands are listed above under - :ref:`runnningtests`. More notes on different test runners can - be found in :ref:`testing`. - -- Core tests live in - `qcodes/tests `__ - and instrument tests live in the same directories as the instrument - drivers. - - We should have a *few* high-level "integration" tests, but simple unit tests (that just depend on code in one module) are more valuable for several reasons: @@ -330,7 +240,6 @@ good or fail and where it fails. - When features change it is likely that more tests will need to change - Unit tests can cover many scenarios much faster than integration tests. - - If you're having difficulty making unit tests, first consider whether your code could be restructured to make it less dependent on other modules. Often, however, extra techniques are needed to break down a @@ -339,9 +248,8 @@ good or fail and where it fails. - Patching, one of the most useful parts of the `unittest.mock `__ library. This lets you specify exactly how other functions/objects - should behave when they're called by the code you are testing. For a - simple example, see - `test\_multiprocessing.py `__ + should behave when they're called by the code you are testing. + - Supporting files / data: Lets say you have a test of data acquisition and analysis. You can break that up into an acquisition test and an analysis by saving the intermediate state, namely the data file, in diff --git a/README.rst b/README.rst index 423dfab85e8e..c17387b7d929 100644 --- a/README.rst +++ b/README.rst @@ -70,9 +70,11 @@ $QCODES_INSTALL_DIR is the folder where you want to have the source code. cd $QCODES_INSTALL_DIR pyenv install 3.5.2 pyenv virtualenv 3.5.2 qcodes-dev + pyenv activate qcodes-dev pip install -r requirements.txt + pip install coverage pytest-cov pytest --upgrade pip install -e . - python qcodes/test.py -f + py.test --cov=qcodes --cov-config=.coveragerc If the tests pass you are ready to hack! This is the reference setup one needs to have to contribute, otherwise diff --git a/benchmarking/mptest.py b/benchmarking/mptest.py deleted file mode 100644 index 4072ef921694..000000000000 --- a/benchmarking/mptest.py +++ /dev/null @@ -1,156 +0,0 @@ -# stress test multiprocessing -# run with: python mptest.py -# proc_count: the number of processes to spin up and load qcodes -# period: milliseconds to wait between calling each process -# repetitions: how many times to call each one -# start_method: multiprocessing method to use (fork, forkserver, spawn) - -import multiprocessing as mp -import sys -import os -import psutil -import time - -import qcodes as qc - - -timer = time.perf_counter - - -def print_perf(): - print(time.perf_counter()) - - -def mp_test(name, qin, qout, qglobal): - ''' - simple test that keeps a process running until asked to stop, - and looks for an attribute within qcodes just to ensure it has loaded it - ''' - delays = [] - first = True - while True: - item, qtime = qin.get() - if first: # ignore the first one... process is still starting - first = False - else: - delays.append(timer() - qtime) - if item == 'break': - qglobal.put({ - 'name': name, - 'avg': sum(delays) / len(delays), - 'max': max(delays) - }) - break - qout.put(repr(getattr(qc, item))) - - -def get_memory(pid): - mi = psutil.Process(pid).memory_info() - return mi.rss, mi.vms - - -def get_all_memory(processes, title): - main_memory = get_memory(os.getpid()) - proc_memory = [0, 0] - for proc in processes: - for i, v in enumerate(get_memory(proc.pid)): - proc_memory[i] += v - # print(v) - - return { - 'main_physical': main_memory[0]/1e6, - 'main_virtual': main_memory[1]/1e6, - 'proc_physical': proc_memory[0]/1e6, - 'proc_virtual': proc_memory[1]/1e6, - 'title': title - } - - -def format_memory(mem): - return ('{title}\n' - ' main: {main_physical:.0f} MB phys, ' - '{main_virtual:.0f} MB virt\n' - ' procs: {proc_physical:.0f} MB phys, ' - '{proc_virtual:.0f} MB virt\n' - '').format(**mem) - - -if __name__ == '__main__': - proc_count = int(sys.argv[-4]) - period = float(sys.argv[-3]) - reps = int(sys.argv[-2]) - method = sys.argv[-1] - mp.set_start_method(method) - - qglobal = mp.Queue() - - mem = [get_all_memory([], 'on startup')] - - queues = [] - processes = [] - resp_delays = [] - - t_before_start = timer() - - for proc_num in range(proc_count): - qin = mp.Queue() - qout = mp.Queue() - queues.append((qin, qout)) - p = mp.Process(target=mp_test, - args=('p{}'.format(proc_num), qin, qout, qglobal)) - processes.append(p) - p.start() - - start_delay = (timer() - t_before_start) * 1000 - - mem.append(get_all_memory(processes, 'procs started')) - - for i in range(reps): - for qin, qout in queues: - t1 = timer() - qin.put(('Loop', timer())) - loop_repr = qout.get() - if i: - # ignore the first one, process is still starting - resp_delays.append((timer() - t1) * 1000) - if(loop_repr != repr(qc.Loop)): - raise RuntimeError('{} != {}'.format(loop_repr, repr(qc.Loop))) - print('.', end='', flush=True) - time.sleep(period / 1000) - print('') - - mem.append(get_all_memory(processes, 'procs done working')) - - for qin, qout in queues: - qin.put(('break', timer())) - - t_before_join = timer() - for proc in processes: - proc.join() - join_delay = (timer() - t_before_join) * 1000 - - delays = [qglobal.get() for proc in processes] - avg_delay = sum([d['avg'] for d in delays]) * 1000 / len(delays) - max_delay = max([d['max'] for d in delays]) * 1000 - - avg_resp_delay = sum(resp_delays) / len(resp_delays) - max_resp_delay = max(resp_delays) - - print(('Ran {} procs using "{}" method\n' - 'sent messages every {} milliseconds, {} times\n' - ).format(proc_count, method, period, reps)) - - print('Milliseconds to start all processes: {:.3f}'.format(start_delay)) - print('Final join delay: {:.3f}\n'.format(join_delay)) - - print('Milliseconds to receive to queue request') - print(' avg: {:.6f}'.format(avg_delay)) - print(' max: {:.6f}\n'.format(max_delay)) - - print('Milliseconds to respond to queue request') - print(' avg: {:.6f}'.format(avg_resp_delay)) - print(' max: {:.6f}\n'.format(max_resp_delay)) - - # report on the memory results - for m in mem: - print(format_memory(m)) diff --git a/benchmarking/mptest_results_mac.txt b/benchmarking/mptest_results_mac.txt deleted file mode 100644 index ec8e2a7765d6..000000000000 --- a/benchmarking/mptest_results_mac.txt +++ /dev/null @@ -1,117 +0,0 @@ -Mac OS X -------------------- - -Alexs-Air-2:Qcodes alex$ p3 mptest.py 5 50 50 spawn -.................................................. -Ran 5 procs using "spawn" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 30.601 -Final join delay: 130.124 - -Milliseconds to receive to queue request - avg: 0.321102 - max: 1.061535 - -Milliseconds to respond to queue request - avg: 0.607558 - max: 1.465819 - -on startup - main: 29 MB phys, 2498 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 29 MB phys, 2498 MB virt - procs: 8 MB phys, 12327 MB virt - -procs done working - main: 29 MB phys, 2524 MB virt - procs: 142 MB phys, 12509 MB virt - - -Alexs-Air-2:Qcodes alex$ p3 mptest.py 20 50 50 spawn -.................................................. -Ran 20 procs using "spawn" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 512.364 -Final join delay: 456.155 - -Milliseconds to receive to queue request - avg: 0.302726 - max: 2.754109 - -Milliseconds to respond to queue request - avg: 0.555251 - max: 1.123662 - -on startup - main: 28 MB phys, 2498 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 29 MB phys, 2498 MB virt - procs: 150 MB phys, 49561 MB virt - -procs done working - main: 29 MB phys, 2603 MB virt - procs: 571 MB phys, 50029 MB virt - - -Alexs-Air-2:Qcodes alex$ p3 mptest.py 100 50 50 spawn -.................................................. -Ran 100 procs using "spawn" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 5242.341 -Final join delay: 3806.779 - -Milliseconds to receive to queue request - avg: 0.527770 - max: 59.421732 - -Milliseconds to respond to queue request - avg: 0.416881 - max: 4.560305 - -on startup - main: 28 MB phys, 2497 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 30 MB phys, 2499 MB virt - procs: 1234 MB phys, 248003 MB virt - -procs done working - main: 11 MB phys, 3026 MB virt - procs: 2144 MB phys, 250162 MB virt - - -Alexs-Air-2:Qcodes alex$ p3 mptest.py 100 50 50 fork -.................................................. -Ran 100 procs using "fork" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 447.375 -Final join delay: 113.426 - -Milliseconds to receive to queue request - avg: 0.351694 - max: 11.889809 - -Milliseconds to respond to queue request - avg: 0.494252 - max: 2.525026 - -on startup - main: 28 MB phys, 2498 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 29 MB phys, 2499 MB virt - procs: 444 MB phys, 249616 MB virt - -procs done working - main: 32 MB phys, 3025 MB virt - procs: 525 MB phys, 250142 MB virt diff --git a/benchmarking/mptest_results_windows.txt b/benchmarking/mptest_results_windows.txt deleted file mode 100644 index e267edeb280b..000000000000 --- a/benchmarking/mptest_results_windows.txt +++ /dev/null @@ -1,88 +0,0 @@ -Windows -------------------- - -c:\Qcodes>python mptest.py 5 50 50 spawn -.................................................. -Ran 5 procs using "spawn" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 15.406 -Final join delay: 92.359 - -Milliseconds to receive to queue request - avg: -510.603209 - max: -509.684951 - -Milliseconds to respond to queue request - avg: 0.360488 - max: 0.659211 - -on startup - main: 36 MB phys, 31 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 36 MB phys, 31 MB virt - procs: 14 MB phys, 9 MB virt - -procs done working - main: 36 MB phys, 32 MB virt - procs: 179 MB phys, 155 MB virt - - -c:\Qcodes>python mptest.py 20 50 50 spawn -.................................................. -Ran 20 procs using "spawn" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 126.134 -Final join delay: 228.260 - -Milliseconds to receive to queue request - avg: -1591.011659 - max: -1563.003352 - -Milliseconds to respond to queue request - avg: 0.365974 - max: 0.794690 - -on startup - main: 36 MB phys, 31 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 36 MB phys, 31 MB virt - procs: 98 MB phys, 60 MB virt - -procs done working - main: 37 MB phys, 32 MB virt - procs: 716 MB phys, 620 MB virt - - -c:\Qcodes>python mptest.py 100 50 50 spawn -.................................................. -Ran 100 procs using "spawn" method -sent messages every 50.0 milliseconds, 50 times - -Milliseconds to start all processes: 761.556 -Final join delay: 927.847 - -Milliseconds to receive to queue request - avg: -8560.453780 - max: -8470.848083 - -Milliseconds to respond to queue request - avg: 0.423033 - max: 1.299948 - -on startup - main: 35 MB phys, 31 MB virt - procs: 0 MB phys, 0 MB virt - -procs started - main: 37 MB phys, 32 MB virt - procs: 872 MB phys, 572 MB virt - -procs done working - main: 40 MB phys, 36 MB virt - procs: 3575 MB phys, 3094 MB virt diff --git a/benchmarking/thread_test.py b/benchmarking/thread_test.py deleted file mode 100644 index 40dac28bc692..000000000000 --- a/benchmarking/thread_test.py +++ /dev/null @@ -1,55 +0,0 @@ -import threading -import time - - -def sleeper(t, n, out, make_error): - time.sleep(t) - out[n] = n - if make_error: - raise RuntimeError('hello from # {}!'.format(n)) - - -def runmany(n, t, error_nums): - out = [None] * n - - t0 = time.perf_counter() - - threads = [ - CatchingThread(target=sleeper, args=(t, i, out, i in error_nums)) - for i in range(n)] - - # start threads backward - [t.start() for t in reversed(threads)] - [t.join() for t in threads] - - t1 = time.perf_counter() - - out_ok = [] - for i in range(n): - if out[i] != i: - out_ok += ['ERROR! out[{}] = {}'.format(i, out[i])] - - if not out_ok: - out_ok += ['all output correct'] - - print('{} parallel threads sleeping\n'.format(n) + - 'given time: {}\n'.format(t) + - 'resulting time: {}\n'.format(t1 - t0) + - '\n'.join(out_ok)) - - -class CatchingThread(threading.Thread): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.exception = None - - def run(self): - try: - super().run() - except Exception as e: - self.exception = e - - def join(self): - super().join() - if self.exception: - raise self.exception diff --git a/docs/api/private.rst b/docs/api/private.rst index 8fffb7cb2fa8..f5e38caebc95 100644 --- a/docs/api/private.rst +++ b/docs/api/private.rst @@ -12,8 +12,5 @@ Classes and Functions utils.command utils.deferred_operations - utils.nested_attrs utils.helpers - utils.timing utils.metadata - process.qcodes_process diff --git a/docs/api/public.rst b/docs/api/public.rst index 12acc2e94643..04521a6f0d58 100644 --- a/docs/api/public.rst +++ b/docs/api/public.rst @@ -42,8 +42,6 @@ Loops .. autosummary:: :toctree: generated/ - get_bg - halt_bg Loop Measure @@ -70,9 +68,6 @@ Data .. autosummary:: :toctree: generated/ - get_data_manager - qcodes.data.manager.DataManager - DataMode DataSet new_data load_data @@ -102,8 +97,6 @@ Instrument Instrument IPInstrument VisaInstrument - MockInstrument - MockModel Plot @@ -123,7 +116,5 @@ Utils & misc :toctree: generated/ qcodes.utils.validators - qcodes.process.helpers.set_mp_method qcodes.utils.helpers.in_notebook - qcodes.widgets.widgets.show_subprocess_widget diff --git a/docs/community/index.rst b/docs/community/index.rst index 0d42653900d0..c0cd05f6962e 100644 --- a/docs/community/index.rst +++ b/docs/community/index.rst @@ -11,4 +11,3 @@ This is the guide for you, the developer that wants to maintain/expand QCoDes. install contributing objects - testing diff --git a/docs/community/testing.rst b/docs/community/testing.rst deleted file mode 100644 index dc0ef7d22107..000000000000 --- a/docs/community/testing.rst +++ /dev/null @@ -1,80 +0,0 @@ -.. _testing: - - -Notes on test runners compatible with Qcodes -============================================ - -There is now a test script `test.py `__ in the qcodes -directory that uses the standard ``unittest`` machinery to run all the -core tests (does not include instrument drivers). It has been tested on -Mac (terminal), and Windows (cmd, git bash, and PowerShell). It includes -coverage testing, but will only print a coverage report if tests pass. - -The biggest difficulty with testing Qcodes is windows multiprocessing. -The spawn method restricts execution in ways that are annoying for -regular users (no class/function definitions in the notebook, no -closures) but seem to be completely incompatible with some test runners -(and/or coverage tracking) - -I considered the following test runners: - **nose**: works well, but it -has a `note on its homepage `__ -that it is no longer being actively maintained (in favor of nose2 -development), so we should not use it long-term. - -- **unittest**: the standard, built-in python tester. The only thing we - really need to add to this is coverage testing, so now the question - is what's the easiest way to do this? On Windows just using unittest - wrapped in coverage fails. - -- **nose2**: has a broken coverage plugin - it reports all the - unindented lines, ie everything # that executes on import, as - uncovered - but can be used by wrapping it inside coverage instead, - just like unittest. - -- **py.test**: seems to add lots of features, but it's not clear they - are useful for us? Has a good coverage plugin but seems to require - tons of command-line options. Requires both ``pytest`` and - ``pytest-cov`` packages - -on Mac terminal: - -:: - - # the following work with coverage: - nosetests - python setup.py nosetests - py.test --cov-config .coveragerc --cov qcodes --cov-report term-missing - coverage run -m nose2 && coverage report -m - # both of these run unittest: - coverage run setup.py test && coverage report -m - coverage run -m unittest && coverage report -m - - # nose2's coverage plugin is broken - it reports all the unindented lines (everything - # that executes on import) as uncovered - nose2 -C --coverage-report term-missing - -Windows cmd shell and git bash behave identically, PowerShell has -different chain syntax (commands with &&): - -:: - - # the following work with coverage: - nosetests - py.test --cov-config .coveragerc --cov qcodes --cov-report term-missing - coverage run -m nose2 && coverage report -m # cmd or bash - (coverage run -m nose2) -and (coverage report -m) # PowerShell - - # the following work without coverage: - python -m unittest discover - python -m unittest # discover is unnecessary now, perhaps because I put test_suite in setup.py? - - # the following do not work: - - # fails on relative import in unittest inside separate process (why is it importing that anyway?) - coverage run -m unittest discover && coverage report -m # cmd or bash - (coverage run -m unittest discover) -and (coverage report -m) # PowerShell - # these fail asking for freeze_support() but nothing I do with that seems to help - python setup.py test - coverage run setup.py test && coverage report -m # cmd or bash - (coverage run setup.py test) -and (coverage report -m) # PowerShell - python setup.py nosetests diff --git a/docs/examples/Agilent 34411A versus Keysight 34465A.ipynb b/docs/examples/Agilent 34411A versus Keysight 34465A.ipynb new file mode 100644 index 000000000000..0699f323e3db --- /dev/null +++ b/docs/examples/Agilent 34411A versus Keysight 34465A.ipynb @@ -0,0 +1,442 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "# Agilent 34411A versus Keysight 34465A\n", + "\n", + "The following notebook perfoms a benchmarking of the two DMMs. In part one, raw readings of immediate voltages are timed\n", + "and compared. In part two, actual sweeps are performed with a QDac." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import time\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "import qcodes as qc\n", + "from qcodes.instrument_drivers.Keysight.Keysight_34465A import Keysight_34465A\n", + "from qcodes.instrument_drivers.agilent.Agilent_34400A import Agilent_34400A\n", + "from qcodes.instrument_drivers.QDev.QDac import QDac" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Import and setup" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to: Keysight Technologies 34465A (serial:MY54505388, firmware:A.02.14-02.40-02.14-00.49-02-01) in 6.93s\n", + "Connected to: Agilent Technologies 34411A (serial:MY48002016, firmware:2.35-2.35-0.09-46-09) in 0.06s\n" + ] + } + ], + "source": [ + "ks = Keysight_34465A('Keysight', 'TCPIP0::K-000000-00000::inst0::INSTR')\n", + "agi = Agilent_34400A('Agilent', 'TCPIP0::192.168.15.105::inst0::INSTR')\n", + "qdac = QDac('qdac', 'ASRL4::INSTR')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "station = qc.Station(ks, agi, qdac)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "# set up a NPLC of 0.06 corresponding to an aperture time of 1 ms\n", + "ks.NPLC(0.06)\n", + "agi.NPLC(0.06)\n", + "\n", + "# set the same range on both DMMs\n", + "ks.range(1)\n", + "agi.range(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Part one - raw readings" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reading with displays ON\n", + "----------\n", + "Read 512 values one at a time from Keysight in: 8.587134838104248 s\n", + "Read 512 values one at a time from Agilent in: 4.099916696548462 s\n", + "----------\n", + "Read 1024 values one at a time from Keysight in: 16.83395004272461 s\n", + "Read 1024 values one at a time from Agilent in: 8.229825019836426 s\n", + "----------\n", + "Read 2048 values one at a time from Keysight in: 33.76206350326538 s\n", + "Read 2048 values one at a time from Agilent in: 16.453675985336304 s\n", + "----------\n" + ] + } + ], + "source": [ + "# Preliminary testing, just get N readings from each instrument\n", + "# with the displays on\n", + "\n", + "ks.display_clear() # make sure that the display is on\n", + "agi.display_clear()\n", + "\n", + "print('Reading with displays ON')\n", + "print('-'*10)\n", + "\n", + "for N in [512, 1024, 2048]:\n", + "\n", + " t_start = time.time()\n", + " for ii in range(N):\n", + " ks.volt()\n", + " t_stop = time.time()\n", + " print('Read {} values one at a time from Keysight in: {} s'.format(N, t_stop-t_start))\n", + "\n", + " t_start = time.time()\n", + " for ii in range(N):\n", + " agi.volt()\n", + " t_stop = time.time()\n", + " print('Read {} values one at a time from Agilent in: {} s'.format(N, t_stop-t_start))\n", + "\n", + " print('-'*10)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reading with displays OFF\n", + "----------\n", + "Read 512 values one at a time from Keysight in: 4.5132529735565186 s\n", + "Read 512 values one at a time from Agilent in: 4.098856449127197 s\n", + "----------\n", + "Read 1024 values one at a time from Keysight in: 8.996399164199829 s\n", + "Read 1024 values one at a time from Agilent in: 8.227899074554443 s\n", + "----------\n", + "Read 2048 values one at a time from Keysight in: 17.969755172729492 s\n", + "Read 2048 values one at a time from Agilent in: 16.399696350097656 s\n", + "----------\n" + ] + } + ], + "source": [ + "# The same test, but with the displays off to decrease latency\n", + "\n", + "print('Reading with displays OFF')\n", + "print('-'*10)\n", + "\n", + "for N in [512, 1024, 2048]:\n", + "\n", + " ks.display_text('QCoDeS')\n", + " t_start = time.time()\n", + " for ii in range(N):\n", + " ks.volt()\n", + " t_stop = time.time()\n", + " print('Read {} values one at a time from Keysight in: {} s'.format(N, t_stop-t_start))\n", + " ks.display_clear()\n", + "\n", + " agi.display_text('QCoDes')\n", + " t_start = time.time()\n", + " for ii in range(N):\n", + " agi.volt()\n", + " t_stop = time.time()\n", + " print('Read {} values one at a time from Agilent in: {} s'.format(N, t_stop-t_start))\n", + " agi.display_clear()\n", + "\n", + " print('-'*10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "source": [ + "## Part two - QCoDeS looping" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "### 1D Sweep" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-03-07/#018_testsweep_12-32-13'\n", + " | | | \n", + " Setpoint | qdac_ch42_v_set | ch42_v | (512,)\n", + " Measured | Keysight_volt | volt | (512,)\n", + "started at 2017-03-07 12:32:29\n", + "\n", + "\n", + "Did a 512-point QCoDeS sweep of the QDac/Keysight pair in 16.5 s\n", + "\n", + "\n", + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-03-07/#019_testsweep_12-32-29'\n", + " | | | \n", + " Setpoint | qdac_ch41_v_set | ch41_v | (512,)\n", + " Measured | Agilent_volt | volt | (512,)\n", + "started at 2017-03-07 12:32:46\n", + "\n", + "\n", + "Did a 512-point QCoDeS sweep of the QDac/Agilent pair in 16.5 s\n", + "\n", + "\n" + ] + } + ], + "source": [ + "# Sweep a voltage from 0.2 V to 0.5 V in 512 steps\n", + "# Switch off displays for speed\n", + "\n", + "ks.display_text('QCoDeS')\n", + "agi.display_text('QCoDeS')\n", + "\n", + "N = 512 \n", + "V1 = 0.2\n", + "V2 = 0.5\n", + "dV = (V2-V1)/(N-1) # endpoint included in sweep\n", + "\n", + "loop = qc.Loop(qdac.ch42_v.sweep(V1, V2, dV)).each(ks.volt)\n", + "data = loop.get_data_set(name='testsweep')\n", + "t_start = time.time()\n", + "_ = loop.run()\n", + "t_stop = time.time()\n", + "print('\\n\\nDid a {}-point QCoDeS sweep of the QDac/Keysight pair in {:.1f} s\\n\\n'.format(N, t_stop-t_start))\n", + "\n", + "loop = qc.Loop(qdac.ch41_v.sweep(V1, V2, dV)).each(agi.volt)\n", + "data = loop.get_data_set(name='testsweep')\n", + "t_start = time.time()\n", + "_ = loop.run()\n", + "t_stop = time.time()\n", + "print('\\n\\nDid a {}-point QCoDeS sweep of the QDac/Agilent pair in {:.1f} s\\n\\n'.format(N, t_stop-t_start))\n", + "\n", + "agi.display_clear()\n", + "ks.display_clear()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "### 2D Sweep" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-03-07/#020_testsweep_12-33-46'\n", + " | | | \n", + " Setpoint | qdac_ch42_v_set | ch42_v | (100,)\n", + " Setpoint | qdac_ch41_v_set | ch41_v | (100, 100)\n", + " Measured | Keysight_volt | volt | (100, 100)\n", + "started at 2017-03-07 12:39:09\n", + "\n", + "\n", + "Did a 100x100-point QCoDeS sweep of the QDac/Keysight pair in 323.9 s\n", + "\n", + "\n", + "DataSet:\n", + " mode = DataMode.LOCAL\n", + " location = 'data/2017-03-07/#021_testsweep_12-39-09'\n", + " | | | \n", + " Setpoint | qdac_ch41_v_set | ch41_v | (100,)\n", + " Setpoint | qdac_ch42_v_set | ch42_v | (100, 100)\n", + " Measured | Agilent_volt | volt | (100, 100)\n", + "started at 2017-03-07 12:44:33\n", + "\n", + "\n", + "Did a 100x100-point QCoDeS sweep of the QDac/Agilent pair in 323.8 s\n", + "\n", + "\n" + ] + } + ], + "source": [ + "# Perform the same sweep as before, but this time nested inside another sweep of the same length,\n", + "# i.e. at each point of the original sweep, another QDac channel sweeps its voltage in the same number of steps\n", + "\n", + "ks.display_text('QCoDeS')\n", + "agi.display_text('QCoDeS')\n", + "\n", + "N = 100\n", + "V1 = 0.2\n", + "V2 = 0.5\n", + "dV = (V2-V1)/(N-1)\n", + "\n", + "loop = qc.Loop(qdac.ch42_v.sweep(V1, V2, dV)).loop(qdac.ch41_v.sweep(V1, V2, dV)).each(ks.volt)\n", + "data = loop.get_data_set(name='testsweep')\n", + "t_start = time.time()\n", + "_ = loop.run()\n", + "t_stop = time.time()\n", + "print('\\n\\nDid a {}x{}-point QCoDeS sweep of the QDac/Keysight pair in {:.1f} s\\n\\n'.format(N, N, t_stop-t_start))\n", + "\n", + "loop = qc.Loop(qdac.ch41_v.sweep(V1, V2, dV)).loop(qdac.ch42_v.sweep(V1, V2, dV)).each(agi.volt)\n", + "data = loop.get_data_set(name='testsweep')\n", + "t_start = time.time()\n", + "_ = loop.run()\n", + "t_stop = time.time()\n", + "print('\\n\\nDid a {}x{}-point QCoDeS sweep of the QDac/Agilent pair in {:.1f} s\\n\\n'.format(N, N, t_stop-t_start))\n", + "\n", + "ks.display_clear()\n", + "agi.display_clear()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "ks.close()\n", + "agi.close()\n", + "qdac.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": 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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/examples/Creating Instrument Drivers.ipynb b/docs/examples/Creating Instrument Drivers.ipynb index 807a5dc6eac1..138e33d71f2d 100644 --- a/docs/examples/Creating Instrument Drivers.ipynb +++ b/docs/examples/Creating Instrument Drivers.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Creating QCoDeS instrument drivers" ] @@ -11,7 +14,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -28,7 +33,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Base Classes\n", "\n", @@ -42,7 +50,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## VisaInstrument: Simple example\n", "The Weinschel 8320 driver is about as basic a driver as you can get. It only defines one parameter, \"attenuation\". All the comments here are my additions to describe what's happening." @@ -52,7 +63,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -100,7 +113,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## VisaInstrument: a more involved example\n", "the K2600 breaks one physical instrument into two software instruments, one for each channel. It:\n", @@ -117,7 +133,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -195,7 +213,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## DLL-based instruments\n", "The Alazar cards use their own DLL. C interfaces tend to need a lot of boilerplate, so I'm not going to include it all. The key is: use `Instrument` directly, load the DLL, and have parameters interact with it." @@ -205,7 +226,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -243,7 +266,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Manual instruments\n", "A totally manual instrument (like the ithaco 1211) will contain only `ManualParameter`s. Some instruments may have a mix of manual and standard parameters. Here we also define a new `CurrentParameter` class that uses the ithaco parameters to convert a measured voltage to a current." @@ -253,7 +279,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -360,7 +388,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Custom Parameter classes\n", "\n", @@ -383,7 +414,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Dynamically adding and removing parameters\n", "\n", @@ -401,7 +435,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Functions\n", "\n", @@ -410,7 +447,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Organization\n", "\n", @@ -445,7 +485,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/docs/examples/Datasaving examples.ipynb b/docs/examples/Datasaving examples.ipynb index 1117b4b695bb..9aa2b7746b1e 100644 --- a/docs/examples/Datasaving examples.ipynb +++ b/docs/examples/Datasaving examples.ipynb @@ -4,346 +4,11 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/*\n", - " * Qcodes Jupyter/IPython widgets\n", - " */\n", - "require([\n", - " 'nbextensions/widgets/widgets/js/widget',\n", - " 'nbextensions/widgets/widgets/js/manager'\n", - "], function (widget, manager) {\n", - "\n", - " var UpdateView = widget.DOMWidgetView.extend({\n", - " render: function() {\n", - " window.MYWIDGET = this;\n", - " this._interval = 0;\n", - " this.update();\n", - " },\n", - " update: function() {\n", - " this.display(this.model.get('_message'));\n", - " this.setInterval();\n", - " },\n", - " display: function(message) {\n", - " /*\n", - " * display method: override this for custom display logic\n", - " */\n", - " this.el.innerHTML = message;\n", - " },\n", - " remove: function() {\n", - " clearInterval(this._updater);\n", - " },\n", - " setInterval: function(newInterval) {\n", - " var me = this;\n", - " if(newInterval===undefined) newInterval = me.model.get('interval');\n", - " if(newInterval===me._interval) return;\n", - "\n", - " me._interval = newInterval;\n", - "\n", - " if(me._updater) clearInterval(me._updater);\n", - "\n", - " if(me._interval) {\n", - " me._updater = setInterval(function() {\n", - " me.send({myupdate: true});\n", - " if(!me.model.comm_live) {\n", - " console.log('missing comm, canceling widget updates', me);\n", - " clearInterval(me._updater);\n", - " }\n", - " }, me._interval * 1000);\n", - " }\n", - " }\n", - " });\n", - " manager.WidgetManager.register_widget_view('UpdateView', UpdateView);\n", - "\n", - " var HiddenUpdateView = UpdateView.extend({\n", - " display: function(message) {\n", - " this.$el.hide();\n", - " }\n", - " });\n", - " manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView);\n", - "\n", - " var SubprocessView = UpdateView.extend({\n", - " render: function() {\n", - " var me = this;\n", - " me._interval = 0;\n", - " me._minimize = '';\n", - " me._restore = '';\n", - "\n", - " // max lines of output to show\n", - " me.maxOutputLength = 500;\n", - "\n", - " // in case there is already an outputView present,\n", - " // like from before restarting the kernel\n", - " $('.qcodes-output-view').not(me.$el).remove();\n", - "\n", - " me.$el\n", - " .addClass('qcodes-output-view')\n", - " .attr('qcodes-state', 'docked')\n", - " .html(\n", - " '
' +\n", - " '
' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '
' +\n", - " '
'\n",
-       "                );\n",
-       "\n",
-       "            me.clearButton = me.$el.find('.qcodes-clear-output');\n",
-       "            me.minButton = me.$el.find('.qcodes-minimize');\n",
-       "            me.outputArea = me.$el.find('pre');\n",
-       "            me.subprocessList = me.$el.find('.qcodes-process-list');\n",
-       "            me.abortButton = me.$el.find('.qcodes-abort-loop');\n",
-       "            me.processLinesButton = me.$el.find('.qcodes-processlines')\n",
-       "\n",
-       "            me.outputLines = [];\n",
-       "\n",
-       "            me.clearButton.click(function() {\n",
-       "                me.outputArea.html('');\n",
-       "                me.clearButton.addClass('disabled');\n",
-       "            });\n",
-       "\n",
-       "            me.abortButton.click(function() {\n",
-       "                me.send({abort: true});\n",
-       "            });\n",
-       "\n",
-       "            me.processLinesButton.click(function() {\n",
-       "                // toggle multiline process list display\n",
-       "                me.subprocessesMultiline = !me.subprocessesMultiline;\n",
-       "                me.showSubprocesses();\n",
-       "            });\n",
-       "\n",
-       "            me.$el.find('.js-state').click(function() {\n",
-       "                var state = this.className.substr(this.className.indexOf('qcodes'))\n",
-       "                        .split('-')[1].split(' ')[0];\n",
-       "                me.model.set('_state', state);\n",
-       "            });\n",
-       "\n",
-       "            $(window)\n",
-       "                .off('resize.qcodes')\n",
-       "                .on('resize.qcodes', function() {me.clipBounds();});\n",
-       "\n",
-       "            me.update();\n",
-       "        },\n",
-       "\n",
-       "        updateState: function() {\n",
-       "            var me = this,\n",
-       "                oldState = me.$el.attr('qcodes-state'),\n",
-       "                state = me.model.get('_state');\n",
-       "\n",
-       "            if(state === oldState) return;\n",
-       "\n",
-       "            setTimeout(function() {\n",
-       "                // not sure why I can't pop it out of the widgetarea in render, but it seems that\n",
-       "                // some other bit of code resets the parent after render if I do it there.\n",
-       "                // To be safe, just do it on every state click.\n",
-       "                me.$el.appendTo('body');\n",
-       "\n",
-       "                if(oldState === 'floated') {\n",
-       "                    console.log('here');\n",
-       "                    me.$el.draggable('destroy').css({left:'', top: ''});\n",
-       "                }\n",
-       "\n",
-       "                me.$el.attr('qcodes-state', state);\n",
-       "\n",
-       "                if(state === 'floated') {\n",
-       "                    me.$el\n",
-       "                        .draggable({stop: function() { me.clipBounds(); }})\n",
-       "                        .css({\n",
-       "                            left: window.innerWidth - me.$el.width() - 15,\n",
-       "                            top: window.innerHeight - me.$el.height() - 10\n",
-       "                        });\n",
-       "                }\n",
-       "\n",
-       "                // any previous highlighting is now moot\n",
-       "                me.$el.removeClass('qcodes-highlight');\n",
-       "            }, 0);\n",
-       "\n",
-       "        },\n",
-       "\n",
-       "        clipBounds: function() {\n",
-       "            var me = this;\n",
-       "            if(me.$el.attr('qcodes-state') === 'floated') {\n",
-       "                var bounds = me.$el[0].getBoundingClientRect(),\n",
-       "                    minVis = 40,\n",
-       "                    maxLeft = window.innerWidth - minVis,\n",
-       "                    minLeft = minVis - bounds.width,\n",
-       "                    maxTop = window.innerHeight - minVis;\n",
-       "\n",
-       "                if(bounds.left > maxLeft) me.$el.css('left', maxLeft);\n",
-       "                else if(bounds.left < minLeft) me.$el.css('left', minLeft);\n",
-       "\n",
-       "                if(bounds.top > maxTop) me.$el.css('top', maxTop);\n",
-       "                else if(bounds.top < 0) me.$el.css('top', 0);\n",
-       "            }\n",
-       "        },\n",
-       "\n",
-       "        display: function(message) {\n",
-       "            var me = this;\n",
-       "            if(message) {\n",
-       "                var initialScroll = me.outputArea.scrollTop();\n",
-       "                me.outputArea.scrollTop(me.outputArea.prop('scrollHeight'));\n",
-       "                var scrollBottom = me.outputArea.scrollTop();\n",
-       "\n",
-       "                if(me.$el.attr('qcodes-state') === 'minimized') {\n",
-       "                    // if we add text and the box is minimized, highlight the\n",
-       "                    // title bar to alert the user that there are new messages.\n",
-       "                    // remove then add the class, so we get the animation again\n",
-       "                    // if it's already highlighted\n",
-       "                    me.$el.removeClass('qcodes-highlight');\n",
-       "                    setTimeout(function(){\n",
-       "                        me.$el.addClass('qcodes-highlight');\n",
-       "                    }, 0);\n",
-       "                }\n",
-       "\n",
-       "                var newLines = message.split('\\n'),\n",
-       "                    out = me.outputLines,\n",
-       "                    outLen = out.length;\n",
-       "                if(outLen) out[outLen - 1] += newLines[0];\n",
-       "                else out.push(newLines[0]);\n",
-       "\n",
-       "                for(var i = 1; i < newLines.length; i++) {\n",
-       "                    out.push(newLines[i]);\n",
-       "                }\n",
-       "\n",
-       "                if(out.length > me.maxOutputLength) {\n",
-       "                    out.splice(0, out.length - me.maxOutputLength + 1,\n",
-       "                        '<<< Output clipped >>>');\n",
-       "                }\n",
-       "\n",
-       "                me.outputArea.text(out.join('\\n'));\n",
-       "                me.clearButton.removeClass('disabled');\n",
-       "\n",
-       "                // if we were scrolled to the bottom initially, make sure\n",
-       "                // we stay that way.\n",
-       "                me.outputArea.scrollTop(initialScroll === scrollBottom ?\n",
-       "                    me.outputArea.prop('scrollHeight') : initialScroll);\n",
-       "            }\n",
-       "\n",
-       "            me.showSubprocesses();\n",
-       "            me.updateState();\n",
-       "        },\n",
-       "\n",
-       "        showSubprocesses: function() {\n",
-       "            var me = this,\n",
-       "                replacer = me.subprocessesMultiline ? '
' : ', ',\n", - " processes = (me.model.get('_processes') || '')\n", - " .replace(/\\n/g, '>' + replacer + '<');\n", - "\n", - " if(processes) processes = '<' + processes + '>';\n", - " else processes = 'No subprocesses';\n", - "\n", - " me.abortButton.toggleClass('disabled', processes.indexOf('Measurement')===-1);\n", - "\n", - " me.subprocessList.html(processes);\n", - " }\n", - " });\n", - " manager.WidgetManager.register_widget_view('SubprocessView', SubprocessView);\n", - "});\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "%matplotlib nbagg\n", "import numpy as np\n", @@ -355,7 +20,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -381,7 +48,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -392,7 +61,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Start a loop and generate data from dummy instruments " ] @@ -401,7 +73,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -412,7 +86,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -440,10 +116,12 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": { "collapsed": false, - "scrolled": true + "deletable": true, + "editable": true, + "scrolled": false }, "outputs": [ { @@ -451,27 +129,27 @@ "output_type": "stream", "text": [ "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '2016-09-04/13-24-02_MockParabola_run'\n", + " location = 'data/2017-03-10/#017_MockParabola_run_15-44-02'\n", " | | | \n", " Setpoint | MockParabola_x_set | x | (10,)\n", " Measured | MockParabola_skewed_parabola | skewed_parabola | (10,)\n", - "started at 2016-09-04 13:24:02\n" + "started at 2017-03-10 15:44:02\n" ] } ], "source": [ "loop = qc.Loop(station.MockParabola.x[-100:100:20]).each(station.MockParabola.skewed_parabola)\n", - "data_l = loop.run(name='MockParabola_run', formatter=qc.data.gnuplot_format.GNUPlotFormat(), \n", - " background=False, data_manager=False)\n", + "data_l = loop.run(name='MockParabola_run', formatter=qc.data.gnuplot_format.GNUPlotFormat())\n", "\n" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -479,13 +157,12 @@ "output_type": "stream", "text": [ "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '2016-09-04/13-24-02_MockParabola_run_002'\n", + " location = 'data/2017-03-10/#018_MockParabola_run_15-44-07'\n", " | | | \n", " Setpoint | MockParabola_x_set | x | (10,)\n", " Setpoint | MockParabola_y_set | y | (10, 15)\n", " Measured | MockParabola_skewed_parabola | skewed_parabola | (10, 15)\n", - "started at 2016-09-04 13:24:03\n" + "started at 2017-03-10 15:44:07\n" ] } ], @@ -494,14 +171,16 @@ "h5fmt = hdf5_format.HDF5Format()\n", "loop = qc.Loop(station.MockParabola.x[-100:100:20]).loop(\n", " station.MockParabola.y[-100:50:10]).each(station.MockParabola.skewed_parabola)\n", - "data_l = loop.run(name='MockParabola_run', formatter=h5fmt, background=False, data_manager=False)\n" + "data_l = loop.run(name='MockParabola_run', formatter=h5fmt)\n" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -515,16 +194,21 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Run the tests for the dataformat" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [ @@ -545,7 +229,22 @@ "test_incremental_write (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", "test_loop_writing (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... root - WARNING - Error getting or interpreting *IDN?: ''\n", "ok\n", - "test_loop_writing_2D (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... root - WARNING - Error getting or interpreting *IDN?: ''\n" + "test_loop_writing_2D (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... root - WARNING - Error getting or interpreting *IDN?: ''\n", + "ok\n", + "test_metadata_write_read (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", + "test_read_writing_dicts_withlists_to_hdf5 (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", + "test_reading_into_existing_data_array (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", + "test_str_to_bool (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", + "test_writing_metadata (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", + "test_writing_unsupported_types_to_hdf5 (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... root - WARNING - List of type \"\" for \"list_of_dataset\":\"[DataSet:\n", + " location = '/Users/unga/src/Qcodes/qcodes/unittest_data/2017-03-10/#016_test_missing_attr_15-44-09'\n", + " | | | , DataSet:\n", + " location = '/Users/unga/src/Qcodes/qcodes/unittest_data/2017-03-10/#016_test_missing_attr_15-44-09'\n", + " | | | ]\" not supported, storing as string\n", + "root - WARNING - Type \"\" for \"nested_dataset\":\"DataSet:\n", + " location = '/Users/unga/src/Qcodes/qcodes/unittest_data/2017-03-10/#016_test_missing_attr_15-44-09'\n", + " | | | \" not supported, storing as string\n", + "root - WARNING - List of mixed type for \"\":\"list_of_mixed_type\" not supported, storing as string\n" ] }, { @@ -553,60 +252,28 @@ "output_type": "stream", "text": [ "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '/Users/Adriaan/GitHubRepos/DiCarloLab_Repositories/Qcodes/qcodes/unittest_data/2016-09-04/#631_MockLoop_hdf5_test_13-24-03'\n", + " location = '/Users/unga/src/Qcodes/qcodes/unittest_data/2017-03-10/#010_MockLoop_hdf5_test_15-44-09'\n", " | | | \n", " Setpoint | Loop_writing_test_x_set | x | (10,)\n", " Measured | Loop_writing_test_skewed_parabola | skewed_parabola | (10,)\n", - "started at 2016-09-04 13:24:04\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "ok\n", - "test_metadata_write_read (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", - "test_read_writing_dicts_withlists_to_hdf5 (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", - "test_reading_into_existing_data_array (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... " - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "started at 2017-03-10 15:44:09\n", "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '/Users/Adriaan/GitHubRepos/DiCarloLab_Repositories/Qcodes/qcodes/unittest_data/2016-09-04/#632_MockLoop_hdf5_test_13-24-04'\n", + " location = '/Users/unga/src/Qcodes/qcodes/unittest_data/2017-03-10/#011_MockLoop_hdf5_test_15-44-09'\n", " | | | \n", " Setpoint | Loop_writing_test_2D_x_set | x | (10,)\n", " Setpoint | Loop_writing_test_2D_y_set | y | (10, 10)\n", " Measured | Loop_writing_test_2D_skewed_parabola | skewed_parabola | (10, 10)\n", - "started at 2016-09-04 13:24:04\n" + "started at 2017-03-10 15:44:09\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "ok\n", - "test_str_to_bool (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... ok\n", - "test_writing_unsupported_types_to_hdf5 (qcodes.tests.test_hdf5formatter.TestHDF5_Format) ... root - WARNING - List of mixed type for \"\":\"list_of_mixed_type\" not supported, storing as string\n", - "root - WARNING - List of type \"\" for \"list_of_dataset\":\"[DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '/Users/Adriaan/GitHubRepos/DiCarloLab_Repositories/Qcodes/qcodes/unittest_data/2016-09-04/#636_test_missing_attr_13-24-04'\n", - " | | | , DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '/Users/Adriaan/GitHubRepos/DiCarloLab_Repositories/Qcodes/qcodes/unittest_data/2016-09-04/#636_test_missing_attr_13-24-04'\n", - " | | | ]\" not supported, storing as string\n", - "root - WARNING - Type \"\" for \"nested_dataset\":\"DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '/Users/Adriaan/GitHubRepos/DiCarloLab_Repositories/Qcodes/qcodes/unittest_data/2016-09-04/#636_test_missing_attr_13-24-04'\n", - " | | | \" not supported, storing as string\n", "ok\n", "\n", "----------------------------------------------------------------------\n", - "Ran 16 tests in 1.970s\n", + "Ran 17 tests in 0.348s\n", "\n", "OK\n" ] @@ -624,6 +291,15 @@ "suite = unittest.defaultTestLoader.loadTestsFromTestCase(tst)\n", "result = unittest.TextTestRunner(verbosity=2).run(suite)\n" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { @@ -642,7 +318,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.5.2" }, "widgets": { "state": {}, diff --git a/docs/examples/Measure without a Loop.ipynb b/docs/examples/Measure without a Loop.ipynb index 828db49fce49..f59aeebab111 100644 --- a/docs/examples/Measure without a Loop.ipynb +++ b/docs/examples/Measure without a Loop.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Measure without a Loop\n", "\n", @@ -13,358 +16,38 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, - "outputs": [ - { - "data": { - "application/javascript": [ - "/*\n", - " * Qcodes Jupyter/IPython widgets\n", - " */\n", - "require([\n", - " 'nbextensions/widgets/widgets/js/widget',\n", - " 'nbextensions/widgets/widgets/js/manager'\n", - "], function (widget, manager) {\n", - "\n", - " var UpdateView = widget.DOMWidgetView.extend({\n", - " render: function() {\n", - " window.MYWIDGET = this;\n", - " this._interval = 0;\n", - " this.update();\n", - " },\n", - " update: function() {\n", - " this.display(this.model.get('_message'));\n", - " this.setInterval();\n", - " },\n", - " display: function(message) {\n", - " /*\n", - " * display method: override this for custom display logic\n", - " */\n", - " this.el.innerHTML = message;\n", - " },\n", - " remove: function() {\n", - " clearInterval(this._updater);\n", - " },\n", - " setInterval: function(newInterval) {\n", - " var me = this;\n", - " if(newInterval===undefined) newInterval = me.model.get('interval');\n", - " if(newInterval===me._interval) return;\n", - "\n", - " me._interval = newInterval;\n", - "\n", - " if(me._updater) clearInterval(me._updater);\n", - "\n", - " if(me._interval) {\n", - " me._updater = setInterval(function() {\n", - " me.send({myupdate: true});\n", - " if(!me.model.comm_live) {\n", - " console.log('missing comm, canceling widget updates', me);\n", - " clearInterval(me._updater);\n", - " }\n", - " }, me._interval * 1000);\n", - " }\n", - " }\n", - " });\n", - " manager.WidgetManager.register_widget_view('UpdateView', UpdateView);\n", - "\n", - " var HiddenUpdateView = UpdateView.extend({\n", - " display: function(message) {\n", - " this.$el.hide();\n", - " }\n", - " });\n", - " manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView);\n", - "\n", - " var SubprocessView = UpdateView.extend({\n", - " render: function() {\n", - " var me = this;\n", - " me._interval = 0;\n", - " me._minimize = '';\n", - " me._restore = '';\n", - "\n", - " // max lines of output to show\n", - " me.maxOutputLength = 500;\n", - "\n", - " // in case there is already an outputView present,\n", - " // like from before restarting the kernel\n", - " $('.qcodes-output-view').not(me.$el).remove();\n", - "\n", - " me.$el\n", - " .addClass('qcodes-output-view')\n", - " .attr('qcodes-state', 'docked')\n", - " .html(\n", - " '
' +\n", - " '
' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '' +\n", - " '
' +\n", - " '
'\n",
-       "                );\n",
-       "\n",
-       "            me.clearButton = me.$el.find('.qcodes-clear-output');\n",
-       "            me.minButton = me.$el.find('.qcodes-minimize');\n",
-       "            me.outputArea = me.$el.find('pre');\n",
-       "            me.subprocessList = me.$el.find('.qcodes-process-list');\n",
-       "            me.abortButton = me.$el.find('.qcodes-abort-loop');\n",
-       "            me.processLinesButton = me.$el.find('.qcodes-processlines')\n",
-       "\n",
-       "            me.outputLines = [];\n",
-       "\n",
-       "            me.clearButton.click(function() {\n",
-       "                me.outputArea.html('');\n",
-       "                me.clearButton.addClass('disabled');\n",
-       "            });\n",
-       "\n",
-       "            me.abortButton.click(function() {\n",
-       "                me.send({abort: true});\n",
-       "            });\n",
-       "\n",
-       "            me.processLinesButton.click(function() {\n",
-       "                // toggle multiline process list display\n",
-       "                me.subprocessesMultiline = !me.subprocessesMultiline;\n",
-       "                me.showSubprocesses();\n",
-       "            });\n",
-       "\n",
-       "            me.$el.find('.js-state').click(function() {\n",
-       "                var state = this.className.substr(this.className.indexOf('qcodes'))\n",
-       "                        .split('-')[1].split(' ')[0];\n",
-       "                me.model.set('_state', state);\n",
-       "            });\n",
-       "\n",
-       "            $(window)\n",
-       "                .off('resize.qcodes')\n",
-       "                .on('resize.qcodes', function() {me.clipBounds();});\n",
-       "\n",
-       "            me.update();\n",
-       "        },\n",
-       "\n",
-       "        updateState: function() {\n",
-       "            var me = this,\n",
-       "                oldState = me.$el.attr('qcodes-state'),\n",
-       "                state = me.model.get('_state');\n",
-       "\n",
-       "            if(state === oldState) return;\n",
-       "\n",
-       "            setTimeout(function() {\n",
-       "                // not sure why I can't pop it out of the widgetarea in render, but it seems that\n",
-       "                // some other bit of code resets the parent after render if I do it there.\n",
-       "                // To be safe, just do it on every state click.\n",
-       "                me.$el.appendTo('body');\n",
-       "\n",
-       "                if(oldState === 'floated') {\n",
-       "                    console.log('here');\n",
-       "                    me.$el.draggable('destroy').css({left:'', top: ''});\n",
-       "                }\n",
-       "\n",
-       "                me.$el.attr('qcodes-state', state);\n",
-       "\n",
-       "                if(state === 'floated') {\n",
-       "                    me.$el\n",
-       "                        .draggable({stop: function() { me.clipBounds(); }})\n",
-       "                        .css({\n",
-       "                            left: window.innerWidth - me.$el.width() - 15,\n",
-       "                            top: window.innerHeight - me.$el.height() - 10\n",
-       "                        });\n",
-       "                }\n",
-       "\n",
-       "                // any previous highlighting is now moot\n",
-       "                me.$el.removeClass('qcodes-highlight');\n",
-       "            }, 0);\n",
-       "\n",
-       "        },\n",
-       "\n",
-       "        clipBounds: function() {\n",
-       "            var me = this;\n",
-       "            if(me.$el.attr('qcodes-state') === 'floated') {\n",
-       "                var bounds = me.$el[0].getBoundingClientRect(),\n",
-       "                    minVis = 40,\n",
-       "                    maxLeft = window.innerWidth - minVis,\n",
-       "                    minLeft = minVis - bounds.width,\n",
-       "                    maxTop = window.innerHeight - minVis;\n",
-       "\n",
-       "                if(bounds.left > maxLeft) me.$el.css('left', maxLeft);\n",
-       "                else if(bounds.left < minLeft) me.$el.css('left', minLeft);\n",
-       "\n",
-       "                if(bounds.top > maxTop) me.$el.css('top', maxTop);\n",
-       "                else if(bounds.top < 0) me.$el.css('top', 0);\n",
-       "            }\n",
-       "        },\n",
-       "\n",
-       "        display: function(message) {\n",
-       "            var me = this;\n",
-       "            if(message) {\n",
-       "                var initialScroll = me.outputArea.scrollTop();\n",
-       "                me.outputArea.scrollTop(me.outputArea.prop('scrollHeight'));\n",
-       "                var scrollBottom = me.outputArea.scrollTop();\n",
-       "\n",
-       "                if(me.$el.attr('qcodes-state') === 'minimized') {\n",
-       "                    // if we add text and the box is minimized, highlight the\n",
-       "                    // title bar to alert the user that there are new messages.\n",
-       "                    // remove then add the class, so we get the animation again\n",
-       "                    // if it's already highlighted\n",
-       "                    me.$el.removeClass('qcodes-highlight');\n",
-       "                    setTimeout(function(){\n",
-       "                        me.$el.addClass('qcodes-highlight');\n",
-       "                    }, 0);\n",
-       "                }\n",
-       "\n",
-       "                var newLines = message.split('\\n'),\n",
-       "                    out = me.outputLines,\n",
-       "                    outLen = out.length;\n",
-       "                if(outLen) out[outLen - 1] += newLines[0];\n",
-       "                else out.push(newLines[0]);\n",
-       "\n",
-       "                for(var i = 1; i < newLines.length; i++) {\n",
-       "                    out.push(newLines[i]);\n",
-       "                }\n",
-       "\n",
-       "                if(out.length > me.maxOutputLength) {\n",
-       "                    out.splice(0, out.length - me.maxOutputLength + 1,\n",
-       "                        '<<< Output clipped >>>');\n",
-       "                }\n",
-       "\n",
-       "                me.outputArea.text(out.join('\\n'));\n",
-       "                me.clearButton.removeClass('disabled');\n",
-       "\n",
-       "                // if we were scrolled to the bottom initially, make sure\n",
-       "                // we stay that way.\n",
-       "                me.outputArea.scrollTop(initialScroll === scrollBottom ?\n",
-       "                    me.outputArea.prop('scrollHeight') : initialScroll);\n",
-       "            }\n",
-       "\n",
-       "            me.showSubprocesses();\n",
-       "            me.updateState();\n",
-       "        },\n",
-       "\n",
-       "        showSubprocesses: function() {\n",
-       "            var me = this,\n",
-       "                replacer = me.subprocessesMultiline ? '
' : ', ',\n", - " processes = (me.model.get('_processes') || '')\n", - " .replace(/\\n/g, '>' + replacer + '<');\n", - "\n", - " if(processes) processes = '<' + processes + '>';\n", - " else processes = 'No subprocesses';\n", - "\n", - " me.abortButton.toggleClass('disabled', processes.indexOf('Measurement')===-1);\n", - "\n", - " me.subprocessList.html(processes);\n", - " }\n", - " });\n", - " manager.WidgetManager.register_widget_view('SubprocessView', SubprocessView);\n", - "});\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "# setup\n", "%matplotlib nbagg\n", - "import numpy as np\n", - "import qcodes as qc" + "import qcodes as qc\n", + "# import dummy driver for the tutorial\n", + "from qcodes.tests.instrument_mocks import DummyInstrument\n", + "from qcodes.instrument.mock import ArrayGetter\n", + "\n", + "dac1 = DummyInstrument(name=\"dac\")\n", + "dac2 = DummyInstrument(name=\"dac2\")\n", + "# the default dummy instrument returns always a constant value, in the following line we make it random \n", + "# just for the looks 💅\n", + "import random\n", + "dac2.dac2.get = lambda: random.randint(0,100)\n", + "\n", + "# The station is a container for all instruments that makes it easy \n", + "# to log meta-data\n", + "station = qc.Station(dac1, dac2)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Instantiates all the instruments needed for the demo\n", "\n", @@ -372,34 +55,11 @@ ] }, { - "cell_type": "code", - "execution_count": 2, + "cell_type": "markdown", "metadata": { - "collapsed": true + "deletable": true, + "editable": true }, - "outputs": [], - "source": [ - "from toymodel import AModel, MockGates, MockSource, MockMeter, AverageAndRaw\n", - "from qcodes.instrument.mock import ArrayGetter\n", - "\n", - "# now create this \"experiment\", note that all these are instruments \n", - "model = AModel()\n", - "gates = MockGates('gates', model=model)\n", - "source = MockSource('source', model=model)\n", - "meter = MockMeter('meter', model=model)\n", - "\n", - "# The station is a container for all instruments that makes it easy \n", - "# to log meta-data\n", - "station = qc.Station(gates, source, meter)\n", - "\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" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, "source": [ "### Only array output\n", "The arguments to Measure are all the same actions you use in a Loop.\n", @@ -408,74 +68,21 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '2016-10-20/12-14-31'\n", - " | | | \n", - " Measured | chan2_1 | chan2 | (100,)\n", - " Measured | meter_amplitude_1_0 | amplitude | (100,)\n", - " Measured | chan2_3 | chan2 | (100,)\n", - " Measured | meter_amplitude_3_0 | amplitude | (100,)\n", - "acquired at 2016-10-20 12:14:31\n" - ] - } - ], + "outputs": [], "source": [ "data = qc.Measure(\n", - " qc.Task(c0.set, 0),\n", - " ArrayGetter(meter.amplitude, c2[-10:10:0.2], 0.001),\n", - " qc.Task(c0.set, 2),\n", - " ArrayGetter(meter.amplitude, c2[-10:10:0.2], 0.001)\n", + " qc.Task(dac1.dac1.set, 0),\n", + " ArrayGetter(dac1.dac2, dac1.dac3.sweep(-10,10,0.2), 0.001),\n", + " qc.Task(dac1.dac1.set, 2),\n", + " ArrayGetter(dac1.dac2, dac1.dac3.sweep(-10,10,0.2), 0.001),\n", ").run()" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Output with scalars\n", - "Scalars still get treated as data, but it's not exactly natural:\n", - "- They still get put into arrays, just 1D 1-element arrays.\n", - "- They need a setpoint array, at least for now, so we make a fake one \"single_set.\" There's something nice about this, actually, in that it makes it absolutely clear that these items are not really arrays." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = '2016-10-20/12-14-35'\n", - " | | | \n", - " Setpoint | single_set | single | (1,)\n", - " Measured | gates_chan0 | chan0 | (1,)\n", - " Measured | gates_chan1 | chan1 | (1,)\n", - " Measured | chan2 | chan2 | (100,)\n", - " Measured | meter_amplitude | amplitude | (100,)\n", - " Measured | meter_avg_amplitude | avg_amplitude | (1,)\n", - "acquired at 2016-10-20 12:14:35\n" - ] - } - ], - "source": [ - "data = qc.Measure(c0, c1, AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)).run()" - ] } ], "metadata": { @@ -494,7 +101,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/docs/examples/Metadata with instruments.ipynb b/docs/examples/Metadata with instruments.ipynb index 8b5905412aa9..21cf0b24cb17 100644 --- a/docs/examples/Metadata with instruments.ipynb +++ b/docs/examples/Metadata with instruments.ipynb @@ -4,7 +4,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -247,19 +249,16 @@ ], "source": [ "from pprint import pprint\n", - "\n", - "import qcodes as qc\n", - "\n", - "qc.set_mp_method('spawn')\n", - "\n", - "qc.show_subprocess_widget()" + "import qcodes as qc" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -294,6 +293,8 @@ "execution_count": 3, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [ @@ -647,6 +648,8 @@ "execution_count": 4, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": false }, "outputs": [], @@ -662,7 +665,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -684,7 +689,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -711,7 +718,9 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [] @@ -733,7 +742,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/docs/examples/Parameters.ipynb b/docs/examples/Parameters.ipynb index b279762f0771..722840217ba9 100644 --- a/docs/examples/Parameters.ipynb +++ b/docs/examples/Parameters.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Parameters in QCoDeS" ] @@ -11,7 +14,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": true + "collapsed": true, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -21,7 +26,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "QCoDeS provides 5 classes of parameter built in. Three base classes (which must be subclassed to use):\n", "- `Parameter` represents a single value at a time\n", @@ -40,7 +48,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Parameter\n", "Most of the time you can use `StandardParameter` directly; even if you have custom `get`/`set` functions, but sometimes it's useful to subclass `Parameter`:" @@ -50,7 +61,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -94,7 +107,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -130,7 +145,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -166,7 +183,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## ArrayParameter\n", "For actions that create a whole array of values at once. When you use it in a `Loop`, it makes a single `DataArray` with the array returned by `get` nested inside extra dimension(s) for the loop.\n", @@ -178,7 +198,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -223,7 +245,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -261,7 +285,9 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -336,7 +362,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## MultiParameter\n", "Return multiple items at once, where each item can be a single value or an array. When you use it in a `Loop`, it makes a separate `DataArray` for each item.\n", @@ -350,7 +379,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -391,7 +422,9 @@ "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -427,7 +460,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -467,7 +502,9 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -546,7 +583,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.0" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/docs/examples/Qcodes example with Keysight 33500B.ipynb b/docs/examples/Qcodes example with Keysight 33500B.ipynb new file mode 100644 index 000000000000..6c46366b7663 --- /dev/null +++ b/docs/examples/Qcodes example with Keysight 33500B.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyqtgraph plotting not supported, try \"from qcodes.plots.pyqtgraph import QtPlot\" to see the full error\n" + ] + } + ], + "source": [ + "import qcodes as qc\n", + "from qcodes.instrument_drivers.Keysight.Keysight_33500B import Keysight_33500B" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ks = Keysight_33500B('ks', 'TCPIP0::K-33522B-00256::inst0::INSTR')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# SET up a sawtooth\n", + "ks.ch1_function_type('RAMP')\n", + "ks.ch1_ramp_symmetry(100)\n", + "ks.ch1_amplitude_unit('VPP')\n", + "ks.ch1_amplitude(1)\n", + "ks.ch1_offset(0)\n", + "ks.ch1_frequency(2e3)\n", + "ks.sync_source(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Start it\n", + "ks.sync_output('ON')\n", + "ks.ch1_output('ON')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "ks.ch1_frequency(1e3)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# stop it\n", + "ks.sync_output('OFF')\n", + "ks.ch1_output('OFF')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ks.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/examples/Qcodes example with ZI UHF-LI.ipynb b/docs/examples/Qcodes example with ZI UHF-LI.ipynb new file mode 100644 index 000000000000..e90b3d1fb775 --- /dev/null +++ b/docs/examples/Qcodes example with ZI UHF-LI.ipynb @@ -0,0 +1,3144 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# QCoDeS Example with ZI UHF-LI\n", + "\n", + "\n", + "## Table of Contents\n", + "\n", + "* [Prerequisites](#prereq)\n", + "* [Basic Usage of the ZI UHF-LI](#basic)\n", + " * [Signal Inputs](#sigins)\n", + " * [Demodulators](#demods)\n", + " * [Signal Outputs](#sigouts)\n", + "* [Using the Sweeper](#sweeper)\n", + "* [Using the Scope](#scope)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true, + "scrolled": true + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import time\n", + "import logging\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "plt.ion()\n", + "\n", + "import qcodes as qc\n", + "from qcodes.instrument_drivers.ZI.ZIUHFLI import ZIUHFLI\n", + "\n", + "log = logging.getLogger(__name__)\n", + "log.setLevel(logging.INFO)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "# Prerequisites \n", + "\n", + "It is necessary to download and install the ZI Lab One software. Additionally, both the data server and the web server must run, and a connection to the instrument must be instantiated (this can be done via the web interface). This example notebook makes no assumptions on what is connected to the instrument (but if you want nice data plots, go ahead and connect something interesting)." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true, + "scrolled": false + }, + "outputs": [], + "source": [ + "# Instantiate the QCoDeS instrument\n", + "zi = ZIUHFLI('ZIUHFLI', 'dev2235')\n", + "\n", + "# Bind it to a QCoDeS station for later use (one should ALWAYS do this with instruments)\n", + "station = qc.Station(zi)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "# Basic Usage of the ZI UHF-LI \n", + "\n", + "Most of the \"front panel\" (i.e. the Web UI) `Lock-In` and `signal input` settings are available as parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oscillator 2 has frequency: 752100 Hz\n" + ] + } + ], + "source": [ + "zi.oscillator2_freq.set(752.1e3)\n", + "print('Oscillator 2 has frequency: {:.0f} Hz'.format(zi.oscillator2_freq.get()))\n", + "zi.signal_input1_range(1)\n", + "zi.signal_input1_scaling(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Signal Inputs " + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available signal input settings:\n", + "\n", + " signal_input1_AC, AC coupling ()\n", + " signal_input1_impedance, Input impedance ()\n", + " signal_input1_scaling, Input scaling ()\n", + " signal_input1_diff, Input signal subtraction ()\n", + " signal_input1_range, Input range (V)\n" + ] + } + ], + "source": [ + "print('Available signal input settings:\\n')\n", + "for param in [p for p in zi.parameters if 'signal_input1' in p]:\n", + " print(' {}, {} ({})'.format(param, zi.parameters[param].label, zi.parameters[param].unit))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Demodulators " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available demodulator settings:\n", + "\n", + " demod1_streaming, Data streaming ()\n", + " demod1_timeconstant, Filter time constant (s)\n", + " demod1_samplerate, Sample rate (Sa/s)\n", + " demod1_phaseshift, Phase shift (degrees)\n", + " demod1_sinc, Sinc filter ()\n", + " demod1_harmonic, Reference frequency multiplication factor ()\n", + " demod1_signalin, Signal input ()\n", + " demod1_order, Filter order ()\n", + " demod1_trigger, Trigger ()\n" + ] + } + ], + "source": [ + "print('Available demodulator settings:\\n')\n", + "for param in [p for p in zi.parameters if 'demod1' in p]:\n", + " print(' {}, {} ({})'.format(param, zi.parameters[param].label, zi.parameters[param].unit))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Signal Outputs " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available signal output settings:\n", + "\n", + " signal_output1_enable, Enable signal output's amplitude. ()\n", + " signal_output1_offset, Signal output offset (V)\n", + " signal_output1_imp50, Switch to turn on 50 Ohm impedance ()\n", + " signal_output1_on, Turn signal output on and off. ()\n", + " signal_output1_range, Signal output range ()\n", + " signal_output1_autorange, Enable signal output range. ()\n", + " signal_output1_ampdef, Signal output amplitude's definition (V)\n", + " signal_output1_amplitude, Signal output amplitude (V)\n" + ] + } + ], + "source": [ + "print('Available signal output settings:\\n')\n", + "for param in [p for p in zi.parameters if 'signal_output1' in p]:\n", + " print(' {}, {} ({})'.format(param, zi.parameters[param].label, zi.parameters[param].unit))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "# Using the sweeper \n", + "\n", + "The QCoDeS sweep is fully indendent of the GUI Sweep, meaning that parameters set in QCoDeS will not affect the GUI sweep and vice versa.\n", + "\n", + "The sweeper settings are configured via a bunch of parameters, all named `sweeper_XXX`.\n", + "This configures the x-axis of the sweep as well as the sweep acquisition settings. To learn more about what a certain parameter does, it is sometimes helpful to print its `__doc__` attribute.\n", + "\n", + "Which signals are **returned** by the sweeper is controlled by adding (removing) signals to (from) the sweep.\n", + "\n", + "The sweep settings can be displayed with the `print_sweeper_settings` command.\n", + "\n", + "Before the sweep can be performed, it must be built. \n", + "This is done with the Sweep parameter, which is the parameter holding the sweep data.\n", + "Note that building the sweep may change some of the time constants, and in particular change the sweep time. In case of doubt, re-run `print_sweeper_settings`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "# Set up a sweep sweeping an internal oscillator frequency from 1 MHz to 5 MHz\n", + "# We want the sweeper to sweep linearly over 200 points\n", + "zi.sweeper_param('Osc 1 Frequency')\n", + "zi.sweeper_xmapping('lin')\n", + "zi.sweeper_start(1e6)\n", + "zi.sweeper_stop(10e6)\n", + "zi.sweeper_samplecount(100)\n", + "zi.sweeper_BWmode('fixed')\n", + "zi.sweeper_BW(250)\n", + "zi.sweeper_order(4)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " For each sweep point, the demodulator\n", + " filter bandwidth (time constant) may\n", + " be either set automatically, be the\n", + " current demodulator bandwidth or be\n", + " a fixed number; the sweeper_BW\n", + " parameter.\n", + " \r\n", + "\r\n", + "Parameter class:\r\n", + "\r\n", + "* `name` sweeper_BWmode\r\n", + "* `label` Sweeper bandwidth control mode\r\n", + "* `unit` \r\n", + "* `vals` \n" + ] + } + ], + "source": [ + "# I wonder what the sweeper BWmode does...\n", + "print(zi.sweeper_BWmode.__doc__)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "# Add three signals to the sweep, all measured on demodulator 1\n", + "zi.add_signal_to_sweeper(1, 'Xrms')\n", + "zi.add_signal_to_sweeper(1, 'Yrms')\n", + "zi.add_signal_to_sweeper(1, 'Rrms')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "# Make sure that demodulator 1 is measuring what and as it should\n", + "zi.demod1_trigger('Continuous')\n", + "zi.demod1_signalin('Sig In 1')" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACQUISITION\n", + " Sweeper bandwidth control mode: fixed ()\n", + " Fixed bandwidth sweeper bandwidth (NEP): 50.0 ()\n", + " Sweeper filter order: 1 ()\n", + " Minimal no. of samples to average at each sweep point: 25 ()\n", + " Minimal averaging time: 0.1 (s)\n", + " Minimal settling time for the sweeper: 1e-06 (s)\n", + " Sweep filter settling time: 4.605170185988091 (dim. less.)\n", + "HORISONTAL\n", + " Start value of the sweep: 1000000.0\n", + " Stop value of the sweep: 10000000.0\n", + " Units of sweep x-axis: Hz\n", + " Length of the sweep (pts): 25\n", + " Parameter to sweep (sweep x-axis): Osc 1 Frequency\n", + " Sweep mode: Sequential\n", + " Sweep timeout: 600\n", + "VERTICAL\n", + " Signal 1: Demodulator 1: Xrms\n", + " Signal 2: Demodulator 1: Yrms\n", + " Signal 3: Demodulator 1: Rrms\n", + "DEMODULATORS\n", + " Demodulator 1: Filter time constant: 0.000311 (s)\n", + " Demodulator 1: Filter order: 4.000000 ()\n", + " Demodulator 1: Sample rate: 1716.613770 (Sa/s)\n", + "META\n", + " Expected sweep time: 0.9 (s)\n", + " Sweep timeout: 600 (s)\n", + " Sweep built and ready to execute: False\n" + ] + } + ], + "source": [ + "# I wonder what kind of sweep we have made now...\n", + "zi.print_sweeper_settings()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACQUISITION\n", + " Sweeper bandwidth control mode: fixed ()\n", + " Fixed bandwidth sweeper bandwidth (NEP): 250.0 ()\n", + " Sweeper filter order: 4 ()\n", + " Minimal no. of samples to average at each sweep point: 25 ()\n", + " Minimal averaging time: 0.1 (s)\n", + " Minimal settling time for the sweeper: 1e-06 (s)\n", + " Sweep filter settling time: 9.998049677807453 (dim. less.)\n", + "HORISONTAL\n", + " Start value of the sweep: 1000000.0\n", + " Stop value of the sweep: 10000000.0\n", + " Units of sweep x-axis: Hz\n", + " Length of the sweep (pts): 100\n", + " Parameter to sweep (sweep x-axis): Osc 1 Frequency\n", + " Sweep mode: Sequential\n", + " Sweep timeout: 600\n", + "VERTICAL\n", + " Signal 1: Demodulator 1: Xrms\n", + " Signal 2: Demodulator 1: Yrms\n", + " Signal 3: Demodulator 1: Rrms\n", + "DEMODULATORS\n", + " Demodulator 1: Filter time constant: 0.000311 (s)\n", + " Demodulator 1: Filter order: 4.000000 ()\n", + " Demodulator 1: Sample rate: 1716.613770 (Sa/s)\n", + "META\n", + " Expected sweep time: 1.8 (s)\n", + " Sweep timeout: 600 (s)\n", + " Sweep built and ready to execute: True\n" + ] + } + ], + "source": [ + "# Gee, that looks good! Note the last line, the sweep is NOT ready to execute.\n", + "zi.Sweep.build_sweep()\n", + "# Now it is!\n", + "zi.print_sweeper_settings()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "# We can now execute the sweeper by simply invoking Sweep.get\n", + "# This returns a tuple with the signals we asked for\n", + "(X, Y, R) = zi.Sweep.get()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\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", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\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 backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\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 * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\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", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\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" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\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", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\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 backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\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 * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\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", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\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": "stderr", - "output_type": "stream", - "text": [ - "WARNING:root:negative delay -0.001320 sec\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = 'data/2016-11-08/#025_2D_test_13-29-23'\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-11-08 13:29:35\n" - ] - } - ], + "outputs": [], "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, plot.save, 0.05).run()" + "dac2.snapshot()" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ - "### The equivalent in QtPlot" + "## Loading data" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true, + "scrolled": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = 'data/2016-11-08/#026_2D_test_13-29-35'\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-11-08 13:29:57\n" - ] - } - ], + "outputs": [], "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(name='2D_test')\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, plotQ.save).run()" + "loaded_data = qc.load_data(\"location\")\n", + "plot = qc.MatPlot(loaded_data.array_id)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ - "### Example same outer loop, different inner loop " + "## Example: multiple 2D measurements with live plotting" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "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(name='TwoD_different_inner_test') " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### several plots updating simultaneously (Currently broken on matplotlib)" + "loop = qc.Loop(dac1.dac1.sweep(0,5,1), 0.1).loop(dac1.dac2.sweep(0,5,1), 0.1).each(\n", + " dac2.dac2\n", + " )\n", + "data = loop.get_data_set(name='2D_test')" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/unga/src/Qcodes/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-11-08/#027_TwoD_different_inner_test_13-29-57'\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-11-08 13:30:32\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, plotQ.save).run()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, + "outputs": [], "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." + "plot = qc.QtPlot()\n", + "plot.add(data.dac2_dac2, figsize=(1200, 500))\n", + "#plot.add(data.dac2_dac3, subplot=2)\n", + "_ = loop.with_bg_task(plot.update, plot.save).run()" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "collapsed": false, - "scrolled": false + "deletable": true, + "editable": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet:\n", - " mode = DataMode.LOCAL\n", - " location = 'data/2016-11-08/#028_TwoD_average_test_13-30-32'\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-11-08 13:30:49\n" - ] - } - ], + "outputs": [], "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(name='TwoD_average_test')\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, plotQ.save).run()" + "plot" ] } ], diff --git a/docs/examples/adaptive_sweep.py b/docs/examples/adaptive_sweep.py deleted file mode 100644 index 709f15114117..000000000000 --- a/docs/examples/adaptive_sweep.py +++ /dev/null @@ -1,103 +0,0 @@ -import qcodes as qc - - -class AdaptiveSweep(qc.SweepValues): - ''' - an example class to show how adaptive sampling might be implemented - this code has not been tested - - usage: - Loop(AdaptiveSweep(param, start, end, target_delta), delay).run() - - inputs: - start: initial parameter value - end: final parameter value - target_delta: change in the measured val to target - max_step: biggest change in parameter value allowed - min_step: smallest change allowed, so we don't sweep forever - measurement_index: which measurement parameter are we feeding back on? - ''' - def __init__(self, parameter, start, end, target_delta, max_step=None, - min_step=None, measurement_index=0): - super().__init__(parameter) - - self._start = start - self._end = end - self._direction = 1 if end > start else -1 - - self._target_delta = target_delta - - self._max_step = max_step or abs(end - start) / 100 - self._min_step = min_step or self._max_step / 100 - - self._measurement_index = measurement_index - - def __iter__(self): - ''' - start or restart the adaptive algorithm - called at the beginning of "for ... in ..." - - in principle, each iteration could base its outputs - on the previous iteration, for example to follow peaks - that move slowly as a function of the outer loop parameter. - but in this simple example we're just basing each point on the - previous two - ''' - self._setting = None - self._step = None - self._measured = None - self._delta = None - self._new_val_count = 0 - - # return self so iteration will call our own __next__ - return self - - def feedback(self, set_values, measured_values): - ''' - the sweep routine will look for a .feedback method - to pass new measurements into the SweepValues object - - it provides: - set_values: sequence of the current sweep parameters - measured_values: sequence of the measured values at this setting - ''' - self._new_val_count += 1 - if self._new_val_count > 1: - # more than one measurement per iteration means we're - # not in the inner loop. in principle one could adaptively - # sample an outer loop too, using the whole line of inner loop - # measurements, but the algorithm here only applies to the inner. - raise RuntimeError( - 'AdaptiveSweep can only be used on the inner loop') - - new_measured = measured_values[self._measurement_index] - - if self._measured is not None: - self._delta = new_measured - self._measured - - self._measured = new_measured - - def __next__(self): - self._new_val_count = 0 - - if self._setting == self._end: - # terminate the iteration if we've already set the endpoint - raise StopIteration - - # target the step so the next delta is target_delta, if data is linear - if self._delta is None: - step = self._min_step # start off slow - else: - step = abs(self._step * self._target_delta / self._delta) - # don't increase too much at once - step = max(step, self._step * 3) - - # constrain it to provide min and max - step = min(max(self._min_step, step), self._max_step) - self._setting += self._direction * step - - # stop at the end - if self._setting * self._direction > self._end * self._direction: - self._setting = self._end - - return self._setting diff --git a/docs/examples/Keithley_example.ipynb b/docs/examples/driver_examples/Keithley_example.ipynb similarity index 85% rename from docs/examples/Keithley_example.ipynb rename to docs/examples/driver_examples/Keithley_example.ipynb index 1d9799c229f8..e5e3d3015002 100644 --- a/docs/examples/Keithley_example.ipynb +++ b/docs/examples/driver_examples/Keithley_example.ipynb @@ -2,7 +2,10 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "# Example script for Keithley driver" ] @@ -11,7 +14,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -257,19 +262,15 @@ "import time\n", "import numpy as np\n", "from imp import reload\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" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "deletable": true, + "editable": true + }, "source": [ "## Keithley driver\n", "\n", @@ -284,7 +285,9 @@ "cell_type": "code", "execution_count": 12, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -300,14 +303,16 @@ ], "source": [ "import qcodes.instrument_drivers\n", - "import qcodes.instrument_drivers.tektronix.Keithley_2700 as keith; reload(keith)" + "import qcodes.instrument_drivers.tektronix.Keithley_2700 as keith" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -327,7 +332,9 @@ "cell_type": "code", "execution_count": 4, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -347,6 +354,8 @@ "execution_count": 5, "metadata": { "collapsed": false, + "deletable": true, + "editable": true, "scrolled": true }, "outputs": [ @@ -372,7 +381,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -400,7 +411,9 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -422,7 +435,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -442,57 +457,6 @@ "source": [ "print(k1.nplc.__doc__)" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test whether the object can be pickled" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "ename": "ValueError", - "evalue": "ctypes objects containing pointers cannot be pickled", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\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[0;32m 2\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mtempfile\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mwith\u001b[0m \u001b[0mtempfile\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mTemporaryFile\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'wb'\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0moutput\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdump\u001b[0m\u001b[1;33m(\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mk1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0moutput\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;31mValueError\u001b[0m: ctypes objects containing pointers cannot be pickled" - ] - } - ], - "source": [ - "import pickle\n", - "import tempfile\n", - "with tempfile.TemporaryFile('wb') as output:\n", - " pickle.dump( (k1, ), output)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -511,7 +475,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/docs/examples/Qcodes example ATS_ONWORK.ipynb b/docs/examples/driver_examples/Qcodes example ATS_ONWORK.ipynb similarity index 100% rename from docs/examples/Qcodes example ATS_ONWORK.ipynb rename to docs/examples/driver_examples/Qcodes example ATS_ONWORK.ipynb diff --git a/docs/examples/Qcodes example with Agilent 34400A.ipynb b/docs/examples/driver_examples/Qcodes example with Agilent 34400A.ipynb similarity index 100% rename from docs/examples/Qcodes example with Agilent 34400A.ipynb rename to docs/examples/driver_examples/Qcodes example with Agilent 34400A.ipynb diff --git a/docs/examples/Qcodes example with Decadac.ipynb b/docs/examples/driver_examples/Qcodes example with Decadac.ipynb similarity index 100% rename from docs/examples/Qcodes example with Decadac.ipynb rename to docs/examples/driver_examples/Qcodes example with Decadac.ipynb diff --git a/docs/examples/Qcodes example with Ithaco.ipynb b/docs/examples/driver_examples/Qcodes example with Ithaco.ipynb similarity index 100% rename from docs/examples/Qcodes example with Ithaco.ipynb rename to docs/examples/driver_examples/Qcodes example with Ithaco.ipynb diff --git a/docs/examples/Qcodes example with Keithley 2600.ipynb b/docs/examples/driver_examples/Qcodes example with Keithley 2600.ipynb similarity index 100% rename from docs/examples/Qcodes example with Keithley 2600.ipynb rename to docs/examples/driver_examples/Qcodes example with Keithley 2600.ipynb diff --git a/docs/examples/Qcodes example with Mercury IPS (Magnet).ipynb b/docs/examples/driver_examples/Qcodes example with Mercury IPS (Magnet).ipynb similarity index 100% rename from docs/examples/Qcodes example with Mercury IPS (Magnet).ipynb rename to docs/examples/driver_examples/Qcodes example with Mercury IPS (Magnet).ipynb diff --git a/docs/examples/Qcodes example with QDac.ipynb b/docs/examples/driver_examples/Qcodes example with QDac.ipynb similarity index 100% rename from docs/examples/Qcodes example with QDac.ipynb rename to docs/examples/driver_examples/Qcodes example with QDac.ipynb diff --git a/docs/examples/Qcodes example with Rohde Schwarz ZN20.ipynb b/docs/examples/driver_examples/Qcodes example with Rohde Schwarz ZN20.ipynb similarity index 100% rename from docs/examples/Qcodes example with Rohde Schwarz ZN20.ipynb rename to docs/examples/driver_examples/Qcodes example with Rohde Schwarz ZN20.ipynb diff --git a/docs/examples/Qcodes example with Tektronix AWG5014C.ipynb b/docs/examples/driver_examples/Qcodes example with Tektronix AWG5014C.ipynb similarity index 100% rename from docs/examples/Qcodes example with Tektronix AWG5014C.ipynb rename to docs/examples/driver_examples/Qcodes example with Tektronix AWG5014C.ipynb diff --git a/docs/examples/Qcodes example with Triton.ipynb b/docs/examples/driver_examples/Qcodes example with Triton.ipynb similarity index 95% rename from docs/examples/Qcodes example with Triton.ipynb rename to docs/examples/driver_examples/Qcodes example with Triton.ipynb index dd3d40e41485..b1737b8c3b4f 100644 --- a/docs/examples/Qcodes example with Triton.ipynb +++ b/docs/examples/driver_examples/Qcodes example with Triton.ipynb @@ -4,7 +4,9 @@ "cell_type": "code", "execution_count": 1, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -250,21 +252,16 @@ "import matplotlib.pyplot as plt\n", "import time\n", "import numpy as np\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" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -301,7 +298,9 @@ "cell_type": "code", "execution_count": 8, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [], "source": [ @@ -312,7 +311,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "collapsed": false, + "deletable": true, + "editable": true }, "outputs": [ { @@ -329,15 +330,6 @@ "source": [ "t.split(':')[1:]" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -356,7 +348,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/docs/examples/Triton1_thermometry.reg b/docs/examples/driver_examples/Triton1_thermometry.reg similarity index 100% rename from docs/examples/Triton1_thermometry.reg rename to docs/examples/driver_examples/Triton1_thermometry.reg diff --git a/docs/examples/testsweep/gates_chan0_set.dat b/docs/examples/testsweep/gates_chan0_set.dat deleted file mode 100644 index 7a747e0d052d..000000000000 --- a/docs/examples/testsweep/gates_chan0_set.dat +++ /dev/null @@ -1,404 +0,0 @@ -# gates_chan0_set meter_amplitude -# "Gate Channel 0 (mV)" "Current (nA)" -# 401 --20 0.117 --19.9 0.117 --19.8 0.115 --19.7 0.111 --19.6 0.106 --19.5 0.099 --19.4 0.092 --19.3 0.085 --19.2 0.077 --19.1 0.071 --19 0.064 --18.9 0.058 --18.8 0.053 --18.7 0.048 --18.6 0.044 --18.5 0.04 --18.4 0.037 --18.3 0.034 --18.2 0.031 --18.1 0.029 --18 0.027 --17.9 0.025 --17.8 0.023 --17.7 0.022 --17.6 0.02 --17.5 0.019 --17.4 0.018 --17.3 0.017 --17.2 0.016 --17.1 0.015 --17 0.014 --16.9 0.013 --16.8 0.013 --16.7 0.012 --16.6 0.011 --16.5 0.011 --16.4 0.01 --16.3 0.01 --16.2 0.01 --16.1 0.009 --16 0.009 --15.9 0.008 --15.8 0.008 --15.7 0.008 --15.6 0.007 --15.5 0.007 --15.4 0.007 --15.3 0.007 --15.2 0.006 --15.1 0.006 --15 0.006 --14.9 0.006 --14.8 0.006 --14.7 0.007 --14.6 0.007 --14.5 0.007 --14.4 0.007 --14.3 0.008 --14.2 0.008 --14.1 0.008 --14 0.009 --13.9 0.009 --13.8 0.01 --13.7 0.01 --13.6 0.01 --13.5 0.011 --13.4 0.011 --13.3 0.012 --13.2 0.013 --13.1 0.013 --13 0.014 --12.9 0.015 --12.8 0.016 --12.7 0.017 --12.6 0.018 --12.5 0.019 --12.4 0.02 --12.3 0.022 --12.2 0.023 --12.1 0.025 --12 0.027 --11.9 0.029 --11.8 0.031 --11.7 0.034 --11.6 0.037 --11.5 0.04 --11.4 0.044 --11.3 0.048 --11.2 0.053 --11.1 0.058 --11 0.064 --10.9 0.071 --10.8 0.077 --10.7 0.085 --10.6 0.092 --10.5 0.099 --10.4 0.106 --10.3 0.111 --10.2 0.115 --10.1 0.117 --10 0.117 --9.9 0.117 --9.8 0.115 --9.7 0.111 --9.6 0.106 --9.5 0.099 --9.4 0.092 --9.3 0.085 --9.2 0.077 --9.1 0.071 --9 0.064 --8.9 0.058 --8.8 0.053 --8.7 0.048 --8.6 0.044 --8.5 0.04 --8.4 0.037 --8.3 0.034 --8.2 0.031 --8.1 0.029 --8 0.027 --7.9 0.025 --7.8 0.023 --7.7 0.022 --7.6 0.02 --7.5 0.019 --7.4 0.018 --7.3 0.017 --7.2 0.016 --7.1 0.015 --7 0.014 --6.9 0.013 --6.8 0.013 --6.7 0.012 --6.6 0.011 --6.5 0.011 --6.4 0.01 --6.3 0.01 --6.2 0.01 --6.1 0.009 --6 0.009 --5.9 0.008 --5.8 0.008 --5.7 0.008 --5.6 0.007 --5.5 0.007 --5.4 0.007 --5.3 0.007 --5.2 0.006 --5.1 0.006 --5 0.006 --4.9 0.006 --4.8 0.006 --4.7 0.007 --4.6 0.007 --4.5 0.007 --4.4 0.007 --4.3 0.008 --4.2 0.008 --4.1 0.008 --4 0.009 --3.9 0.009 --3.8 0.01 --3.7 0.01 --3.6 0.01 --3.5 0.011 --3.4 0.011 --3.3 0.012 --3.2 0.013 --3.1 0.013 --3 0.014 --2.9 0.015 --2.8 0.016 --2.7 0.017 --2.6 0.018 --2.5 0.019 --2.4 0.02 --2.3 0.022 --2.2 0.023 --2.1 0.025 --2 0.027 --1.9 0.029 --1.8 0.031 --1.7 0.034 --1.6 0.037 --1.5 0.04 --1.4 0.044 --1.3 0.048 --1.2 0.053 --1.1 0.058 --1 0.064 --0.9 0.071 --0.8 0.077 --0.7 0.085 --0.6 0.092 --0.5 0.099 --0.4 0.106 --0.3 0.111 --0.2 0.115 --0.1 0.117 -0 0.117 -0.1 0.117 -0.2 0.115 -0.3 0.111 -0.4 0.106 -0.5 0.099 -0.6 0.092 -0.7 0.085 -0.8 0.077 -0.9 0.071 -1 0.064 -1.1 0.058 -1.2 0.053 -1.3 0.048 -1.4 0.044 -1.5 0.04 -1.6 0.037 -1.7 0.034 -1.8 0.031 -1.9 0.029 -2 0.027 -2.1 0.025 -2.2 0.023 -2.3 0.022 -2.4 0.02 -2.5 0.019 -2.6 0.018 -2.7 0.017 -2.8 0.016 -2.9 0.015 -3 0.014 -3.1 0.013 -3.2 0.013 -3.3 0.012 -3.4 0.011 -3.5 0.011 -3.6 0.01 -3.7 0.01 -3.8 0.01 -3.9 0.009 -4 0.009 -4.1 0.008 -4.2 0.008 -4.3 0.008 -4.4 0.007 -4.5 0.007 -4.6 0.007 -4.7 0.007 -4.8 0.006 -4.9 0.006 -5 0.006 -5.1 0.006 -5.2 0.006 -5.3 0.007 -5.4 0.007 -5.5 0.007 -5.6 0.007 -5.7 0.008 -5.8 0.008 -5.9 0.008 -6 0.009 -6.1 0.009 -6.2 0.01 -6.3 0.01 -6.4 0.01 -6.5 0.011 -6.6 0.011 -6.7 0.012 -6.8 0.013 -6.9 0.013 -7 0.014 -7.1 0.015 -7.2 0.016 -7.3 0.017 -7.4 0.018 -7.5 0.019 -7.6 0.02 -7.7 0.022 -7.8 0.023 -7.9 0.025 -8 0.027 -8.1 0.029 -8.2 0.031 -8.3 0.034 -8.4 0.037 -8.5 0.04 -8.6 0.044 -8.7 0.048 -8.8 0.053 -8.9 0.058 -9 0.064 -9.1 0.071 -9.2 0.077 -9.3 0.085 -9.4 0.092 -9.5 0.099 -9.6 0.106 -9.7 0.111 -9.8 0.115 -9.9 0.117 -10 0.117 -10.1 0.117 -10.2 0.115 -10.3 0.111 -10.4 0.106 -10.5 0.099 -10.6 0.092 -10.7 0.085 -10.8 0.077 -10.9 0.071 -11 0.064 -11.1 0.058 -11.2 0.053 -11.3 0.048 -11.4 0.044 -11.5 0.04 -11.6 0.037 -11.7 0.034 -11.8 0.031 -11.9 0.029 -12 0.027 -12.1 0.025 -12.2 0.023 -12.3 0.022 -12.4 0.02 -12.5 0.019 -12.6 0.018 -12.7 0.017 -12.8 0.016 -12.9 0.015 -13 0.014 -13.1 0.013 -13.2 0.013 -13.3 0.012 -13.4 0.011 -13.5 0.011 -13.6 0.01 -13.7 0.01 -13.8 0.01 -13.9 0.009 -14 0.009 -14.1 0.008 -14.2 0.008 -14.3 0.008 -14.4 0.007 -14.5 0.007 -14.6 0.007 -14.7 0.007 -14.8 0.006 -14.9 0.006 -15 0.006 -15.1 0.006 -15.2 0.006 -15.3 0.007 -15.4 0.007 -15.5 0.007 -15.6 0.007 -15.7 0.008 -15.8 0.008 -15.9 0.008 -16 0.009 -16.1 0.009 -16.2 0.01 -16.3 0.01 -16.4 0.01 -16.5 0.011 -16.6 0.011 -16.7 0.012 -16.8 0.013 -16.9 0.013 -17 0.014 -17.1 0.015 -17.2 0.016 -17.3 0.017 -17.4 0.018 -17.5 0.019 -17.6 0.02 -17.7 0.022 -17.8 0.023 -17.9 0.025 -18 0.027 -18.1 0.029 -18.2 0.031 -18.3 0.034 -18.4 0.037 -18.5 0.04 -18.6 0.044 -18.7 0.048 -18.8 0.053 -18.9 0.058 -19 0.064 -19.1 0.071 -19.2 0.077 -19.3 0.085 -19.4 0.092 -19.5 0.099 -19.6 0.106 -19.7 0.111 -19.8 0.115 -19.9 0.117 -20 0.117 diff --git a/docs/examples/testsweep/snapshot.json b/docs/examples/testsweep/snapshot.json deleted file mode 100644 index 93ef979f99f4..000000000000 --- a/docs/examples/testsweep/snapshot.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "__class__": "qcodes.data.data_set.DataSet", - "arrays": { - "gates_chan0_set": { - "__class__": "qcodes.data.data_array.DataArray", - "action_indices": [], - "array_id": "gates_chan0_set", - "instrument": "toymodel.MockGates", - "instrument_name": "gates", - "is_setpoint": true, - "label": "Gate Channel 0 (mV)", - "name": "chan0", - "shape": [ - 401 - ], - "units": "" - }, - "meter_amplitude": { - "__class__": "qcodes.data.data_array.DataArray", - "action_indices": [ - 0 - ], - "array_id": "meter_amplitude", - "instrument": "toymodel.MockMeter", - "instrument_name": "meter", - "is_setpoint": false, - "label": "Current (nA)", - "name": "amplitude", - "shape": [ - 401 - ], - "units": "" - } - }, - "formatter": "qcodes.data.gnuplot_format.GNUPlotFormat", - "io": "", - "location": "testsweep", - "loop": { - "__class__": "qcodes.loops.ActiveLoop", - "actions": [ - { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockMeter", - "instrument_name": "meter", - "label": "Current (nA)", - "name": "amplitude", - "ts": "2016-06-15 22:18:12", - "units": "", - "value": 0.117 - } - ], - "background": true, - "delay": 0.003, - "sweep_values": { - "parameter": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockGates", - "instrument_name": "gates", - "label": "Gate Channel 0 (mV)", - "name": "chan0", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": 0.0 - }, - "values": [ - { - "first": -20.0, - "last": 20.0, - "num": 401, - "type": "linear" - } - ] - }, - "then_actions": [], - "ts_end": "2016-06-15 22:18:19", - "ts_start": "2016-06-15 22:18:16", - "use_data_manager": true, - "use_threads": true - }, - "station": { - "components": {}, - "default_measurement": [ - { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockMeter", - "instrument_name": "meter", - "label": "Current (nA)", - "name": "amplitude", - "ts": "2016-06-15 22:18:12", - "units": "", - "value": 0.117 - } - ], - "instruments": { - "gates": { - "__class__": "toymodel.MockGates", - "functions": { - "reset": {} - }, - "name": "gates", - "parameters": { - "IDN": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockGates", - "instrument_name": "gates", - "label": "IDN", - "name": "IDN", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": { - "firmware": null, - "model": null, - "serial": null, - "vendor": null - } - }, - "chan0": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockGates", - "instrument_name": "gates", - "label": "Gate Channel 0 (mV)", - "name": "chan0", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": 0.0 - }, - "chan1": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockGates", - "instrument_name": "gates", - "label": "Gate Channel 1 (mV)", - "name": "chan1", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": 0.0 - }, - "chan2": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockGates", - "instrument_name": "gates", - "label": "Gate Channel 2 (mV)", - "name": "chan2", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": 0.0 - } - } - }, - "meter": { - "__class__": "toymodel.MockMeter", - "functions": {}, - "name": "meter", - "parameters": { - "IDN": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockMeter", - "instrument_name": "meter", - "label": "IDN", - "name": "IDN", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": { - "firmware": null, - "model": null, - "serial": null, - "vendor": null - } - }, - "amplitude": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockMeter", - "instrument_name": "meter", - "label": "Current (nA)", - "name": "amplitude", - "ts": "2016-06-15 22:18:12", - "units": "", - "value": 0.117 - } - } - }, - "source": { - "__class__": "toymodel.MockSource", - "functions": {}, - "name": "source", - "parameters": { - "IDN": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockSource", - "instrument_name": "source", - "label": "IDN", - "name": "IDN", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": { - "firmware": null, - "model": null, - "serial": null, - "vendor": null - } - }, - "amplitude": { - "__class__": "qcodes.instrument.parameter.StandardParameter", - "instrument": "toymodel.MockSource", - "instrument_name": "source", - "label": "Source Amplitude (μV)", - "name": "amplitude", - "ts": "2016-06-15 22:18:10", - "units": "", - "value": 0.1 - } - } - } - }, - "parameters": {} - } -} \ No newline at end of file diff --git a/docs/examples/toymodel.py b/docs/examples/toymodel.py deleted file mode 100644 index fb28c353b4aa..000000000000 --- a/docs/examples/toymodel.py +++ /dev/null @@ -1,149 +0,0 @@ -# code for example notebook - -import math - -from qcodes import MockInstrument, MockModel, Parameter, Loop, DataArray -from qcodes.utils.validators import Numbers -from qcodes.instrument.mock import ArrayGetter - -class AModel(MockModel): - def __init__(self): - self._gates = [0.0, 0.0, 0.0] - self._excitation = 0.1 - super().__init__() - - def _output(self): - # my super exciting model! - # make a nice pattern that looks sort of double-dotty - # with the first two gates controlling the two dots, - # and the third looking like Vsd - delta_i = 10 - delta_j = 10 - di = (self._gates[0] + delta_i / 2) % delta_i - delta_i / 2 - dj = (self._gates[1] + delta_j / 2) % delta_j - delta_j / 2 - vsd = math.sqrt(self._gates[2]**2 + self._excitation**2) - dij = math.sqrt(di**2 + dj**2) - vsd - g = (vsd**2 + 1) * (1 / (dij**2 + 1) + - 0.1 * (math.atan(-dij) + math.pi / 2)) - return g - - def fmt(self, value): - return '{:.3f}'.format(value) - - def gates_set(self, parameter, value): - if parameter[0] == 'c': - self._gates[int(parameter[1:])] = float(value) - elif parameter == 'rst' and value is None: - self._gates = [0.0, 0.0, 0.0] - else: - raise ValueError - - def gates_get(self, parameter): - if parameter[0] == 'c': - return self.fmt(self._gates[int(parameter[1:])]) - else: - raise ValueError - - def source_set(self, parameter, value): - if parameter == 'ampl': - self._excitation = float(value) - else: - raise ValueError - - def source_get(self, parameter): - if parameter == 'ampl': - return self.fmt(self._excitation) - else: - raise ValueError - - def meter_get(self, parameter): - if parameter == 'ampl': - return self.fmt(self._output() * self._excitation) - else: - raise ValueError - - -# make our mock instruments -# real instruments would subclass IPInstrument or VisaInstrument -# or just the base Instrument instead of MockInstrument, -# and be instantiated with an address rather than a model -class MockGates(MockInstrument): - def __init__(self, name, model=None, **kwargs): - super().__init__(name, model=model, **kwargs) - - for i in range(3): - cmdbase = 'c{}'.format(i) - self.add_parameter('chan{}'.format(i), - label='Gate Channel {} (mV)'.format(i), - get_cmd=cmdbase + '?', - set_cmd=cmdbase + ':{:.4f}', - get_parser=float, - vals=Numbers(-100, 100)) - - self.add_function('reset', call_cmd='rst') - - -class MockSource(MockInstrument): - def __init__(self, name, model=None, **kwargs): - super().__init__(name, model=model, **kwargs) - - # this parameter uses built-in sweeping to change slowly - self.add_parameter('amplitude', - label='Source Amplitude (\u03bcV)', - get_cmd='ampl?', - set_cmd='ampl:{:.4f}', - get_parser=float, - vals=Numbers(0, 10), - step=0.1, - delay=0.05) - - -class MockMeter(MockInstrument): - def __init__(self, name, model=None, **kwargs): - super().__init__(name, model=model, **kwargs) - - self.add_parameter('amplitude', - label='Current (nA)', - get_cmd='ampl?', - get_parser=float) - - -class AverageGetter(Parameter): - def __init__(self, measured_param, sweep_values, delay): - super().__init__(name='avg_' + measured_param.name) - self._instrument = getattr(measured_param, '_instrument', None) - self.measured_param = measured_param - self.sweep_values = sweep_values - self.delay = delay - if hasattr(measured_param, 'label'): - self.label = 'Average: ' + measured_param.label - - def get(self): - loop = Loop(self.sweep_values, self.delay).each(self.measured_param) - data = loop.run_temp() - array = data.arrays[self.measured_param.full_name] - return array.mean() - - -class AverageAndRaw(Parameter): - def __init__(self, measured_param, sweep_values, delay): - name = measured_param.name - super().__init__(names=(name, 'avg_' + name)) - self._instrument = getattr(measured_param, '_instrument', None) - self.measured_param = measured_param - self.sweep_values = sweep_values - self.delay = delay - self.shapes = ((len(sweep_values),), None) - set_array = DataArray(parameter=sweep_values.parameter, - preset_data=sweep_values) - self.setpoints = ((set_array,), None) - if hasattr(measured_param, 'label'): - self.labels = (measured_param.label, - 'Average: ' + measured_param.label) - - def get(self): - loop = Loop(self.sweep_values, self.delay).each(self.measured_param) - data = loop.run_temp() - array = data.arrays[self.measured_param.full_name] - return (array, array.mean()) - diff --git a/docs/user/tutorial.rst b/docs/user/tutorial.rst index b1e646ddb8b5..41bc21a9d448 100644 --- a/docs/user/tutorial.rst +++ b/docs/user/tutorial.rst @@ -3,34 +3,22 @@ Tutorial ======== -In this tutorial we'll walk through ***** - - -.. _driver : - Writing a Driver ---------------- -Write a simple driver example -with commented code -- add parameter -- add validator -- add custom stuff -- add doccstrings f.ex - -.. todo:: missing +First look at what parameters are and how to create them: `qcodes parameter `__ . +Then check out the walk-through to write your first driver: `qcodes instrument `__ . Measuring --------- -.. todo:: missing +Browse the `example `__ . .. _simulation : Simulation ---------- -Explain the mock mock .. todo:: missing diff --git a/qcodes/__init__.py b/qcodes/__init__.py index 859bcaf45dd3..b7d7676af239 100644 --- a/qcodes/__init__.py +++ b/qcodes/__init__.py @@ -2,10 +2,6 @@ # flake8: noqa (we don't need the "<...> imported but unused" error) -# just for convenience in debugging, so we don't have to -# separately import multiprocessing -from multiprocessing import active_children - # config from qcodes.config import Config @@ -13,7 +9,6 @@ config = Config() from qcodes.version import __version__ -from qcodes.process.helpers import set_mp_method from qcodes.utils.helpers import in_notebook # code that should only be imported into the main (notebook) thread @@ -34,18 +29,12 @@ 'try "from qcodes.plots.pyqtgraph import QtPlot" ' 'to see the full error') -# only import in name space if the gui is set to noebook -# and there is multiprocessing -if config['gui']['notebook'] and config['core']['legacy_mp']: - from qcodes.widgets.widgets import show_subprocess_widget - from qcodes.station import Station -from qcodes.loops import get_bg, halt_bg, Loop +from qcodes.loops import Loop from qcodes.measure import Measure from qcodes.actions import Task, Wait, BreakIf -from qcodes.data.manager import get_data_manager -from qcodes.data.data_set import DataMode, DataSet, new_data, load_data +from qcodes.data.data_set import DataSet, new_data, load_data from qcodes.data.location import FormatLocation from qcodes.data.data_array import DataArray from qcodes.data.format import Formatter @@ -56,7 +45,6 @@ from qcodes.instrument.base import Instrument from qcodes.instrument.ip import IPInstrument from qcodes.instrument.visa import VisaInstrument -from qcodes.instrument.mock import MockInstrument, MockModel from qcodes.instrument.function import Function from qcodes.instrument.parameter import ( @@ -72,4 +60,3 @@ from qcodes.utils import validators from qcodes.instrument_drivers.test import test_instruments, test_instrument -from qcodes.test import test_core, test_part diff --git a/qcodes/config/config.py b/qcodes/config/config.py index a69eb703ac8b..767832efe359 100644 --- a/qcodes/config/config.py +++ b/qcodes/config/config.py @@ -1,3 +1,4 @@ +import collections import copy import json import logging @@ -122,19 +123,19 @@ def update_config(self): if os.path.isfile(self.home_file_name): home_config = self.load_config(self.home_file_name) - config.update(home_config) + config = update(config, home_config) self.validate(config, self.current_schema, self.schema_home_file_name) if os.path.isfile(self.env_file_name): env_config = self.load_config(self.env_file_name) - config.update(env_config) + config = update(config, env_config) self.validate(config, self.current_schema, self.schema_env_file_name) if os.path.isfile(self.cwd_file_name): cwd_config = self.load_config(self.cwd_file_name) - config.update(cwd_config) + config = update(config, cwd_config) self.validate(config, self.current_schema, self.schema_cwd_file_name) @@ -161,9 +162,7 @@ def validate(self, json_config=None, schema=None, extra_schema_path=None): # be overwritten new_user = json.load(f)["properties"]["user"] user = schema["properties"]['user'] - user["properties"].update( - new_user["properties"] - ) + user["properties"].update(new_user["properties"]) jsonschema.validate(json_config, schema) else: logger.warning(EMPTY_USER_SCHEMA.format(extra_schema_path)) @@ -232,11 +231,13 @@ def add(self, key, value, value_type=None, description=None, default=None): # update schema! schema_entry = {key: {"type": value_type}} if description is not None: - schema_entry = {key: { - "type": value_type, - "default": default, - "description": description} - } + schema_entry = { + key: { + "type": value_type, + "default": default, + "description": description + } + } # the schema is nested we only update properties of the user object user = self.current_schema['properties']["user"] user["properties"].update(schema_entry) @@ -336,12 +337,7 @@ def describe(self, name): # add cool description to docstring base_docstring = """{}.\nCurrent value: {}. Type: {}. Default: {}.""" - doc = base_docstring.format( - description, - val, - _type, - default - ) + doc = base_docstring.format(description, val, _type, default) return doc @@ -357,15 +353,14 @@ def __getattr__(self, name): def __repr__(self): old = super().__repr__() base = """Current values: \n {} \n Current path: \n {} \n {}""" - return base.format(self.current_config, - self.current_config_path, - old) + return base.format(self.current_config, self.current_config_path, old) class DotDict(dict): """ Wrapper dict that allows to get dotted attributes """ + def __init__(self, value=None): if value is None: pass @@ -403,3 +398,13 @@ def __deepcopy__(self, memo): # dot acces baby __setattr__ = __setitem__ __getattr__ = __getitem__ + + +def update(d, u): + for k, v in u.items(): + if isinstance(v, collections.Mapping): + r = update(d.get(k, {}), v) + d[k] = r + else: + d[k] = u[k] + return d diff --git a/qcodes/config/qcodesrc.json b/qcodes/config/qcodesrc.json index d63cfbc28002..ca4a05aa88d0 100644 --- a/qcodes/config/qcodesrc.json +++ b/qcodes/config/qcodesrc.json @@ -1,6 +1,5 @@ { "core":{ - "legacy_mp": false, "loglevel": "DEBUG", "default_fmt": "data/{date}/#{counter}_{name}_{time}" }, diff --git a/qcodes/config/qcodesrc_schema.json b/qcodes/config/qcodesrc_schema.json index c3b7963ba214..bcaecc8e329f 100644 --- a/qcodes/config/qcodesrc_schema.json +++ b/qcodes/config/qcodesrc_schema.json @@ -7,11 +7,6 @@ "description": "controls core settings of qcodes", "type" : "object", "properties" : { - "legacy_mp": { - "type" : "boolean", - "description": "control legacy buggy multiprocess", - "default": false - }, "default_fmt": { "type" : "string", "description": "default location formatter", @@ -30,7 +25,7 @@ ] } }, - "required":[ "legacy_mp", "loglevel" ] + "required":["loglevel" ] }, "gui" : { "type" : "object", diff --git a/qcodes/data/data_set.py b/qcodes/data/data_set.py index 98b3fa60c3a6..d5407807b5db 100644 --- a/qcodes/data/data_set.py +++ b/qcodes/data/data_set.py @@ -1,35 +1,19 @@ """DataSet class and factory functions.""" -from enum import Enum import time import logging from traceback import format_exc from copy import deepcopy from collections import OrderedDict -from .manager import get_data_manager, NoData from .gnuplot_format import GNUPlotFormat from .io import DiskIO from .location import FormatLocation from qcodes.utils.helpers import DelegateAttributes, full_class, deep_update -class DataMode(Enum): - - """Server connection modes supported by a DataSet.""" - - LOCAL = 1 - PUSH_TO_SERVER = 2 - PULL_FROM_SERVER = 3 - - -SERVER_MODES = set((DataMode.PULL_FROM_SERVER, DataMode.PUSH_TO_SERVER)) - - def new_data(location=None, loc_record=None, name=None, overwrite=False, - io=None, data_manager=False, mode=DataMode.LOCAL, **kwargs): - # NOTE(giulioungaretti): leave this docstrings as it is, because - # documenting the types is silly in this case. + io=None, **kwargs): """ Create a new DataSet. @@ -61,24 +45,6 @@ def new_data(location=None, loc_record=None, name=None, overwrite=False, says the root data directory is the current working directory, ie where you started the python session. - data_manager (Optional[bool]): use a manager for the - ``DataServer`` that offloads storage and syncing of this Defaults - to ``False`` i.e. this ``DataSet`` will store itself without extra - processes. Set to ``True`` to use the default from - ``get_data_manager()``. - - mode (DataMode, optional): connection type to the ``DataServer``. - - - ``DataMode.LOCAL``: this DataSet doesn't communicate across - processes. - - ``DataMode.PUSH_TO_SERVER``: no local copy of data, just pushes - each measurement to a ``DataServer``. - - ``DataMode.PULL_FROM_SERVER``: pulls changes from the - ``DataServer`` on calling ``self.sync()``. Reverts to local if - and when it stops being the live measurement. - - Default ``DataMode.LOCAL``. - arrays (Optional[List[qcodes.DataArray]): arrays to add to the DataSet. Can be added later with ``self.add_array(array)``. @@ -86,11 +52,8 @@ def new_data(location=None, loc_record=None, name=None, overwrite=False, write (and read) with. Default ``DataSet.default_formatter`` which is initially ``GNUPlotFormat()``. - write_period (float or None, optional): Only if ``mode=LOCAL``, seconds - between saves to disk. If not ``LOCAL``, the ``DataServer`` handles - this and generally writes more often. Use None to disable writing - from calls to ``self.store``. Default 5. - + write_period (float or None, optional):seconds + between saves to disk. Returns: A new ``DataSet`` object ready for storing new data in. """ @@ -111,23 +74,13 @@ def new_data(location=None, loc_record=None, name=None, overwrite=False, if location and (not overwrite) and io.list(location): raise FileExistsError('"' + location + '" already has data') - if data_manager is True: - data_manager = get_data_manager() - else: - if mode != DataMode.LOCAL: - raise ValueError('DataSets without a data_manager must be local') + return DataSet(location=location, io=io, **kwargs) - return DataSet(location=location, io=io, data_manager=data_manager, - mode=mode, **kwargs) - -def load_data(location=None, data_manager=None, formatter=None, io=None): +def load_data(location=None, formatter=None, io=None): """ Load an existing DataSet. - The resulting ``DataSet.mode`` is determined automatically from location: - PULL_FROM_SERVER if this is the live DataSet, otherwise LOCAL - Args: location (str, optional): the location to load from. Default is the current live DataSet. @@ -135,13 +88,6 @@ def load_data(location=None, data_manager=None, formatter=None, io=None): combination of io + location. the default ``DiskIO`` sets the base directory, which this location is a relative path inside. - data_manager (DataManager or False, optional): manager for the - ``DataServer`` that offloads storage and syncing of this - ``DataSet``. Usually omitted (default None) to use the default - from ``get_data_manager()``. If ``False``, this ``DataSet`` will - store itself. ``load_data`` will not start a DataManager but may - query an existing one to determine (and pull) the live data. - formatter (Formatter, optional): sets the file format/structure to read with. Default ``DataSet.default_formatter`` which is initially ``GNUPlotFormat()``. @@ -154,39 +100,14 @@ def load_data(location=None, data_manager=None, formatter=None, io=None): Returns: A new ``DataSet`` object loaded with pre-existing data. """ - if data_manager is None: - data_manager = get_data_manager(only_existing=True) - - if location is None: - if not data_manager: - raise RuntimeError('Live data requested but DataManager does ' - 'not exist or was requested not to be used') - - return _get_live_data(data_manager) - - elif location is False: + if location is False: raise ValueError('location=False means a temporary DataSet, ' 'which is incompatible with load_data') - elif (data_manager and - location == data_manager.ask('get_data', 'location')): - return _get_live_data(data_manager) - - else: - data = DataSet(location=location, formatter=formatter, io=io, - mode=DataMode.LOCAL) - data.read_metadata() - data.read() - return data - - -def _get_live_data(data_manager): - live_data = data_manager.ask('get_data') - if live_data is None or isinstance(live_data, NoData): - raise RuntimeError('DataManager has no live data') - - live_data.mode = DataMode.PULL_FROM_SERVER - return live_data + data = DataSet(location=location, formatter=formatter, io=io) + data.read_metadata() + data.read() + return data class DataSet(DelegateAttributes): @@ -212,24 +133,6 @@ class DataSet(DelegateAttributes): says the root data directory is the current working directory, ie where you started the python session. - data_manager (Optional[bool]): use a manager for the - ``DataServer`` that offloads storage and syncing of this Defaults - to ``False`` i.e. this ``DataSet`` will store itself without extra - processes. Set to ``True`` to use the default from - ``get_data_manager()``. - - mode (DataMode, optional): connection type to the ``DataServer``. - - - ``DataMode.LOCAL``: this DataSet doesn't communicate across - processes. - - ``DataMode.PUSH_TO_SERVER``: no local copy of data, just pushes - each measurement to a ``DataServer``. - - ``DataMode.PULL_FROM_SERVER``: pulls changes from the - ``DataServer`` on calling ``self.sync()``. Reverts to local if - and when it stops being the live measurement. - - Default to ``DataMode.LOCAL``. - arrays (Optional[List[qcodes.DataArray]): arrays to add to the DataSet. Can be added later with ``self.add_array(array)``. @@ -265,8 +168,8 @@ class DataSet(DelegateAttributes): background_functions = OrderedDict() - def __init__(self, location=None, mode=DataMode.LOCAL, arrays=None, - data_manager=False, formatter=None, io=None, write_period=5): + def __init__(self, location=None, arrays=None, formatter=None, io=None, + write_period=5): if location is False or isinstance(location, str): self.location = location else: @@ -289,92 +192,10 @@ def __init__(self, location=None, mode=DataMode.LOCAL, arrays=None, for array in arrays: self.add_array(array) - if data_manager is True and mode in SERVER_MODES: - data_manager = get_data_manager() - - if mode == DataMode.LOCAL: - self._init_local() - elif mode == DataMode.PUSH_TO_SERVER: - self._init_push_to_server(data_manager) - elif mode == DataMode.PULL_FROM_SERVER: - self._init_live(data_manager) - else: - raise ValueError('unrecognized DataSet mode', mode) - - def _init_local(self): - self.mode = DataMode.LOCAL - if self.arrays: for array in self.arrays.values(): array.init_data() - def _init_push_to_server(self, data_manager): - self.mode = DataMode.PUSH_TO_SERVER - - # If some code was not available when data_manager was started, - # we can't unpickle it on the other end. - # So we'll try, then restart if this error occurs, then try again. - # - # This still has a pitfall, if code has been *changed* since - # starting the server, it will still have the old version and - # everything will look fine but it won't have the new behavior. - # If the user does that, they need to manually restart the server, - # using: - # data_manager.restart() - try: - data_manager.ask('new_data', self) - except AttributeError: - data_manager.restart() - data_manager.ask('new_data', self) - - # need to set data_manager *after* sending to data_manager because - # we can't (and shouldn't) send data_manager itself through a queue - self.data_manager = data_manager - - def init_on_server(self): - """ - Configure this DataSet as the DataServer copy. - - Should be run only by the DataServer itself. - """ - if not self.arrays: - raise RuntimeError('A server-side DataSet needs DataArrays.') - - self._init_local() - - def _init_live(self, data_manager): - self.mode = DataMode.PULL_FROM_SERVER - self.data_manager = data_manager - with data_manager.query_lock: - if self.is_on_server: - live_obj = data_manager.ask('get_data') - self.arrays = live_obj.arrays - else: - self._init_local() - - @property - def is_live_mode(self): - """ - Indicate whether this DataSet thinks it is live in the DataServer. - - Does not actually talk to the DataServer or sync with it. - """ - return self.mode in SERVER_MODES and self.data_manager and True - - @property - def is_on_server(self): - """ - Check whether this DataSet is actually live in the DataServer. - - If it thought it was but isn't, convert it to mode=LOCAL - """ - if not self.is_live_mode or self.location is False: - return False - - with self.data_manager.query_lock: - live_location = self.data_manager.ask('get_data', 'location') - return self.location == live_location - def sync(self): """ Synchronize this DataSet with the DataServer or storage. @@ -391,39 +212,9 @@ def sync(self): # changed (and I guess throw an error if both did? Would be cool if we # could find a robust and intuitive way to make modifications to the # version on the DataServer from the main copy) - if not self.is_live_mode: - # LOCAL DataSet - no need to sync just use local data - return False - # TODO - for remote live plotting, maybe set some timestamp - # threshold and call it static after it's been dormant a long time? - # I'm thinking like a minute, or ten? Maybe it's configurable? - - with self.data_manager.query_lock: - if self.is_on_server: - synced_indices = { - array_id: array.get_synced_index() - for array_id, array in self.arrays.items() - } - - changes = self.data_manager.ask('get_changes', synced_indices) - - for array_id, array_changes in changes.items(): - self.arrays[array_id].apply_changes(**array_changes) - - measuring = self.data_manager.ask('get_measuring') - if not measuring: - # we must have *just* stopped measuring - # but the DataSet is still on the server, - # so we got the data, and don't need to read. - self.mode = DataMode.LOCAL - return False - return True - else: - # this DataSet *thought* it was on the server, but it wasn't, - # so we haven't synced yet and need to read from storage - self.mode = DataMode.LOCAL - self.read() - return False + + # LOCAL DataSet - no need to sync just use local data + return False def fraction_complete(self): """ @@ -582,9 +373,6 @@ def store(self, loop_indices, ids_values): """ Insert data into one or more of our DataArrays. - If in ``PUSH_TO_SERVER`` mode, this is where we do that! - Otherwise we also periodically trigger a write to storage. - Args: loop_indices (tuple): the indices within whatever loops we are inside. May have fewer dimensions than some of the arrays @@ -594,23 +382,13 @@ def store(self, loop_indices, ids_values): array_ids, and values are single numbers or entire slices to insert into that array. """ - if self.mode == DataMode.PUSH_TO_SERVER: - # Defers to the copy on the dataserver to call this identical - # function - self.data_manager.write('store_data', loop_indices, ids_values) - 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.') + 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() def default_parameter_name(self, paramname='amplitude'): """ Return name of default parameter for plotting @@ -692,10 +470,6 @@ def write(self, write_metadata=False): Args: write_metadata (bool): write the metadata to disk """ - if self.mode != DataMode.LOCAL: - raise RuntimeError('This object is connected to a DataServer, ' - 'which handles writing automatically.') - if self.location is False: return @@ -783,20 +557,11 @@ def finalize(self): Also closes the data file(s), if the ``Formatter`` we're using supports that. """ - if self.mode == DataMode.PUSH_TO_SERVER: - # Just like .store, if this DataSet is on the DataServer, - # we defer to the copy there and execute this same method. - self.data_manager.ask('finalize_data') - 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 - self.write() + self.write() + + if hasattr(self.formatter, 'close_file'): + self.formatter.close_file(self) - if hasattr(self.formatter, 'close_file'): - self.formatter.close_file(self) - else: - raise RuntimeError('This mode does not allow finalizing', - self.mode) self.save_metadata() def snapshot(self, update=False): @@ -833,8 +598,7 @@ def __repr__(self): """Rich information about the DataSet and contained arrays.""" out = type(self).__name__ + ':' - attrs = [['mode', self.mode], - ['location', repr(self.location)]] + attrs = [['location', repr(self.location)]] attr_template = '\n {:8} = {}' for var, val in attrs: out += attr_template.format(var, val) diff --git a/qcodes/data/manager.py b/qcodes/data/manager.py deleted file mode 100644 index 53121dac2fd7..000000000000 --- a/qcodes/data/manager.py +++ /dev/null @@ -1,163 +0,0 @@ -from datetime import datetime, timedelta -from queue import Empty -from traceback import format_exc -import logging - -from qcodes.process.server import ServerManager, BaseServer - - -def get_data_manager(only_existing=False): - """ - create or retrieve the storage manager - makes sure we don't accidentally create multiple DataManager processes - """ - dm = DataManager.default - if dm and dm._server.is_alive(): - return dm - elif only_existing: - return None - return DataManager() - - -class NoData: - """ - A placeholder object for DataServer to hold - when there is no loop running. - """ - location = None - - def store(self, *args, **kwargs): - raise RuntimeError('no DataSet to add to') - - def write(self, *args, **kwargs): - pass - - -class DataManager(ServerManager): - default = None - """ - creates a separate process (DataServer) that holds running measurement - and monitor data, and manages writing these to disk or other storage - - DataServer communicates with other processes through messages - Written using multiprocessing Queue's, but should be easily - extensible to other messaging systems - """ - def __init__(self): - type(self).default = self - super().__init__(name='DataServer', server_class=DataServer) - - def restart(self, force=False): - """ - Restart the DataServer - Use force=True to abort a running measurement. - """ - if (not force) and self.ask('get_data', 'location'): - raise RuntimeError('A measurement is running. Use ' - 'restart(force=True) to override.') - super().restart() - - -class DataServer(BaseServer): - """ - Running in its own process, receives, holds, and returns current `Loop` and - monitor data, and writes it to disk (or other storage) - - When a `Loop` is *not* running, the DataServer also calls the monitor - routine. But when a `Loop` *is* running, *it* calls the monitor so that it - can avoid conflicts. Also while a `Loop` is running, there are - complementary `DataSet` objects in the loop and `DataServer` processes - - they are nearly identical objects, but are configured differently so that - the loop `DataSet` doesn't hold any data itself, it only passes that data - on to the `DataServer` - """ - default_storage_period = 1 # seconds between data storage calls - queries_per_store = 5 - default_monitor_period = 60 # seconds between monitoring storage calls - - def __init__(self, query_queue, response_queue, extras=None): - super().__init__(query_queue, response_queue, extras) - - self._storage_period = self.default_storage_period - self._monitor_period = self.default_monitor_period - - self._data = NoData() - self._measuring = False - - self.run_event_loop() - - def run_event_loop(self): - self.running = True - next_store_ts = datetime.now() - next_monitor_ts = datetime.now() - - while self.running: - read_timeout = self._storage_period / self.queries_per_store - try: - query = self._query_queue.get(timeout=read_timeout) - self.process_query(query) - except Empty: - pass - - try: - now = datetime.now() - - if self._measuring and now > next_store_ts: - td = timedelta(seconds=self._storage_period) - next_store_ts = now + td - self._data.write() - - if now > next_monitor_ts: - td = timedelta(seconds=self._monitor_period) - next_monitor_ts = now + td - # TODO: update the monitor data storage - - except: - logging.error(format_exc()) - - ###################################################################### - # query handlers # - ###################################################################### - - def handle_new_data(self, data_set): - """ - Load a new (normally empty) DataSet into the DataServer, and - prepare it to start receiving and storing data - """ - if self._measuring: - raise RuntimeError('Already executing a measurement') - - self._data = data_set - self._data.init_on_server() - self._measuring = True - - def handle_finalize_data(self): - """ - Mark this DataSet as complete and write its final changes to storage - """ - self._data.finalize() - self._measuring = False - - def handle_store_data(self, *args): - """ - Put some data into the DataSet - """ - self._data.store(*args) - - def handle_get_measuring(self): - """ - Is a measurement loop presently running? - """ - return self._measuring - - def handle_get_data(self, attr=None): - """ - Return the active DataSet or some attribute of it - """ - return getattr(self._data, attr) if attr else self._data - - def handle_get_changes(self, synced_indices): - """ - Return all new data after the last sync - """ - return self._data.get_changes(synced_indices) diff --git a/qcodes/instrument/base.py b/qcodes/instrument/base.py index d2dc887423ce..6c02d7c27388 100644 --- a/qcodes/instrument/base.py +++ b/qcodes/instrument/base.py @@ -1,21 +1,18 @@ """Instrument base class.""" import logging +import numpy as np import time import warnings import weakref -import numpy as np from qcodes.utils.metadata import Metadatable from qcodes.utils.helpers import DelegateAttributes, strip_attrs, full_class -from qcodes.utils.nested_attrs import NestedAttrAccess from qcodes.utils.validators import Anything from .parameter import StandardParameter from .function import Function -from .metaclass import InstrumentMetaclass -class Instrument(Metadatable, DelegateAttributes, NestedAttrAccess, - metaclass=InstrumentMetaclass): +class Instrument(Metadatable, DelegateAttributes): """ Base class for all QCodes instruments. @@ -24,48 +21,10 @@ class Instrument(Metadatable, DelegateAttributes, NestedAttrAccess, name (str): an identifier for this instrument, particularly for attaching it to a Station. - server_name (Optional[str]): If not ``None``, this instrument starts a - separate server process (or connects to one, if one already exists - with the same name) and all hardware calls are made there. - - Default '', then we call classmethod ``default_server_name``, - passing in all the constructor kwargs, to determine the name. - If not overridden, this just gives 'Instruments'. - - **see subclass constructors below for more on ``server_name``** - - Use None to operate without a server - but then this Instrument - will not work with qcodes Loops or other multiprocess procedures. - - If a server is used, the ``Instrument`` you asked for is - instantiated on the server, and the object you get in the main - process is actually a ``RemoteInstrument`` that proxies all method - calls, ``Parameters``, and ``Functions`` to the server. - - The metaclass ``InstrumentMetaclass`` handles making either the - requested class or its RemoteInstrument proxy. - metadata (Optional[Dict]): additional static metadata to add to this instrument's JSON snapshot. - Any unpicklable objects that are inputs to the constructor must be set - on server initialization, and must be shared between all instruments - that reside on the same server. To make this happen, set the - ``shared_kwargs`` class attribute to a tuple of kwarg names that should - be treated this way. - - It is an error to initialize two instruments on the same server with - different keys or values for ``shared_kwargs``, unless the later - instruments have NO ``shared_kwargs`` at all. - - subclass constructors: ``server_name`` and any ``shared_kwargs`` must be - available as kwargs and kwargs ONLY (not positional) in all subclasses, - and not modified in the inheritance chain. This is because we need to - create the server before instantiating the actual instrument. The easiest - way to manage this is to accept ``**kwargs`` in your subclass and pass them - on to ``super().__init()``. - Attributes: name (str): an identifier for this instrument, particularly for attaching it to a Station. @@ -81,8 +40,11 @@ class Instrument(Metadatable, DelegateAttributes, NestedAttrAccess, _all_instruments = {} - def __init__(self, name, server_name=None, **kwargs): + def __init__(self, name, **kwargs): self._t0 = time.time() + if kwargs.pop('server_name', False): + warnings.warn("server_name argument not supported any more", + stacklevel=0) super().__init__(**kwargs) self.parameters = {} self.functions = {} @@ -94,7 +56,7 @@ def __init__(self, name, server_name=None, **kwargs): self._meta_attrs = ['name'] - self._no_proxy_methods = {'__getstate__'} + self.record_instance(self) def get_idn(self): """ @@ -127,8 +89,8 @@ def get_idn(self): if len(idparts) < 4: idparts += [None] * (4 - len(idparts)) except: - logging.warn('Error getting or interpreting *IDN?: ' + repr(idstr)) - idparts = [None, None, None, None] + logging.debug('Error getting or interpreting *IDN?: ' + repr(idstr)) + idparts = [None, self.name, None, None] # some strings include the word 'model' at the front of model if str(idparts[1]).lower().startswith('model'): @@ -136,21 +98,6 @@ def get_idn(self): return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts)) - @classmethod - def default_server_name(cls, **kwargs): - """ - Generate a default name for the server to host this instrument. - - Args: - **kwargs: the constructor kwargs, used if necessary to choose a - name. - - Returns: - str: The default server name for the specific instrument instance - we are constructing. - """ - return 'Instruments' - def connect_message(self, idn_param='IDN', begin_time=None): """ Print a standard message on initial connection to an instrument. @@ -181,7 +128,7 @@ def __del__(self): """Close the instrument and remove its instance record.""" try: wr = weakref.ref(self) - if wr in getattr(self, '_instances', {}): + if wr in getattr(self, '_instances', []): self._instances.remove(wr) self.close() except: @@ -209,17 +156,13 @@ def record_instance(cls, instance): that there are no other instruments with the same name. Args: - instance (Union[Instrument, RemoteInstrument]): Note: we *do not* - check that instance is actually an instance of ``cls``. This is - important, because a ``RemoteInstrument`` should function as an - instance of the instrument it proxies. + instance (Instrument): Instance to record Raises: KeyError: if another instance with the same name is already present """ wr = weakref.ref(instance) name = instance.name - # First insert this instrument in the record of *all* instruments # making sure its name is unique existing_wr = cls._all_instruments.get(name) @@ -243,12 +186,8 @@ def instances(cls): You can use this to get the objects back if you lose track of them, and it's also used by the test system to find objects to test against. - Note: - Will also include ``RemoteInstrument`` instances that proxy - instruments of this class. - Returns: - List[Union[Instrument, RemoteInstrument]] + List[Instrument]] """ if getattr(cls, '_type', None) is not cls: # only instances of a superclass - we want instances of this @@ -262,7 +201,7 @@ def remove_instance(cls, instance): Remove a particular instance from the record. Args: - instance (Union[Instrument, RemoteInstrument]) + instance (Union[Instrument]) """ wr = weakref.ref(instance) if wr in cls._instances: @@ -286,7 +225,7 @@ def find_instrument(cls, name, instrument_class=None): you are looking for. Returns: - Union[Instrument, RemoteInstrument] + Union[Instrument] Raises: KeyError: if no instrument of that name was found, or if its @@ -308,34 +247,6 @@ def find_instrument(cls, name, instrument_class=None): return ins - @classmethod - def find_component(cls, name_attr, instrument_class=None): - """ - Find a component of an existing instrument by name and attribute. - - Args: - name_attr (str): A string in nested attribute format: - .[.] and so on. - For example, can be a parameter name, - or a method name. - instrument_class (Optional[class]): The type of instrument - you are looking for this component within. - - Returns: - Any: The component requested. - """ - - if '.' in name_attr: - name, attr = name_attr.split('.', 1) - ins = cls.find_instrument(name, instrument_class=instrument_class) - return ins.getattr(attr) - - else: - # allow find_component to return the whole instrument, - # if no attribute was specified, for maximum generality. - return cls.find_instrument(name_attr, - instrument_class=instrument_class) - def add_parameter(self, name, parameter_class=StandardParameter, **kwargs): """ @@ -357,11 +268,6 @@ def add_parameter(self, name, parameter_class=StandardParameter, **kwargs: constructor arguments for ``parameter_class``. - Returns: - dict: attribute information. Only used if you add parameters - from the ``RemoteInstrument`` rather than at construction, to - properly construct the proxy for this parameter. - Raises: KeyError: if this instrument already has a parameter with this name. @@ -371,10 +277,6 @@ def add_parameter(self, name, parameter_class=StandardParameter, param = parameter_class(name=name, instrument=self, **kwargs) self.parameters[name] = param - # for use in RemoteInstruments to add parameters to the server - # we return the info they need to construct their proxy - return param.get_attrs() - def add_function(self, name, **kwargs): """ Bind one Function to this instrument. @@ -395,11 +297,6 @@ def add_function(self, name, **kwargs): **kwargs: constructor kwargs for ``Function`` - Returns: - A dict of attribute information. Only used if you add functions - from the ``RemoteInstrument`` rather than at construction, to - properly construct the proxy for this function. - Raises: KeyError: if this instrument already has a function with this name. @@ -409,10 +306,6 @@ def add_function(self, name, **kwargs): func = Function(name=name, instrument=self, **kwargs) self.functions[name] = func - # for use in RemoteInstruments to add functions to the server - # we return the info they need to construct their proxy - return func.get_attrs() - def snapshot_base(self, update=False): """ State of the instrument as a JSON-compatible dict. @@ -463,7 +356,8 @@ def print_readable_snapshot(self, update=False, max_chars=80): print('{0:<{1}}'.format('\tparameter ', par_field_len) + 'value') print('-'*80) for par in sorted(snapshot['parameters']): - msg = '{0:<{1}}:'.format(snapshot['parameters'][par]['name'], par_field_len) + name = snapshot['parameters'][par]['name'] + msg = '{0:<{1}}:'.format(name, par_field_len) val = snapshot['parameters'][par]['value'] unit = snapshot['parameters'][par]['unit'] if isinstance(val, floating_types): @@ -473,7 +367,7 @@ def print_readable_snapshot(self, update=False, max_chars=80): if unit is not '': # corresponds to no unit msg += '({})'.format(unit) # Truncate the message if it is longer than max length - if len(msg) > max_chars and not max_chars==-1: + if len(msg) > max_chars and not max_chars == -1: msg = msg[0:max_chars-3] + '...' print(msg) @@ -609,60 +503,6 @@ def call(self, func_name, *args): """ return self.functions[func_name].call(*args) - # - # info about what's in this instrument, to help construct the remote # - # - - def connection_attrs(self, new_id): - """ - Collect info to reconstruct the instrument API in the RemoteInstrument. - - Args: - new_id (int): The ID of this instrument on its server. - This is how the RemoteInstrument points its calls to the - correct server instrument when it calls the server. - - Returns: - dict: Dictionary of name: str, id: int, parameters: dict, - functions: dict, _methods: dict - parameters, functions, and _methods are dictionaries of - name: List(str) of attributes to be proxied in the remote. - """ - return { - 'name': self.name, - 'id': new_id, - 'parameters': {name: p.get_attrs() - for name, p in self.parameters.items()}, - 'functions': {name: f.get_attrs() - for name, f in self.functions.items()}, - '_methods': self._get_method_attrs() - } - - def _get_method_attrs(self): - """ - Construct a dict of methods this instrument has. - - Returns: - dict: Dictionary of method names : list of attributes each method - has that should be proxied. As of now, this is just its - docstring, if it has one. - """ - out = {} - - for attr in dir(self): - value = getattr(self, attr) - if ((not callable(value)) or - value is self.parameters.get(attr) or - value is self.functions.get(attr) or - attr in self._no_proxy_methods): - # Functions and Parameters are callable and they show up in - # dir(), but they have their own listing. - continue - - out[attr] = ['__doc__'] if hasattr(value, '__doc__') else [] - - return out - def __getstate__(self): """Prevent pickling instruments, and give a nice error message.""" raise RuntimeError( diff --git a/qcodes/instrument/ip.py b/qcodes/instrument/ip.py index 9799353e7ebf..3b8df6a9883c 100644 --- a/qcodes/instrument/ip.py +++ b/qcodes/instrument/ip.py @@ -28,12 +28,6 @@ class IPInstrument(Instrument): write_confirmation (bool): Whether the instrument acknowledges writes with some response we should read. Default True. - server_name (str): Name of the InstrumentServer to use. Defaults to - 'IPInstruments'. - - Use ``None`` to run locally - but then this instrument will not - work with qcodes Loops or other multiprocess procedures. - metadata (Optional[Dict]): additional static metadata to add to this instrument's JSON snapshot. @@ -59,19 +53,6 @@ def __init__(self, name, address=None, port=None, timeout=5, self.set_persistent(persistent) - @classmethod - def default_server_name(cls, **kwargs): - """ - Get the default server name for this instrument. - - Args: - **kwargs: All the kwargs supplied in the constructor. - - Returns: - str: By default all IPInstruments go on the server 'IPInstruments'. - """ - return 'IPInstruments' - def set_address(self, address=None, port=None): """ Change the IP address and/or port of this instrument. diff --git a/qcodes/instrument/metaclass.py b/qcodes/instrument/metaclass.py deleted file mode 100644 index 641028ab8dfc..000000000000 --- a/qcodes/instrument/metaclass.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Metaclass to choose between Instrument and RemoteInstrument""" - -import warnings - -from .remote import RemoteInstrument - - -class InstrumentMetaclass(type): - def __call__(cls, *args, server_name=None, **kwargs): - """ - Create either a real Instrument or a RemoteInstrument as requested. - - This (metaclass.__call__) is what is actually executed when you - instantiate an instrument, and returns the fully initialized object - (unlike class.__new__ which returns before __init__) so we can use this - to determine if the object was successfully created and only then - record its instance. - - Args: - cls (type): the specific instrument class you invoked - - *args (List[Any]): positional args to the instrument constructor - - server_name (Optional[Union[str, None]]): if ``None`` we construct - a local instrument (with the class you requested). If a string, - we construct this instrument on a server with that name, or the - default from the instrument's classmethod - ``default_server_name`` if a blank string is used) - - **kwargs (Dict[Any]): the kwargs to the instrument constructor, - after omitting server_name - """ - if server_name is None: - instrument = super().__call__(*args, **kwargs) - else: - warnings.warn('Multiprocessing is in beta, use at own risk', - UserWarning) - instrument = RemoteInstrument(*args, instrument_class=cls, - server_name=server_name, **kwargs) - - # for RemoteInstrument, we want to record this instance with the - # class that it proxies, not with RemoteInstrument itself - cls.record_instance(instrument) - - return instrument diff --git a/qcodes/instrument/mock.py b/qcodes/instrument/mock.py deleted file mode 100644 index ec100134e6fd..000000000000 --- a/qcodes/instrument/mock.py +++ /dev/null @@ -1,316 +0,0 @@ -"""Mock instruments for testing purposes.""" -import time -from datetime import datetime - -from .base import Instrument -from .parameter import MultiParameter -from qcodes import Loop -from qcodes.data.data_array import DataArray -from qcodes.process.server import ServerManager, BaseServer -from qcodes.utils.nested_attrs import _NoDefault - - -class MockInstrument(Instrument): - - """ - Create a software instrument, mostly for testing purposes. - - Also works for simulations, but usually this will be simpler, easier to - use, and faster if made as a single ``Instrument`` subclass. - - ``MockInstrument``\s have extra overhead as they serialize all commands - (to mimic a network communication channel) and use at least two processes - (instrument server and model server) both of which must be involved in any - given query. - - parameters to pass to model should be declared with: - - - get_cmd = param_name + '?' - - set_cmd = param_name + ':{:.3f}' (specify the format & precision) - - alternatively independent set/get functions may still be provided. - - Args: - name (str): The name of this instrument. - - delay (number): Time (in seconds) to wait after any operation - to simulate communication delay. Default 0. - - model (MockModel): A model to connect to. Subclasses MUST accept - ``model`` as a constructor kwarg ONLY, even though it is required. - See notes in ``Instrument`` docstring. - The model should have one or two methods related directly to this - instrument by ``name``: - ``_set(param, value)``: set a parameter on the model - ``_get(param)``: returns the value of a parameter - - keep_history (bool): Whether to record (in self.history) every command - sent to this instrument. Default True. - - server_name (Union[str, None]): leave default ('') to make a - MockInsts-####### server with the number matching the model server - id, or set None to not use a server. - - Attributes: - shared_kwargs (List[str]): Class attribute, constructor kwargs to - provide via server init. For MockInstrument this should always be - ['model'] at least. - - keep_history (bool): Whether to record all commands and responses. Set - on init, but may be changed at any time. - - history (List[tuple]): All commands and responses while keep_history is - enabled, as tuples: - (timestamp, 'ask' or 'write', param_name[, value]) - """ - - shared_kwargs = ['model'] - - def __init__(self, name, delay=0, model=None, keep_history=True, **kwargs): - super().__init__(name, **kwargs) - - if not isinstance(delay, (int, float)) or delay < 0: - raise TypeError('delay must be a non-negative number') - self._delay = delay - - # try to access write and ask so we know they exist - model.write - model.ask - self._model = model - - # keep a record of every command sent to this instrument - # for debugging purposes? - self.keep_history = bool(keep_history) - self.history = [] - - @classmethod - def default_server_name(cls, **kwargs): - """ - Get the default server name for this instrument. - - Args: - **kwargs: All the kwargs supplied in the constructor. - - Returns: - str: Default MockInstrument server name is MockInsts-#######, where - ####### is the first 7 characters of the MockModel's uuid. - """ - model = kwargs.get('model', None) - if model: - return model.name.replace('Model', 'MockInsts') - return 'MockInstruments' - - def get_idn(self): - """Shim for IDN parameter.""" - return { - 'vendor': None, - 'model': type(self).__name__, - 'serial': self.name, - 'firmware': None - } - - def write_raw(self, cmd): - """ - Low-level interface to ``model.write``. - - Prepends self.name + ':' to the command, so the ``MockModel`` - will direct this query to its ``_set`` method - - Args: - cmd (str): The command to send to the instrument. - """ - if self._delay: - time.sleep(self._delay) - - try: - parameter, value = cmd.split(':', 1) - except ValueError: - parameter, value = cmd, None # for functions with no value - - if self.keep_history: - self.history.append((datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'write', parameter, value)) - - self._model.write('cmd', self.name + ':' + cmd) - - def ask_raw(self, cmd): - """ - Low-level interface to ``model.ask``. - - Prepends self.name + ':' to the command, so the ``MockModel`` - will direct this query to its ``_get`` method - - Args: - cmd (str): The command to send to the instrument. - - Returns: - str: The instrument's response. - - Raises: - ValueError: If ``cmd`` is malformed in that it contains text - after the '?' - """ - if self._delay: - time.sleep(self._delay) - - parameter, blank = cmd.split('?') - if blank: - raise ValueError('text found after end of query') - - if self.keep_history: - self.history.append((datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'ask', parameter)) - - return self._model.ask('cmd', self.name + ':' + cmd) - - -# MockModel is purely in service of mock instruments which *are* tested -# so coverage testing this (by running it locally) would be a waste. -class MockModel(ServerManager, BaseServer): # pragma: no cover - - """ - Base class for models to connect to various MockInstruments. - - Creates a separate process that holds the model state, so that - any process can interact with the model and get the same state. - - Args: - name (str): The server name to create for the model. - Default 'Model-{:.7s}' uses the first 7 characters of - the server's uuid. - - for every instrument that connects to this model, create two methods: - - ``_set(param, value)``: set a parameter on the model - - ``_get(param)``: returns the value of a parameter - ``param`` and the set/return values should all be strings - - If ``param`` and/or ``value`` is not recognized, the method should raise - an error. - - Other uses of ServerManager use separate classes for the server and its - manager, but here I put the two together into a single class, to make it - easier to define models. The downside is you have a local object with - methods you shouldn't call: the extras (_(set|get)) should - only be called on the server copy. Normally this should only be called via - the attached instruments anyway. - - The model supports ``NestedAttrAccess`` calls ``getattr``, ``setattr``, - ``callattr``, and ``delattr`` Because the manager and server are the same - object, we override these methods with proxy methods after the server has - been started. - """ - - def __init__(self, name='Model-{:.7s}'): - super().__init__(name, server_class=None) - - # now that the server has started, we can remap attribute access - # from the private methods (_getattr) to the public ones (getattr) - # but the server copy will still have the NestedAttrAccess ones - self.getattr = self._getattr - self.setattr = self._setattr - self.callattr = self._callattr - self.delattr = self._delattr - - def _run_server(self): - self.run_event_loop() - - def handle_cmd(self, cmd): - """ - Handler for all model queries. - - Args: - cmd (str): Can take several forms: - - - ':?': - calls ``self._get()`` and forwards - the return value. - - '::': - calls ``self._set(, )`` - - ':'. - calls ``self._set(, None)`` - - Returns: - Union(str, None): The parameter value, if ``cmd`` has the form - ':?', otherwise no return. - - Raises: - ValueError: if cmd does not match one of the patterns above. - """ - query = cmd.split(':') - - instrument = query[0] - param = query[1] - - if param[-1] == '?' and len(query) == 2: - return getattr(self, instrument + '_get')(param[:-1]) - - elif len(query) <= 3: - value = query[2] if len(query) == 3 else None - getattr(self, instrument + '_set')(param, value) - - else: - raise ValueError() - - def _getattr(self, attr, default=_NoDefault): - """ - Get a (possibly nested) attribute of this model on its server. - - See NestedAttrAccess for details. - """ - return self.ask('method_call', 'getattr', attr, default) - - def _setattr(self, attr, value): - """ - Set a (possibly nested) attribute of this model on its server. - - See NestedAttrAccess for details. - """ - self.ask('method_call', 'setattr', attr, value) - - def _callattr(self, attr, *args, **kwargs): - """ - Call a (possibly nested) method of this model on its server. - - See NestedAttrAccess for details. - """ - return self.ask('method_call', 'callattr', attr, *args, **kwargs) - - def _delattr(self, attr): - """ - Delete a (possibly nested) attribute of this model on its server. - - See NestedAttrAccess for details. - """ - self.ask('method_call', 'delattr', attr) - -class ArrayGetter(MultiParameter): - """ - Example parameter that just returns a single array - - TODO: in theory you can make this an ArrayParameter with - name, label & shape (instead of names, labels & shapes) and altered - setpoints (not wrapped in an extra tuple) and this mostly works, - but when run in a loop it doesn't propagate setpoints to the - DataSet. This is a bug - """ - def __init__(self, measured_param, sweep_values, delay): - name = measured_param.name - super().__init__(names=(name,), - shapes=((len(sweep_values),),), - name=name) - self._instrument = getattr(measured_param, '_instrument', None) - self.measured_param = measured_param - self.sweep_values = sweep_values - self.delay = delay - self.shapes = ((len(sweep_values),),) - set_array = DataArray(parameter=sweep_values.parameter, - preset_data=sweep_values) - self.setpoints = ((set_array,),) - if hasattr(measured_param, 'label'): - self.labels = (measured_param.label,) - - def get(self): - loop = Loop(self.sweep_values, self.delay).each(self.measured_param) - data = loop.run_temp() - array = data.arrays[self.measured_param.full_name] - return (array,) \ No newline at end of file diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 3ab4fb14e7e4..7a6e579b9c4a 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -433,7 +433,9 @@ class ArrayParameter(_BaseParameter): per setpoint array. Ignored if a setpoint is a DataArray, which already has a label. - TODO (alexcjohnson) we need setpoint_units (and in MultiParameter) + setpoint_units (Optional[Tuple[str]]): one label (like ``v``) + per setpoint array. Ignored if a setpoint is a DataArray, which + already has a unit. docstring (Optional[str]): documentation string for the __doc__ field of the object. The __doc__ field of the instance is used by @@ -448,14 +450,14 @@ class ArrayParameter(_BaseParameter): def __init__(self, name, shape, instrument=None, label=None, unit=None, units=None, setpoints=None, setpoint_names=None, setpoint_labels=None, - docstring=None, snapshot_get=True, metadata=None): + setpoint_units=None, docstring=None, snapshot_get=True, metadata=None): super().__init__(name, instrument, snapshot_get, metadata) if self.has_set: # TODO (alexcjohnson): can we support, ala Combine? raise AttributeError('ArrayParameters do not support set ' 'at this time.') - self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', + self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', 'setpoint_units', 'label', 'unit']) self.label = name if label is None else label @@ -488,10 +490,15 @@ def __init__(self, name, shape, instrument=None, not is_sequence_of(setpoint_labels, (nt, str), shape=sp_shape)): raise ValueError('setpoint_labels must be a tuple of strings') + if (setpoint_units is not None and + not is_sequence_of(setpoint_units, (nt, str), + shape=sp_shape)): + raise ValueError('setpoint_units must be a tuple of strings') self.setpoints = setpoints self.setpoint_names = setpoint_names self.setpoint_labels = setpoint_labels + self.setpoint_units = setpoint_units self.__doc__ = os.linesep.join(( 'Parameter class:', @@ -591,6 +598,10 @@ class MultiParameter(_BaseParameter): ``labels``) per setpoint array. Ignored if a setpoint is a DataArray, which already has a label. + setpoint_units (Optional[Tuple[Tuple[str]]]): one unit (like + ``V``) per setpoint array. Ignored if a setpoint is a + DataArray, which already has a unit. + docstring (Optional[str]): documentation string for the __doc__ field of the object. The __doc__ field of the instance is used by some help systems, but not all @@ -604,6 +615,7 @@ class MultiParameter(_BaseParameter): def __init__(self, name, names, shapes, instrument=None, labels=None, units=None, setpoints=None, setpoint_names=None, setpoint_labels=None, + setpoint_units=None, docstring=None, snapshot_get=True, metadata=None): super().__init__(name, instrument, snapshot_get, metadata) @@ -611,7 +623,7 @@ def __init__(self, name, names, shapes, instrument=None, raise AttributeError('MultiParameters do not support set ' 'at this time.') - self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', + self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', 'setpoint_units', 'names', 'labels', 'units']) if not is_sequence_of(names, str): @@ -643,9 +655,14 @@ def __init__(self, name, names, shapes, instrument=None, raise ValueError( 'setpoint_labels must be a tuple of tuples of strings') + if not _is_nested_sequence_or_none(setpoint_units, (nt, str), shapes): + raise ValueError( + 'setpoint_units must be a tuple of tuples of strings') + self.setpoints = setpoints self.setpoint_names = setpoint_names self.setpoint_labels = setpoint_labels + self.setpoint_units = setpoint_units self.__doc__ = os.linesep.join(( 'MultiParameter class:', diff --git a/qcodes/instrument/remote.py b/qcodes/instrument/remote.py deleted file mode 100644 index 37401e80b235..000000000000 --- a/qcodes/instrument/remote.py +++ /dev/null @@ -1,529 +0,0 @@ -"""Proxies to interact with server-based instruments from another process.""" -import multiprocessing as mp - -from qcodes.utils.deferred_operations import DeferredOperations -from qcodes.utils.helpers import DelegateAttributes, named_repr -from .parameter import Parameter, GetLatest -from .server import get_instrument_server_manager - - -class RemoteInstrument(DelegateAttributes): - - """ - A proxy for an instrument (of any class) running on a server process. - - Creates the server if necessary, then loads this instrument onto it, - then mirrors the API to that instrument. - - Args: - *args: Passed along to the real instrument constructor. - - instrument_class (type): The class of the real instrument to make. - - server_name (str): The name of the server to create or use for this - instrument. If not provided (''), gets a name from - ``instrument_class.default_server_name(**kwargs)`` using the - same kwargs passed to the instrument constructor. - - **kwargs: Passed along to the real instrument constructor, also - to ``default_server_name`` as mentioned. - - Attributes: - name (str): an identifier for this instrument, particularly for - attaching it to a Station. - - parameters (Dict[Parameter]): All the parameters supported by this - instrument. Usually populated via ``add_parameter`` - - functions (Dict[Function]): All the functions supported by this - instrument. Usually populated via ``add_function`` - """ - - delegate_attr_dicts = ['_methods', 'parameters', 'functions'] - - def __init__(self, *args, instrument_class=None, server_name='', - **kwargs): - if server_name == '': - server_name = instrument_class.default_server_name(**kwargs) - - shared_kwargs = {} - for kwname in instrument_class.shared_kwargs: - if kwname in kwargs: - shared_kwargs[kwname] = kwargs[kwname] - del kwargs[kwname] - - self._server_name = server_name - self._shared_kwargs = shared_kwargs - self._manager = get_instrument_server_manager(self._server_name, - self._shared_kwargs) - - self._instrument_class = instrument_class - self._args = args - self._kwargs = kwargs - - self.connect() - - def connect(self): - """Create the instrument on the server and replicate its API here.""" - - # connection_attrs is created by instrument.connection_attrs(), - # called by InstrumentServer.handle_new after it creates the instrument - # on the server. - connection_attrs = self._manager.connect(self, self._instrument_class, - self._args, self._kwargs) - - self.name = connection_attrs['name'] - self._id = connection_attrs['id'] - self._methods = {} - self.parameters = {} - self.functions = {} - - # bind all the different categories of actions we need - # to interface with the remote instrument - - self._update_components(connection_attrs) - - def _update_components(self, connection_attrs): - """ - Update the three component dicts with new or updated connection attrs. - - Args: - connection_attrs (dict): as returned by - ``Instrument.connection_attrs``, should contain at least keys - ``_methods``, ``parameters``, and ``functions``, whose values - are themselves dicts of {component_name: list of attributes}. - These get translated into the corresponding dicts eg: - ``self.parameters = {parameter_name: RemoteParameter}`` - """ - component_types = (('_methods', RemoteMethod), - ('parameters', RemoteParameter), - ('functions', RemoteFunction)) - - for container_name, component_class in component_types: - container = getattr(self, container_name) - components_spec = connection_attrs[container_name] - - # first delete components that are gone and update those that - # have changed - for name in list(container.keys()): - if name in components_spec: - container[name].update(components_spec[name]) - else: - del container[name] - - # then add new components - for name, attrs in components_spec.items(): - if name not in container: - container[name] = component_class(name, self, attrs) - - def update(self): - """Check with the server for updated components.""" - connection_attrs = self._ask_server('connection_attrs', self._id) - self._update_components(connection_attrs) - - def _ask_server(self, func_name, *args, **kwargs): - """Query the server copy of this instrument, expecting a response.""" - return self._manager.ask('cmd', self._id, func_name, *args, **kwargs) - - def _write_server(self, func_name, *args, **kwargs): - """Send a command to the server, without waiting for a response.""" - self._manager.write('cmd', self._id, func_name, *args, **kwargs) - - def add_parameter(self, name, **kwargs): - """ - Proxy to add a new parameter to the server instrument. - - This is only for adding parameters remotely to the server copy. - Normally parameters are added in the instrument constructor, rather - than via this method. This method is limited in that you can generally - only use the string form of a command, not the callable form. - - Args: - name (str): How the parameter will be stored within - ``instrument.parameters`` and also how you address it using the - shortcut methods: ``instrument.set(param_name, value)`` etc. - - parameter_class (Optional[type]): You can construct the parameter - out of any class. Default ``StandardParameter``. - - **kwargs: constructor arguments for ``parameter_class``. - """ - attrs = self._ask_server('add_parameter', name, **kwargs) - self.parameters[name] = RemoteParameter(name, self, attrs) - - def add_function(self, name, **kwargs): - """ - Proxy to add a new Function to the server instrument. - - This is only for adding functions remotely to the server copy. - Normally functions are added in the instrument constructor, rather - than via this method. This method is limited in that you can generally - only use the string form of a command, not the callable form. - - Args: - name (str): how the function will be stored within - ``instrument.functions`` and also how you address it using the - shortcut methods: ``instrument.call(func_name, *args)`` etc. - - **kwargs: constructor kwargs for ``Function`` - """ - attrs = self._ask_server('add_function', name, **kwargs) - self.functions[name] = RemoteFunction(name, self, attrs) - - def instances(self): - """ - A RemoteInstrument shows as an instance of its proxied class. - - Returns: - List[Union[Instrument, RemoteInstrument]] - """ - return self._instrument_class.instances() - - def find_instrument(self, name, instrument_class=None): - """ - Find an existing instrument by name. - - Args: - name (str) - - Returns: - Union[Instrument, RemoteInstrument] - - Raises: - KeyError: if no instrument of that name was found, or if its - reference is invalid (dead). - """ - return self._instrument_class.find_instrument( - name, instrument_class=instrument_class) - - def close(self): - """Irreversibly close and tear down the server & remote instruments.""" - if hasattr(self, '_manager'): - if self._manager._server in mp.active_children(): - self._manager.delete(self._id) - del self._manager - self._instrument_class.remove_instance(self) - - def restart(self): - """ - Remove and recreate the server copy of this instrument. - - All instrument state will be returned to the initial conditions, - including deleting any parameters you've added after initialization, - or modifications to parameters etc. - """ - self._manager.delete(self._id) - self.connect() - - def __getitem__(self, key): - """Delegate instrument['name'] to parameter or function 'name'.""" - try: - return self.parameters[key] - except KeyError: - return self.functions[key] - - def __repr__(self): - """repr including the instrument name.""" - return named_repr(self) - - -class RemoteComponent: - - """ - An object that lives inside a RemoteInstrument. - - Proxies all of its calls and specific listed attributes to the - corresponding object in the server instrument. - - Args: - name (str): The name of this component. - - instrument (RemoteInstrument): the instrument this is part of. - - attrs (List[str]): instance attributes to proxy to the server - copy of this component. - - Attributes: - name (str): The name of this component. - - _instrument (RemoteInstrument): the instrument this is part of. - - _attrs (Set[str]): All the attributes we are allowed to proxy. - - _delattrs (Set[str]): Attributes we've deleted from the server, - a subset of ``_attrs``, but if you set them again, they will - still be set on the server. - - _local_attrs (Set[str]): (class attribute only) Attributes that we - shouldn't look for on the server, even if they do not exist - locally. Mostly present to prevent infinite recursion in the - accessors. - """ - _local_attrs = { - '_attrs', - 'name', - '_instrument', - '_local_attrs', - '__doc__', - '_delattrs' - } - - def __init__(self, name, instrument, attrs): - self.name = name - self._instrument = instrument - self.update(attrs) - - def update(self, attrs): - """ - Update the set of attributes proxied by this component. - - The docstring is not proxied every time it is accessed, but it is - read and updated during this method. - - Args: - attrs (Sequence[str]): the new set of attributes to proxy. - """ - self._attrs = set(attrs) - self._delattrs = set() - self._set_doc() - - def __getattr__(self, attr): - """ - Get an attribute value from the server. - - If there was a local attribute, we don't even get here. - """ - if attr not in type(self)._local_attrs and attr in self._attrs: - full_attr = self.name + '.' + attr - return self._instrument._ask_server('getattr', full_attr) - else: - raise AttributeError('RemoteComponent has no local or remote ' - 'attribute: ' + attr) - - def __setattr__(self, attr, val): - """ - Set a new attribute value. - - If the attribute is listed as remote, we'll set it on the server, - otherwise we'll set it locally. - """ - if attr not in type(self)._local_attrs and attr in self._attrs: - full_attr = self.name + '.' + attr - self._instrument._ask_server('setattr', full_attr, val) - if attr in self._delattrs: - self._delattrs.remove(attr) - else: - object.__setattr__(self, attr, val) - - def __delattr__(self, attr): - """ - Delete an attribute. - - If the attribute is listed as remote, we'll delete it on the server, - otherwise we'll delete it locally. - """ - - if attr not in type(self)._local_attrs and attr in self._attrs: - full_attr = self.name + '.' + attr - self._instrument._ask_server('delattr', full_attr) - self._delattrs.add(attr) - - else: - object.__delattr__(self, attr) - - def __dir__(self): - """dir listing including both local and server attributes.""" - remote_attrs = self._attrs - self._delattrs - return sorted(remote_attrs.union(super().__dir__())) - - def _set_doc(self): - """ - Prepend a note about remoteness to the server docstring. - - If no server docstring is found, we leave the class docstring. - - __doc__, as a magic attribute, is handled differently from - other attributes so we won't make it dynamic (updating on the - server when you change it here) - """ - doc = self._instrument._ask_server('getattr', - self.name + '.__doc__') - - docbase = '{} {} in RemoteInstrument {}'.format( - type(self).__name__, self.name, self._instrument.name) - - self.__doc__ = docbase + (('\n---\n\n' + doc) if doc else '') - - def __repr__(self): - """repr including the component name.""" - return named_repr(self) - - -class RemoteMethod(RemoteComponent): - - """Proxy for a method of the server instrument.""" - - def __call__(self, *args, **kwargs): - """Call the method on the server, passing on any args and kwargs.""" - return self._instrument._ask_server(self.name, *args, **kwargs) - - -class RemoteParameter(RemoteComponent, DeferredOperations): - - """Proxy for a Parameter of the server instrument.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.get_latest = GetLatest(self) - - def __call__(self, *args): - """ - Shortcut to get (with no args) or set (with one arg) the parameter. - - Args: - *args: If empty, get the parameter. If one arg, set the parameter - to this value - - Returns: - any: The parameter value, if called with no args, - otherwise no return. - """ - if len(args) == 0: - return self.get() - else: - self.set(*args) - - def get(self): - """ - Read the value of this parameter. - - Returns: - any: the current value of the parameter. - """ - return self._instrument._ask_server('get', self.name) - - def set(self, value): - """ - Set a new value of this parameter. - - Args: - value (any): the new value for the parameter. - """ - # TODO: sometimes we want set to block (as here) and sometimes - # we want it async... which would just be changing the '_ask_server' - # to '_write_server' below. how do we decide, and how do we let the - # user do it? - self._instrument._ask_server('set', self.name, value) - - def validate(self, value): - """ - Raise an error if the given value is not allowed for this Parameter. - - Args: - value (any): the proposed new parameter value. - - Raises: - TypeError: if ``value`` has the wrong type for this Parameter. - ValueError: if the type is correct but the value is wrong. - """ - self._instrument._ask_server('callattr', - self.name + '.validate', value) - - # manually copy over sweep and __getitem__ so they execute locally - # and are still based off the RemoteParameter - def __getitem__(self, keys): - """Create a SweepValues from this parameter with slice notation.""" - return Parameter.__getitem__(self, keys) - - def sweep(self, *args, **kwargs): - """Create a SweepValues from this parameter. See Parameter.sweep.""" - return Parameter.sweep(self, *args, **kwargs) - - def _latest(self): - return self._instrument._ask_server('callattr', self.name + '._latest') - - def snapshot(self, update=False): - """ - State of the parameter as a JSON-compatible dict. - - Args: - update (bool): If True, update the state by querying the - instrument. If False, just use the latest value in memory. - - Returns: - dict: snapshot - """ - return self._instrument._ask_server('callattr', - self.name + '.snapshot', update) - - def setattr(self, attr, value): - """ - Set an attribute of the parameter on the server. - - Args: - attr (str): the attribute name. Can be nested as in - ``NestedAttrAccess``. - value: The new value to set. - """ - self._instrument._ask_server('setattr', self.name + '.' + attr, value) - - def getattr(self, attr): - """ - Get an attribute of the parameter on the server. - - Args: - attr (str): the attribute name. Can be nested as in - ``NestedAttrAccess``. - - Returns: - any: The attribute value. - """ - return self._instrument._ask_server('getattr', self.name + '.' + attr) - - def callattr(self, attr, *args, **kwargs): - """ - Call arbitrary methods of the parameter on the server. - - Args: - attr (str): the method name. Can be nested as in - ``NestedAttrAccess``. - *args: positional args to the method - **kwargs: keyword args to the method - - Returns: - any: the return value of the called method. - """ - return self._instrument._ask_server( - 'callattr', self.name + '.' + attr, *args, **kwargs) - - -class RemoteFunction(RemoteComponent): - - """Proxy for a Function of the server instrument.""" - - def __call__(self, *args): - """ - Call the Function. - - Args: - *args: The positional args to this Function. Functions only take - positional args, not kwargs. - - Returns: - any: the return value of the function. - """ - return self._instrument._ask_server('call', self.name, *args) - - def call(self, *args): - """An alias for __call__.""" - return self.__call__(*args) - - def validate(self, *args): - """ - Raise an error if the given args are not allowed for this Function. - - Args: - *args: the proposed arguments with which to call the Function. - """ - return self._instrument._ask_server( - 'callattr', self.name + '.validate', *args) diff --git a/qcodes/instrument/server.py b/qcodes/instrument/server.py deleted file mode 100644 index 0772ff687838..000000000000 --- a/qcodes/instrument/server.py +++ /dev/null @@ -1,190 +0,0 @@ -import multiprocessing as mp - -from qcodes.process.server import ServerManager, BaseServer - - -def get_instrument_server_manager(server_name, shared_kwargs={}): - """ - Find or make a given `InstrumentServerManager`. - - An `InstrumentServer` holds one or more Instrument objects, and an - `InstrumentServerManager` allows other processes to communicate with this - `InstrumentServer`. - - Both the name and the shared attributes must match exactly. If no manager - exists with this name, it will be created with the given `shared_kwargs`. - If an manager exists with this name but different `shared_kwargs` we - raise an error. - - server_name: (default 'Instruments') which server to put the instrument on. - If a server with this name exists, the instrument will be added to it. - If not, a new server is created with this name. - - shared_kwargs: unpicklable items needed by the instruments on the - server, will get sent with the manager when it's started up - and included in the kwargs to construct each new instrument - """ - if not server_name: - server_name = 'Instruments' - - instances = InstrumentServerManager.instances - manager = instances.get(server_name, None) - - if manager and manager._server in mp.active_children(): - if shared_kwargs and manager.shared_kwargs != shared_kwargs: - # it's OK to add another instrument that has *no* shared_kwargs - # but if there are some and they're different from what's - # already associated with this server, that's an error. - raise ValueError(('An InstrumentServer with name "{}" already ' - 'exists but with different shared_attrs' - ).format(server_name)) - else: - manager = InstrumentServerManager(server_name, shared_kwargs) - - return manager - - -class InstrumentServerManager(ServerManager): - """ - Creates and manages connections to an InstrumentServer - - Args: - name: the name of the server to create - kwargs: extra items to send to the server on creation (such as - additional queues, that can only be shared on creation) - These items will be set as attributes of any instrument that - connects to the server - """ - instances = {} - - def __init__(self, name, shared_kwargs=None): - self.name = name - self.shared_kwargs = shared_kwargs - self.instances[name] = self - - self.instruments = {} - - super().__init__(name=name, server_class=InstrumentServer, - shared_attrs=shared_kwargs) - - def restart(self): - """ - Restart the InstrumentServer and reconnect the instruments that - had been connected previously - """ - super().restart() - - instruments = self.instruments.values() - self.instruments = {} - for instrument in instruments: - instrument.connect() - - def connect(self, remote_instrument, instrument_class, args, kwargs): - new_id = self.ask('new_id') - try: - info = self.ask('new', instrument_class, new_id, *args, **kwargs) - self.instruments[new_id] = remote_instrument - - except: - # if anything went wrong adding a new instrument, delete it - # in case it still exists there half-formed. - self.delete(new_id) - raise - - return info - - def delete(self, instrument_id): - self.write('delete', instrument_id) - - if self.instruments.get(instrument_id, None): - del self.instruments[instrument_id] - - if not self.instruments: - self.close() - self.instances.pop(self.name, None) - - -class InstrumentServer(BaseServer): - # just for testing - how long to allow it to wait on a queue.get - timeout = None - - def __init__(self, query_queue, response_queue, shared_kwargs): - super().__init__(query_queue, response_queue, shared_kwargs) - - self.instruments = {} - self.next_id = 0 - - # Ensure no references of instruments defined in the main process - # are copied to the server process. With the spawn multiprocessing - # method this is not an issue, as the class is reimported in the - # new process, but with fork it can be a problem ironically. - from qcodes.instrument.base import Instrument - Instrument._all_instruments = {} - - self.run_event_loop() - - def handle_new_id(self): - """ - split out id generation from adding an instrument - so that we can delete it if something goes wrong! - """ - new_id = self.next_id - self.next_id += 1 - return new_id - - def handle_new(self, instrument_class, new_id, *args, **kwargs): - """ - Add a new instrument to the server. - - After the initial load, the instrument is referred to by its ID. - - Args: - instrument_class (class): The type of instrument to construct. - - new_id (int): The ID by which this instrument will be known on the - server. - - *args: positional arguments to the instrument constructor. - - **kwargs: keyword arguments to the instrument constructor. - - Returns: - dict: info to reconstruct this instrument's API in the remote. - See ``Instrument.connection_attrs`` for details. - """ - - # merge shared_kwargs into kwargs for the constructor, - # but only if this instrument_class is expecting them. - # The *first* instrument put on a given server must have - # all the shared_kwargs sent with it, but others may skip - # (for now others must have *none* but later maybe they could - # just skip some of them) - for key, value in self._shared_attrs.items(): - if key in instrument_class.shared_kwargs: - kwargs[key] = value - ins = instrument_class(*args, server_name=None, **kwargs) - - self.instruments[new_id] = ins - - # info to reconstruct the instrument API in the RemoteInstrument - return ins.connection_attrs(new_id) - - def handle_delete(self, instrument_id): - """ - Delete an instrument from the server, and stop the server if their - are no more instruments left after this. - """ - if instrument_id in self.instruments: - self.instruments[instrument_id].close() - - del self.instruments[instrument_id] - - if not any(self.instruments): - self.handle_halt() - - def handle_cmd(self, instrument_id, func_name, *args, **kwargs): - """ - Run some method of an instrument - """ - func = getattr(self.instruments[instrument_id], func_name) - return func(*args, **kwargs) diff --git a/qcodes/instrument/visa.py b/qcodes/instrument/visa.py index d020a65ac2b5..5d341ea74025 100644 --- a/qcodes/instrument/visa.py +++ b/qcodes/instrument/visa.py @@ -1,6 +1,5 @@ """Visa instrument driver based on pyvisa.""" import visa -import logging from .base import Instrument import qcodes.utils.validators as vals @@ -26,13 +25,6 @@ class VisaInstrument(Instrument): terminator: Read termination character(s) to look for. Default ''. - server_name (str): Name of the InstrumentServer to use. By default - uses 'GPIBServer' for all GPIB instruments, 'SerialServer' for - serial port instruments, and 'VisaServer' for all others. - - Use ``None`` to run locally - but then this instrument will not - work with qcodes Loops or other multiprocess procedures. - metadata (Optional[Dict]): additional static metadata to add to this instrument's JSON snapshot. @@ -57,26 +49,6 @@ def __init__(self, name, address=None, timeout=5, terminator='', **kwargs): self.set_terminator(terminator) self.timeout.set(timeout) - @classmethod - def default_server_name(cls, **kwargs): - """ - Get the default server name for this instrument. - - Args: - **kwargs: All the kwargs supplied in the constructor. - - Returns: - str: The default server name, either 'GPIBServer', 'SerialServer', - or 'VisaServer' depending on ``kwargs['address']``. - """ - upper_address = kwargs.get('address', '').upper() - if 'GPIB' in upper_address: - return 'GPIBServer' - elif 'ASRL' in upper_address: - return 'SerialServer' - - return 'VisaServer' - def set_address(self, address): """ Change the address for this instrument. diff --git a/qcodes/instrument_drivers/HP/HP_83650A.py b/qcodes/instrument_drivers/HP/HP_83650A.py new file mode 100644 index 000000000000..8400aba6fca2 --- /dev/null +++ b/qcodes/instrument_drivers/HP/HP_83650A.py @@ -0,0 +1,124 @@ +# Driver for microwave source HP_83650A +# +# Written by Bruno Buijtendorp (brunobuijtendorp@gmail.com) + + +import logging +from qcodes import VisaInstrument +from qcodes import validators as vals + +log = logging.getLogger(__name__) + + +def parsestr(v): + return v.strip().strip('"') + + +class HP_83650A(VisaInstrument): + + def __init__(self, name, address, verbose=1, reset=False, server_name=None, **kwargs): + """ Driver for HP_83650A + + """ + self.verbose = verbose + log.debug('Initializing instrument') + super().__init__(name, address, **kwargs) + + self.add_parameter('frequency', + label='Frequency', + get_cmd='FREQ:CW?', + set_cmd='FREQ:CW {}', + vals=vals.Numbers(10e6, 40e9), + docstring='Microwave frequency, ....', + get_parser=float, + unit='Hz') + + self.add_parameter('freqmode', + label='Frequency mode', + get_cmd='FREQ:MODE?', + set_cmd='FREQ:MODE {}', + vals=vals.Strings(), + get_parser=parsestr, + docstring='Microwave frequency mode, ....') + + self.add_parameter('power', + label='Power', + get_cmd='SOUR:POW?', + set_cmd='SOUR:POW {}', + vals=vals.Numbers(-20, 20), + get_parser=float, + unit='dBm', + docstring='Microwave power, ....') + + self.add_parameter('rfstatus', + label='RF status', + get_cmd=':POW:STAT?', + set_cmd=':POW:STAT {}', + val_mapping={'on': '1', 'off': '0'}, + vals=vals.Strings(), + get_parser=parsestr, + docstring='Status, ....') + + self.add_parameter('fmstatus', + label='FM status', + get_cmd=':FM:STAT?', + set_cmd=':FM:STAT {}', + val_mapping={'on': '1', 'off': '0'}, + vals=vals.Strings(), + get_parser=parsestr, + docstring='FM status, ....') + + self.add_parameter('fmcoup', + label='FM coupling', + get_cmd=':FM:COUP?', + set_cmd=':FM:COUP {}', + vals=vals.Strings(), + get_parser=parsestr, + docstring='FM coupling, ....') + + self.add_parameter('amstatus', + label='AM status', + get_cmd=':AM:STAT?', + set_cmd=':AM:STAT {}', + val_mapping={'on': '1', 'off': '0'}, + vals=vals.Strings(), + get_parser=parsestr, + docstring='AM status, ....') + + self.add_parameter('pulsestatus', + label='Pulse status', + get_cmd=':PULS:STAT?', + set_cmd=':PULS:STAT {}', + val_mapping={'on': '1', 'off': '0'}, + vals=vals.Strings(), + get_parser=parsestr, + docstring='Pulse status, ....') + + self.add_parameter('pulsesource', + label='Pulse source', + get_cmd=':PULS:SOUR?', + set_cmd=':PULS:SOUR {}', + vals=vals.Strings(), + get_parser=parsestr, + docstring='Pulse source, ....') + + def reset(self): + log.debug('Resetting instrument') + self.write('*RST') + self.print_all() + + def print_all(self): + log.debug('Reading all settings from instrument') + print(self.rfstatus.label + ':', self.rfstatus.get()) + print(self.power.label + ':', self.power.get(), self.power.unit) + print(self.frequency.label + + ': %e' % self.frequency.get(), self.frequency.unit) + print(self.freqmode.label + ':', self.freqmode.get()) + self.print_modstatus() + + def print_modstatus(self): + print(self.fmstatus.label + ':', self.fmstatus.get()) + print(self.fmcoup.label + ':', self.fmcoup.get()) + print(self.amstatus.label + ':', self.amstatus.get()) + print(self.pulsestatus.label + ':', self.pulsestatus.get()) + print(self.pulsesource.label + ':', self.pulsesource.get()) diff --git a/qcodes/instrument_drivers/Keysight/Keysight_33500B.py b/qcodes/instrument_drivers/Keysight/Keysight_33500B.py new file mode 100644 index 000000000000..2a12f42be0bd --- /dev/null +++ b/qcodes/instrument_drivers/Keysight/Keysight_33500B.py @@ -0,0 +1,107 @@ +from qcodes import VisaInstrument, validators as vals +from pyvisa.errors import VisaIOError +import logging + +log = logging.getLogger(__name__) + + +class Keysight_33500B(VisaInstrument): + + def __init__(self, name, address, **kwargs): + + super().__init__(name, address, **kwargs) + + # stupid helper functions + def setcmd(channel, setting): + return 'SOURce{}:'.format(channel) + setting + ' {}' + + def getcmd(channel, setting): + return 'SOURce{}:'.format(channel) + setting + '?' + + for chan in [1, 2]: + + self.add_parameter('ch{}_function_type'.format(chan), + label='Channel {} function type'.format(chan), + set_cmd=setcmd(chan, 'FUNCtion'), + get_cmd=getcmd(chan, 'FUNCtion'), + vals=vals.Enum('SIN', 'SQU', 'TRI', 'RAMP', + 'PULS', 'PRBS', 'NOIS', 'ARB', + 'DC') + ) + + self.add_parameter('ch{}_frequency_mode'.format(chan), + label='Channel {} frequency mode'.format(chan), + set_cmd=setcmd(chan, 'FREQuency:MODE'), + get_cmd=getcmd(chan, 'FREQuency:MODE'), + vals=vals.Enum('CW', 'LIST', 'SWEEP', 'FIXED') + ) + + self.add_parameter('ch{}_frequency'.format(chan), + label='Channel {} frequency'.format(chan), + set_cmd=setcmd(chan, 'FREQuency'), + get_cmd=getcmd(chan, 'FREQUency'), + get_parser=float, + unit='Hz', + # TODO: max. freq. actually really tricky + vals=vals.Numbers(1e-6, 30e6) + ) + + self.add_parameter('ch{}_phase'.format(chan), + label='Channel {} phase'.format(chan), + set_cmd=setcmd(chan, 'PHASe'), + get_cmd=getcmd(chan, 'PHASe'), + get_parser=float, + unit='deg', + vals=vals.Numbers(0, 360) + ) + self.add_parameter('ch{}_amplitude_unit'.format(chan), + label='Channel {} amplitude unit'.format(chan), + set_cmd=setcmd(chan, 'VOLTage:UNIT'), + get_cmd=getcmd(chan, 'VOLTage:UNIT'), + vals=vals.Enum('VPP', 'VRMS', 'DBM') + ) + + self.add_parameter('ch{}_amplitude'.format(chan), + label='Channel {} amplitude'.format(chan), + set_cmd=setcmd(chan, 'VOLTage'), + get_cmd=getcmd(chan, 'VOLTage'), + unit='', # see amplitude_unit + get_parser=float) + + self.add_parameter('ch{}_offset'.format(chan), + label='Channel {} voltage offset'.format(chan), + set_cmd=setcmd(chan, 'VOLTage:OFFSet'), + get_cmd=getcmd(chan, 'VOLTage:OFFSet'), + unit='V', + get_parser=float + ) + self.add_parameter('ch{}_output'.format(chan), + label='Channel {} output state'.format(chan), + set_cmd='OUTPut{}'.format(chan) + ' {}', + get_cmd='OUTPut{}?'.format(chan), + val_mapping={'ON': 1, 'OFF': 0} + ) + + self.add_parameter('ch{}_ramp_symmetry'.format(chan), + label='Channel {} ramp symmetry'.format(chan), + set_cmd=setcmd(chan, 'FUNCtion:RAMP:SYMMetry'), + get_cmd=getcmd(chan, 'FUNCtion:RAMP:SYMMetry'), + get_parser=float, + vals=vals.Numbers(0, 100) + ) + + self.add_parameter('sync_source', + label='Source of sync function', + set_cmd='OUTPut:SYNC:SOURce {}', + get_cmd='OUTPut:SYNC:SOURce?', + val_mapping={1: 'CH1', 2: 'CH2'}, + vals=vals.Enum(1, 2) + ) + + self.add_parameter('sync_output', + label='Sync output state', + set_cmd='OUTPut:SYNC {}', + get_cmd='OUTPut:SYNC?', + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') + ) diff --git a/qcodes/instrument_drivers/Keysight/Keysight_34465A.py b/qcodes/instrument_drivers/Keysight/Keysight_34465A.py new file mode 100644 index 000000000000..4113c7ba9620 --- /dev/null +++ b/qcodes/instrument_drivers/Keysight/Keysight_34465A.py @@ -0,0 +1,399 @@ +# QCoDeS driver for the Keysight 34465A Digital Multimeter + +from functools import partial +import numpy as np +import logging + +from qcodes.instrument.parameter import ArrayParameter +import qcodes.utils.validators as vals +from qcodes import VisaInstrument + +log = logging.getLogger(__name__) + + +class ArrayMeasurement(ArrayParameter): + """ + Class to return several values. Really represents a measurement routine. + """ + + def __init__(self, name, shape=(1,), *args, **kwargs): + + super().__init__(name, shape=shape, *args, **kwargs) + + self.label = '' + self.unit = '' + self.properly_prepared = False + + def prepare(self): + """ + Prepare the measurement, create the setpoints. + + There is some randomness in the measurement times. + """ + + inst = self._instrument + + N = inst.sample_count() + + # ensure correct instrument settings + inst.aperture_mode('OFF') # aperture mode seems slower ON than OFF + inst.trigger_count(1) + inst.trigger_delay(0) + inst.sample_count_pretrigger(0) + inst.sample_source('TIM') + inst.autorange('OFF') + + if inst.trigger_source() is None: + raise ValueError('Trigger source unspecified! Please set ' + "trigger_source to 'INT' or 'EXT'.") + + # Final step + self.time_per_point = inst.sample_timer_minimum() + inst.sample_timer(self.time_per_point) + + self.setpoints = (tuple(np.linspace(0, N*self.time_per_point, N)),) + self.shape = (N,) + + self.properly_prepared = True + + def get(self): + + if not self.properly_prepared: + raise ValueError('ArrayMeasurement not properly_prepared. ' + 'Please run prepare().') + + N = self._instrument.sample_count() + log.debug("Acquiring {} samples.".format(N)) + + # Ensure that the measurement doesn't time out + # TODO (WilliamHPNielsen): What if we wait really long for a trigger? + old_timeout = self._instrument.visa_handle.timeout + self._instrument.visa_handle.timeout = N*1000*1.2*self.time_per_point + self._instrument.visa_handle.timeout += old_timeout + + # Turn off the display to increase measurement speed + self._instrument.display_text('Acquiring {} samples'.format(N)) + + self._instrument.init_measurement() + rawvals = self._instrument.ask('FETCH?') + + self._instrument.visa_handle.timeout = old_timeout + + # parse the aqcuired values + try: + numvals = np.array(list(map(float, rawvals.split(',')))) + except AttributeError: + numvals = None + + self._instrument.display_clear() + + return numvals + + +class Keysight_34465A(VisaInstrument): + """ + Instrument class for Keysight 34465A. + + + The driver is written such that usage with models + 34460A, 34461A, and 34470A should be seamless. + + Tested with: 34465A. + + The driver currently only supports using the instrument as a voltmeter. + + Attributes: + model (str): The model number of the instrument + NPLC_list (list): A list of the available Power Line Cycle settings + ranges (list): A list of the available voltage ranges + """ + + def __init__(self, name, address, DIG=False, utility_freq=50, silent=False, + **kwargs): + """ + Create an instance of the instrument. + + Args: + name (str): Name used by QCoDeS. Appears in the DataSet + address (str): Visa-resolvable instrument address. + utility_freq (int): The local utility frequency in Hz. Default: 50 + DIG (bool): Is the DIG option installed on the instrument? + Default: False. + silent (bool): If True, the connect_message of the instrument + is supressed. Default: False + Returns: + Keysight_34465A + """ + if utility_freq not in [50, 60]: + raise ValueError('Can not set utility frequency to ' + '{}. '.format(utility_freq) + + 'Please enter either 50 Hz or 60 Hz.') + + super().__init__(name, address, terminator='\n', **kwargs) + + idn = self.IDN.get() + self.model = idn['model'] + + #################################### + # Instrument specifications + + PLCs = {'34460A': [0.02, 0.2, 1, 10, 100], + '34461A': [0.02, 0.2, 1, 10, 100], + '34465A': [0.02, 0.06, 0.2, 1, 10, 100], + '34470A': [0.02, 0.06, 0.2, 1, 10, 100] + } + if DIG: + PLCs['34465A'] = [0.001, 0.002, 0.006] + PLCs['34465A'] + PLCs['34470A'] = [0.001, 0.002, 0.006] + PLCs['34470A'] + + ranges = {'34460A': [10**n for n in range(-3, 9)], # 1 m to 100 M + '34461A': [10**n for n in range(-3, 9)], # 1 m to 100 M + '34465A': [10**n for n in range(-3, 10)], # 1 m to 1 G + '34470A': [10**n for n in range(-3, 10)], # 1 m to 1 G + } + + # The resolution factor order matches the order of PLCs + res_factors = {'34460A': [300e-6, 100e-6, 30e-6, 10e-6, 3e-6], + '34461A': [100e-6, 10e-6, 3e-6, 1e-6, 0.3e-6], + '34465A': [3e-6, 1.5e-6, 0.7e-6, 0.3e-6, 0.1e-6, + 0.03e-6], + '34470A': [1e-6, 0.5e-6, 0.3e-6, 0.1e-6, 0.03e-6, + 0.01e-6] + } + if DIG: + res_factors['34465A'] = [30e6, 15e-6, 6e-6] + res_factors['34464A'] + res_factors['34470A'] = [30e-6, 10e-6, 3e-6] + res_factors['34470A'] + + # Define the extreme aperture time values for the 34465A and 34470A + if utility_freq == 50: + apt_times = {'34465A': [0.3e-3, 2], + '34470A': [0.3e-3, 2]} + if utility_freq == 60: + apt_times = {'34465A': [0.3e-3, 1.67], + '34470A': [0.3e-3, 1.67]} + if DIG: + apt_times['34465A'][0] = 20e-6 + apt_times['34470A'][0] = 20e-6 + + self._resolution_factors = res_factors[self.model] + self.ranges = ranges[self.model] + self.NPLC_list = PLCs[self.model] + self._apt_times = apt_times[self.model] + + #################################### + # PARAMETERS + + self.add_parameter('NPLC', + get_cmd='SENSe:VOLTage:DC:NPLC?', + get_parser=float, + set_cmd=self._set_NPLC, + vals=vals.Enum(*self.NPLC_list), + label='Integration time', + unit='NPLC') + + self.add_parameter('volt', + + get_cmd=self._get_voltage, + label='Voltage', + unit='V') + + self.add_parameter('range', + get_cmd='SENSe:VOLTage:DC:RANGe?', + get_parser=float, + set_cmd='SENSe:VOLTage:DC:RANGe {:f}', + vals=vals.Enum(*self.ranges)) + + self.add_parameter('resolution', + get_cmd='SENSe:VOLTage:DC:RESolution?', + get_parser=float, + set_cmd=self._set_resolution, + label='Resolution', + unit='V') + + self.add_parameter('autorange', + label='Autorange', + set_cmd='SENSe:VOLtage:DC:RANGe:AUTO {}', + get_cmd='SENSe:VOLtage:DC:RANGe:AUTO?', + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF')) + + self.add_parameter('display_text', + label='Display text', + set_cmd='DISPLAY:TEXT "{}"', + get_cmd='DISPLAY:TEXT?', + vals=vals.Strings()) + + # TRIGGERING + + self.add_parameter('trigger_count', + label='Trigger Count', + set_cmd='TRIGger:COUNt {}', + get_cmd='TRIGger:COUNt?', + get_parser=float, + vals=vals.MultiType(vals.Numbers(1, 1e6), + vals.Enum('MIN', 'MAX', 'DEF', + 'INF'))) + + self.add_parameter('trigger_delay', + label='Trigger Delay', + set_cmd='TRIGger:DELay {}', + get_cmd='TRIGger:DELay?', + vals=vals.MultiType(vals.Numbers(0, 3600), + vals.Enum('MIN', 'MAX', 'DEF')), + get_parser=float) + + self.add_parameter('trigger_slope', + label='Trigger Slope', + set_cmd='TRIGger:SLOPe {}', + get_cmd='TRIGger:SLOPe?', + vals=vals.Enum('POS', 'NEG')) + + self.add_parameter('trigger_source', + label='Trigger Source', + set_cmd='TRIGger:SOURce {}', + get_cmd='TRIGger:SOURce?', + vals=vals.Enum('IMM', 'EXT', 'BUS', 'INT')) + + # SAMPLING + + self.add_parameter('sample_count', + label='Sample Count', + set_cmd=partial(self._set_databuffer_setpoints, + 'SAMPle:COUNt {}'), + get_cmd='SAMPle:COUNt?', + vals=vals.MultiType(vals.Numbers(1, 1e6), + vals.Enum('MIN', 'MAX', 'DEF')), + get_parser=int) + + self.add_parameter('sample_count_pretrigger', + label='Sample Pretrigger Count', + set_cmd='SAMPle:COUNt:PRETrigger {}', + get_cmd='SAMPle:COUNt:PRETrigger?', + vals=vals.MultiType(vals.Numbers(0, 2e6-1), + vals.Enum('MIN', 'MAX', 'DEF')), + get_parser=int, + docstring=('Allows collection of the data ' + 'being digitized the trigger. Reserves ' + 'memory for pretrigger samples up to the' + ' specified num. of pretrigger samples.') + ) + + self.add_parameter('sample_source', + label='Sample Timing Source', + set_cmd='SAMPle:SOURce {}', + get_cmd='SAMPle:SOURce?', + vals=vals.Enum('IMM', 'TIM'), + docstring=('Determines sampling time, immediate' + ' or using sample_timer')) + + self.add_parameter('sample_timer', + label='Sample Timer', + set_cmd='SAMPle:TIMer {}', + get_cmd='SAMPle:TIMer?', + unit='s', + vals=vals.MultiType(vals.Numbers(0, 3600), + vals.Enum('MIN', 'MAX', 'DEF')), + get_parser=float) + + self.add_parameter('sample_timer_minimum', + label='Minimal sample time', + get_cmd='SAMPle:TIMer? MIN', + get_parser=float, + unit='s') + + # The array parameter + self.add_parameter('data_buffer', + parameter_class=ArrayMeasurement) + + #################################### + # Model-specific parameters + + if self.model in ['34465A', '34470A']: + + self.add_parameter('aperture_mode', + label='Aperture mode', + set_cmd='SENSe:VOLTage:DC:APERture:ENABled {}', + get_cmd='SENSe:VOLTage:DC:APERture:ENABled?', + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF')) + + self.add_parameter('aperture_time', + label='Aperture time', + set_cmd=self._set_apt_time, + get_cmd='SENSe:VOLTage:DC:APERture?', + get_parser=float, + vals=vals.Numbers(*self._apt_times), + docstring=('Setting the aperture time ' + 'automatically enables the aperture' + ' mode.')) + + self.add_function('init_measurement', call_cmd='INIT') + self.add_function('reset', call_cmd='*RST') + self.add_function('display_clear', call_cmd=('DISPLay:TEXT:CLEar')) + + if not silent: + self.connect_message() + + def _get_voltage(self): + # TODO: massive improvements! What if more than one number was + # asked for? + # What if mode is not voltage sensing? What if... + + response = self.ask('READ?') + + return float(response) + + def _set_databuffer_setpoints(self, cmd, value): + """ + set_cmd for all databuffer-setpoint related parameters + """ + + self.data_buffer.properly_prepared = False + self.write(cmd.format(value)) + + def _set_apt_time(self, value): + self.write('SENSe:VOLTage:DC:APERture {:f}'.format(value)) + + # setting aperture time switches aperture mode ON + self.aperture_mode.get() + + def _set_NPLC(self, value): + self.write('SENSe:VOLTage:DC:NPLC {:f}'.format(value)) + + # This will change data_buffer setpoints (timebase) + self.data_buffer.properly_prepared = False + + # resolution settings change with NPLC + self.resolution.get() + + # setting NPLC switches off aperture mode + if self.model in ['34465A', '34470A']: + self.aperture_mode.get() + + def _set_range(self, value): + self.write('SENSe:VOLTage:DC:RANGe {:f}'.format(value)) + + # resolution settings change with range + + self.resolution.get() + + def _set_resolution(self, value): + rang = self.range.get() + + # convert both value*range and the resolution factors + # to strings with few digits, so we avoid floating point + # rounding errors. + res_fac_strs = ['{:.1e}'.format(v * rang) + for v in self._resolution_factors] + if '{:.1e}'.format(value) not in res_fac_strs: + raise ValueError( + 'Resolution setting {:.1e} ({} at range {}) ' + 'does not exist. ' + 'Possible values are {}'.format(value, value, rang, + res_fac_strs)) + + self.write('VOLT:DC:RES {:.1e}'.format(value)) + + # NPLC settings change with resolution + + self.NPLC.get() diff --git a/qcodes/process/__init__.py b/qcodes/instrument_drivers/Keysight/__init__.py similarity index 100% rename from qcodes/process/__init__.py rename to qcodes/instrument_drivers/Keysight/__init__.py diff --git a/qcodes/instrument_drivers/QDev/QDac.py b/qcodes/instrument_drivers/QDev/QDac.py index 66f1dc0c0179..ee60200ba68c 100644 --- a/qcodes/instrument_drivers/QDev/QDac.py +++ b/qcodes/instrument_drivers/QDev/QDac.py @@ -31,7 +31,7 @@ class QDac(VisaInstrument): # set nonzero value (seconds) to accept older status when reading settings max_status_age = 1 - def __init__(self, name, address, num_chans=48): + def __init__(self, name, address, num_chans=48, update_currents=True): """ Instantiates the instrument. @@ -39,6 +39,8 @@ def __init__(self, name, address, num_chans=48): name (str): The instrument name used by qcodes address (str): The VISA name of the resource num_chans (int): Number of channels to assign. Default: 48 + update_currents (bool): Whether to query all channels for their + current current value on startup. Default: True. Returns: QDac object @@ -68,6 +70,9 @@ def __init__(self, name, address, num_chans=48): # Assigned slopes. Entries will eventually be [chan, slope] (V/s) self._slopes = [] + # Function generators (used in _set_voltage) + self._fgs = set(range(1, 9)) + self._assigned_fgs = {} self.chan_range = range(1, 1 + self.num_chans) self.channel_validator = vals.Ints(1, self.num_chans) @@ -77,7 +82,6 @@ def __init__(self, name, address, num_chans=48): self.add_parameter(name='ch{:02}_v'.format(i), label='Channel ' + stri, unit='V', - # TO-DO: implement max slope for setting set_cmd=partial(self._set_voltage, i), vals=vals.Numbers(-10, 10), get_cmd=partial(self.read_state, i, 'v') @@ -99,6 +103,7 @@ def __init__(self, name, address, num_chans=48): ) self.add_parameter(name='ch{:02}_slope'.format(i), label='Maximum voltage slope', + unit='V/s', set_cmd=partial(self._setslope, i), get_cmd=partial(self._getslope, i), vals=vals.Anything() @@ -122,16 +127,15 @@ def __init__(self, name, address, num_chans=48): set_cmd='ver {}', val_mapping={True: 1, False: 0}) - # Initialise the instrument, all channels DC, no attenuation + # Initialise the instrument, all channels DC (unbind func. generators) for chan in self.chan_range: # Note: this call does NOT change the voltage on the channel self.write('wav {} 0 1 0'.format(chan)) - self.write('vol {} 0'.format(chan)) self.verbose.set(False) self.connect_message() log.info('[*] Querying all channels for voltages and currents...') - self._get_status(readcurrents=True) + self._get_status(readcurrents=update_currents) log.info('[+] Done') ######################### @@ -156,8 +160,9 @@ def _set_voltage(self, chan, v_set): slopechans = [sl[0] for sl in self._slopes] if chan in slopechans: - slope = [sl[1] for sl in self._slopes if sl[0]==chan][0] - fg = self._slopes.index([chan, slope]) + 1 + slope = [sl[1] for sl in self._slopes if sl[0] == chan][0] + fg = min(self._fgs.difference(set(self._assigned_fgs.values()))) + self._assigned_fgs[chan] = fg v_start = self.parameters['ch{:02}_v'.format(chan)].get_latest() time = abs(v_set-v_start)/slope # Attenuation compensation takes place inside _rampvoltage @@ -328,18 +333,25 @@ def _setslope(self, chan, slope): raise ValueError('Channel number must be 1-48.') if slope == 'Inf': + self.write('wav {} 0 0 0'.format(chan)) + + # Now clear the assigned slope and function generator (if possible) + try: + self._assigned_fgs.pop(chan) + except KeyError: + pass try: sls = self._slopes - to_remove = [sls.index(sl) for sl in sls if sl[0]==chan][0] + to_remove = [sls.index(sl) for sl in sls if sl[0] == chan][0] self._slopes.remove(sls[to_remove]) return # If the value was already 'Inf', the channel was not # in the list and nothing happens - except ValueError: + except IndexError: return if chan in [sl[0] for sl in self._slopes]: - oldslope = [sl[1] for sl in self._slopes if sl[0]==chan][0] + oldslope = [sl[1] for sl in self._slopes if sl[0] == chan][0] self._slopes[self._slopes.index([chan, oldslope])] = [chan, slope] return diff --git a/qcodes/instrument_drivers/ZI/ZIUHFLI.py b/qcodes/instrument_drivers/ZI/ZIUHFLI.py new file mode 100644 index 000000000000..68876d9a3aee --- /dev/null +++ b/qcodes/instrument_drivers/ZI/ZIUHFLI.py @@ -0,0 +1,1977 @@ +import time +import logging +import numpy as np +from functools import partial + +try: + import zhinst.utils +except ImportError: + raise ImportError('''Could not find Zurich Instruments Lab One software. + Please refer to the Zi UHF-LI User Manual for + download and installation instructions. + ''') + +from qcodes.instrument.parameter import ManualParameter +from qcodes.instrument.parameter import MultiParameter +from qcodes.instrument.base import Instrument +from qcodes.utils import validators as vals + +log = logging.getLogger(__name__) + + +class Sweep(MultiParameter): + """ + Parameter class for the ZIUHFLI instrument class for the sweeper. + + The get method returns a tuple of arrays, where each array contains the + values of a signal added to the sweep (e.g. demodulator 4 phase). + + Attributes: + names (tuple): Tuple of strings containing the names of the sweep + signals (to be measured) + units (tuple): Tuple of strings containg the units of the signals + shapes (tuple): Tuple of tuples each containing the Length of a + signal. + setpoints (tuple): Tuple of N copies of the sweep x-axis points, + where N is he number of measured signals + setpoint_names (tuple): Tuple of N identical strings with the name + of the sweep x-axis. + + """ + def __init__(self, name, instrument, **kwargs): + # The __init__ requires that we supply names and shapes, + # but there is no way to know what they could be known at this time. + # They are updated via build_sweep. + super().__init__(name, names=('',), shapes=((1,),), **kwargs) + self._instrument = instrument + + def build_sweep(self): + """ + Build a sweep with the current sweep settings. Must be called + before the sweep can be executed. + + For developers: + This is a general function for updating the sweeper. + Every time a parameter of the sweeper is changed, this function + must be called to update the sweeper. Although such behaviour is only + strictly necessary for parameters that affect the setpoints of the + Sweep parameter, having to call this function for any parameter is + deemed more user friendly (easier to remember; when? -always). + + The function sets all (user specified) settings on the sweeper and + additionally sets names, units, and setpoints for the Sweep + parameter. + + """ + + signals = self._instrument._sweeper_signals + sweepdict = self._instrument._sweepdict + + log.info('Built a sweep') + + sigunits = {'X': 'V', 'Y': 'V', 'R': 'Vrms', 'Xrms': 'Vrms', + 'Yrms': 'Vrms', 'Rrms': 'Vrms', 'phase': 'degrees'} + names = [] + units = [] + for sig in signals: + name = sig.split('/')[-1] + names.append(name) + units.append(sigunits[name]) + self.names = tuple(names) + self.units = tuple(units) + self.labels = tuple(names) # TODO: What are good labels? + + # TODO: what are good set point names? + spnamedict = {'auxouts/0/offset': 'Volts', + 'auxouts/1/offset': 'Volts', + 'auxouts/2/offset': 'Volts', + 'auxouts/3/offset': 'Volts', + 'demods/0/phaseshift': 'degrees', + 'demods/1/phaseshift': 'degrees', + 'demods/2/phaseshift': 'degrees', + 'demods/3/phaseshift': 'degrees', + 'demods/4/phaseshift': 'degrees', + 'demods/5/phaseshift': 'degrees', + 'demods/6/phaseshift': 'degrees', + 'demods/7/phaseshift': 'degrees', + 'oscs/0/freq': 'Hz', + 'oscs/1/freq': 'Hz', + 'sigouts/0/amplitudes/3': 'Volts', + 'sigouts/0/offset': 'Volts', + 'sigouts/1/amplitudes/7': 'Volts', + 'sigouts/1/offset': 'Volts' + } + sp_name = spnamedict[sweepdict['gridnode']] + + self.setpoint_names = ((sp_name,),)*len(signals) + start = sweepdict['start'] + stop = sweepdict['stop'] + npts = sweepdict['samplecount'] + # TODO: make sure that these setpoints are correct, i.e. actually + # matching what the UHFLI does + # TODO: support non-sequential sweep mode + if not sweepdict['scan'] == 0: + raise NotImplementedError('Only sequential scanning is supported.') + if sweepdict['xmapping'] == 'lin': + sw = tuple(np.linspace(start, stop, npts)) + else: + logstart = np.log10(start) + logstop = np.log10(stop) + sw = tuple(np.logspace(logstart, logstop, npts)) + self.setpoints = ((sw,),)*len(signals) + self.shapes = ((npts,),)*len(signals) + + # Now actually send the settings to the instrument + for (setting, value) in sweepdict.items(): + setting = 'sweep/' + setting + self._instrument.sweeper.set(setting, value) + + self._instrument.sweep_correctly_built = True + + def get(self): + """ + Execute the sweeper and return the data corresponding to the + subscribed signals. + + Returns: + + tuple: Tuple containg N numpy arrays where N is the number + of signals added to the sweep. + + Raises: + ValueError: If no signals have been added to the sweep + ValueError: If a sweep setting has been modified since + the last sweep, but Sweep.build_sweep has not been run + """ + daq = self._instrument.daq + signals = self._instrument._sweeper_signals + sweeper = self._instrument.sweeper + + if signals == []: + raise ValueError('No signals selected! Can not perform sweep.') + + if self._instrument.sweep_correctly_built is False: + raise ValueError('The sweep has not been correctly built.' + + ' Please run Sweep.build_sweep.') + + # We must enable the demodulators we use. + # After the sweep, they should be returned to their original state + streamsettings = [] # This list keeps track of the pre-sweep settings + for sigstr in signals: + path = '/'.join(sigstr.split('/')[:-1]) + (_, dev, _, dmnum, _) = path.split('/') + + # If the setting has never changed, get returns an empty dict. + # In that case, we assume that it's zero (factory default) + try: + toget = path.replace('sample', 'enable') + # ZI like nesting inside dicts... + setting = daq.get(toget)[dev]['demods'][dmnum]['enable']['value'][0] + except KeyError: + setting = 0 + streamsettings.append(setting) + daq.setInt(path.replace('sample', 'enable'), 1) + + # We potentially subscribe several times to the same demodulator, + # but that should not be a problem + sweeper.subscribe(path) + + sweeper.execute() + timeout = self._instrument.sweeper_timeout.get() + start = time.time() + while not sweeper.finished(): # Wait until the sweep is done/timeout + time.sleep(0.2) # Check every 200 ms whether the sweep is done + # Here we could read intermediate data via: + # data = sweeper.read(True)... + # and process it while the sweep is completing. + if (time.time() - start) > timeout: + # If for some reason the sweep is blocking, force the end of the + # measurement. + log.error("Sweep still not finished, forcing finish...") + # should exit function with error message instead of returning + sweeper.finish() + + return_flat_dict = True + data = sweeper.read(return_flat_dict) + + sweeper.unsubscribe('*') + for (state, sigstr) in zip(streamsettings, signals): + path = '/'.join(sigstr.split('/')[:-1]) + daq.setInt(path.replace('sample', 'enable'), int(state)) + + return self._parsesweepdata(data) + + def _parsesweepdata(self, sweepresult): + """ + Parse the raw result of a sweep into just the data asked for by the + added sweeper signals. Used by Sweep.get. + + Args: + sweepresult (dict): The dict returned by sweeper.read + + Returns: + tuple: The requested signals in a tuple + """ + trans = {'X': 'x', 'Y': 'y', 'Aux Input 1': 'auxin0', + 'Aux Input 2': 'auxin1', 'R': 'r', 'phase': 'phase', + 'Xrms': 'xpwr', 'Yrms': 'ypwr', 'Rrms': 'rpwr'} + returndata = [] + + for signal in self._instrument._sweeper_signals: + path = '/'.join(signal.split('/')[:-1]) + attr = signal.split('/')[-1] + data = sweepresult[path][0][0][trans[attr]] + returndata.append(data) + + return tuple(returndata) + + +class Scope(MultiParameter): + """ + Parameter class for the ZI UHF-LI Scope Channel 1 + + The .get method launches an acquisition and returns a tuple of two + np.arrays + FFT mode is NOT supported. + + Attributes: + names (tuple): Tuple of strings containing the names of the sweep + signals (to be measured) + units (tuple): Tuple of strings containg the units of the signals + shapes (tuple): Tuple of tuples each containing the Length of a + signal. + setpoints (tuple): Tuple of N copies of the sweep x-axis points, + where N is he number of measured signals + setpoint_names (tuple): Tuple of N identical strings with the name + of the sweep x-axis. + """ + def __init__(self, name, instrument, **kwargs): + # The __init__ requires that we supply names and shapes, + # but there is no way to know what they could be known at this time. + # They are updated via build_scope. + super().__init__(name, names=('',), shapes=((1,),), **kwargs) + self._instrument = instrument + + def prepare_scope(self): + """ + Prepare the scope for a measurement. Must immediately preceed a + measurement. + """ + + log.info('Preparing the scope') + + # A convenient reference + params = self._instrument.parameters + + # First figure out what the user has asked for + chans = {1: (True, False), 2: (False, True), 3: (True, True)} + channels = chans[params['scope_channels'].get()] + + npts = params['scope_length'].get() + # Find out whether segments are enabled + if params['scope_segments'].get() == 'ON': + segs = params['scope_segments_count'].get() + else: + segs = 1 + + inputunits = {'Signal Input 1': 'V', + 'Signal Input 2': 'V', + 'Trig Input 1': 'V', + 'Trig Input 2': 'V', + 'Aux Output 1': 'V', + 'Aux Output 2': 'V', + 'Aux Output 3': 'V', + 'Aux Output 4': 'V', + 'Aux In 1 Ch 1': 'V', + 'Aux In 1 Ch 2': 'V', + 'Osc phi Demod 4': '°', + 'osc phi Demod 8': '°', + 'AU Cartesian 1': 'arb. un.', + 'AU Cartesian 2': 'arb. un', + 'AU Polar 1': 'arb. un.', + 'AU Polar 2': 'arb. un.', + 'Demod 1 X': 'V', + 'Demod 1 Y': 'V', + 'Demod 1 R': 'V', + 'Demod 1 Phase': '°', + 'Demod 2 X': 'V', + 'Demod 2 Y': 'V', + 'Demod 2 R': 'V', + 'Demod 2 Phase': '°', + 'Demod 3 X': 'V', + 'Demod 3 Y': 'V', + 'Demod 3 R': 'V', + 'Demod 3 Phase': '°', + 'Demod 4 X': 'V', + 'Demod 4 Y': 'V', + 'Demod 4 R': 'V', + 'Demod 4 Phase': '°', + 'Demod 5 X': 'V', + 'Demod 5 Y': 'V', + 'Demod 5 R': 'V', + 'Demod 5 Phase': '°', + 'Demod 6 X': 'V', + 'Demod 6 Y': 'V', + 'Demod 6 R': 'V', + 'Demod 6 Phase': '°', + 'Demod 7 X': 'V', + 'Demod 7 Y': 'V', + 'Demod 7 R': 'V', + 'Demod 7 Phase': '°', + 'Demod 8 X': 'V', + 'Demod 8 Y': 'V', + 'Demod 8 R': 'V', + 'Demod 8 Phase': '°', + } + + #TODO: what are good names? + inputnames = {'Signal Input 1': 'Sig. In 1', + 'Signal Input 2': 'Sig. In 2', + 'Trig Input 1': 'Trig. In 1', + 'Trig Input 2': 'Trig. In 2', + 'Aux Output 1': 'Aux. Out 1', + 'Aux Output 2': 'Aux. Out 2', + 'Aux Output 3': 'Aux. Out 3', + 'Aux Output 4': 'Aux. Out 4', + 'Aux In 1 Ch 1': 'Aux. In 1 Ch 1', + 'Aux In 1 Ch 2': 'Aux. In 1 Ch 2', + 'Osc phi Demod 4': 'Demod. 4 Phase', + 'osc phi Demod 8': 'Demod. 8 Phase', + 'AU Cartesian 1': 'AU Cartesian 1', + 'AU Cartesian 2': 'AU Cartesian 2', + 'AU Polar 1': 'AU Polar 1', + 'AU Polar 2': 'AU Polar 2', + } + # Make the basic setpoints (the x-axis) + duration = params['scope_duration'].get() + delay = params['scope_trig_delay'].get() + starttime = params['scope_trig_reference'].get()*0.01*duration + delay + stoptime = starttime + duration + + setpointlist = tuple(np.linspace(starttime, stoptime, npts)) # x-axis + spname = 'Time' + namestr = "scope_channel{}_input".format(1) + name1 = inputnames[params[namestr].get()] + unit1 = inputunits[params[namestr].get()] + namestr = "scope_channel{}_input".format(2) + name2 = inputnames[params[namestr].get()] + unit2 = inputunits[params[namestr].get()] + + self.setpoints = ((tuple(range(segs)), (setpointlist,)*segs),)*2 + #self.setpoints = ((setpointlist,)*segs,)*2 + self.setpoint_names = (('Segments', 'Time'), ('Segments', 'Time')) + self.names = (name1, name2) + self.units = (unit1, unit2) + self.labels = ('Scope channel 1', 'Scope channel 2') + self.shapes = ((segs, npts), (segs, npts)) + + self._instrument.daq.sync() + self._instrument.scope_correctly_built = True + + def get(self): + """ + Acquire data from the scope. + + Returns: + tuple: Tuple of two n X m arrays where n is the number of segments + and m is the number of points in the scope trace. + + Raises: + ValueError: If the scope has not been prepared by running the + prepare_scope function. + """ + if not self._instrument.scope_correctly_built: + raise ValueError('Scope not properly prepared. Please run ' + 'prepare_scope before measuring.') + + # A convenient reference + params = self._instrument.parameters + # + chans = {1: (True, False), 2: (False, True), 3: (True, True)} + channels = chans[params['scope_channels'].get()] + + if params['scope_trig_holdoffmode'].get_latest() == 'events': + raise NotImplementedError('Scope trigger holdoff in number of ' + 'events not supported. Please specify ' + 'holdoff in seconds.') + + ####################################################### + # The following steps SEEM to give the correct result + + # Make sure all settings have taken effect + self._instrument.daq.sync() + + # Calculate the time needed for the measurement. We often have failed + # measurements, so a timeout is needed. + if params['scope_segments'].get() == 'ON': + segs = params['scope_segments_count'].get() + else: + segs = 1 + deadtime = params['scope_trig_holdoffseconds'].get_latest() + # We add one second to account for latencies and random delays + meas_time = segs*(params['scope_duration'].get()+deadtime)+1 + npts = params['scope_length'].get() + + zi_error = True + error_counter = 0 + num_retries = 10 + timedout = False + while (zi_error or timedout) and error_counter < num_retries: + # one shot per trigger. This needs to be set every time + # a the scope is enabled as below using scope_runstop + self._instrument.daq.setInt('/{}/scopes/0/single'.format(self._instrument.device), 1) + self._instrument.daq.sync() + + scope = self._instrument.scope # There are issues reusing the scope. + scope.set('scopeModule/clearhistory', 1) + # Subscribe to the relevant... publisher? + scope.subscribe('/{}/scopes/0/wave'.format(self._instrument.device)) + + # Start the scope triggering/acquiring + params['scope_runstop'].set('run') # set /dev/scopes/0/enable to 1 + + log.info('[*] Starting ZI scope acquisition.') + # Start something... hauling data from the scopeModule? + scope.execute() + starttime = time.time() + timedout = False + + while scope.progress() < 1: + time.sleep(0.1) # This while+sleep is how ZI engineers do it + if (time.time()-starttime) > 2 * meas_time: + timedout = True + break + metadata = scope.get("scopeModule/*") + zi_error = bool(metadata['error'][0]) + + # Stop the scope from running + params['scope_runstop'].set('stop') + + if not (timedout or zi_error): + log.info('[+] ZI scope acquisition completed OK') + rawdata = scope.read() + data = self._scopedataparser(rawdata, self._instrument.device, + npts, segs, channels) + else: + log.warning('[-] ZI scope acquisition attempt {} ' + 'failed, Timeout: {}, Error: {}, ' + 'retrying'.format(error_counter, timedout, zi_error)) + rawdata = None + data = (None, None) + error_counter += 1 + + # cleanup and make ready for next scope acquisition + scope.finish() + scope.unsubscribe('/{}/scopes/0/wave'.format(self._instrument.device)) + if error_counter >= num_retries: + log.warning('[+] ZI scope acquisition failed, maximum number' + 'of retries performed. No data returned') + return data + + @staticmethod + def _scopedataparser(rawdata, deviceID, scopelength, segments, channels): + """ + Cast the scope return value dict into a tuple. + + Args: + rawdata (dict): The return of scopeModule.read() + deviceID (str): The device ID string of the instrument. + scopelength (int): The length of each segment + segments (int): The number of segments + channels (tuple): Tuple of two bools controlling what data to return + (True, False) will return data for channel 1 etc. + + Returns: + tuple: A 2-tuple of either None or np.array with dimensions + segments x scopelength. + """ + + data = rawdata['{}'.format(deviceID)]['scopes']['0']['wave'][0][0] + if channels[0]: + ch1data = data['wave'][0].reshape(segments, scopelength) + else: + ch1data = None + if channels[1]: + ch2data = data['wave'][1].reshape(segments, scopelength) + else: + ch2data = None + + return (ch1data, ch2data) + +class ZIUHFLI(Instrument): + """ + QCoDeS driver for ZI UHF-LI. + + Currently implementing demodulator settings and the sweeper functionality. + + Requires ZI Lab One software to be installed on the computer running QCoDeS. + Furthermore, the Data Server and Web Server must be running and a connection + between the two must be made. + + TODOs: + * Add zoom-FFT + """ + + def __init__(self, name, device_ID, **kwargs): + """ + Create an instance of the instrument. + + Args: + name (str): The internal QCoDeS name of the instrument + device_ID (str): The device name as listed in the web server. + api_level (int): Compatibility mode of the API interface. Must be 5 + for the UHF. + """ + + super().__init__(name, **kwargs) + self.api_level = 5 + zisession = zhinst.utils.create_api_session(device_ID, self.api_level) + (self.daq, self.device, self.props) = zisession + + self.daq.setDebugLevel(3) + # create (instantiate) an instance of each module we will use + self.sweeper = self.daq.sweep() + self.sweeper.set('sweep/device', self.device) + self.scope = self.daq.scopeModule() + + ######################################## + # INSTRUMENT PARAMETERS + + ######################################## + # Oscillators + for oscs in range(1,3): + self.add_parameter('oscillator{}_freq'.format(oscs), + label='Frequency of oscillator {}'.format(oscs), + unit='Hz', + set_cmd=partial(self._setter, 'oscs', + oscs-1, 1, 'freq'), + get_cmd=partial(self._getter, 'oscs', + oscs-1, 1, 'freq'), + vals=vals.Numbers(0, 600e6)) + + ######################################## + # DEMODULATOR PARAMETERS + + for demod in range(1, 9): + self.add_parameter('demod{}_order'.format(demod), + label='Filter order', + get_cmd=partial(self._getter, 'demods', + demod-1, 0, 'order'), + set_cmd=partial(self._setter, 'demods', + demod-1, 0, 'order'), + vals=vals.Ints(1, 8) + ) + + self.add_parameter('demod{}_harmonic'.format(demod), + label=('Reference frequency multiplication' + + ' factor'), + get_cmd=partial(self._getter, 'demods', + demod-1, 1, 'harmonic'), + set_cmd=partial(self._setter, 'demods', + demod-1, 1, 'harmonic'), + vals=vals.Ints(1, 999) + ) + + self.add_parameter('demod{}_timeconstant'.format(demod), + label='Filter time constant', + get_cmd=partial(self._getter, 'demods', + demod-1, 1, 'timeconstant'), + set_cmd=partial(self._setter, 'demods', + demod-1, 1, 'timeconstant'), + unit='s' + ) + + self.add_parameter('demod{}_samplerate'.format(demod), + label='Sample rate', + get_cmd=partial(self._getter, 'demods', + demod-1, 1, 'rate'), + set_cmd=partial(self._setter, 'demods', + demod-1, 1, 'rate'), + unit='Sa/s', + docstring=""" + Note: the value inserted by the user + may be approximated to the + nearest value supported by the + instrument. + """) + + self.add_parameter('demod{}_phaseshift'.format(demod), + label='Phase shift', + unit='degrees', + get_cmd=partial(self._getter, 'demods', + demod-1, 1, 'phaseshift'), + set_cmd=partial(self._setter, 'demods', + demod-1, 1, 'phaseshift') + ) + + # val_mapping for the demodX_signalin parameter + dmsigins = {'Sig In 1': 0, + 'Sig In 2': 1, + 'Trigger 1': 2, + 'Trigger 2': 3, + 'Aux Out 1': 4, + 'Aux Out 2': 5, + 'Aux Out 3': 6, + 'Aux Out 4': 7, + 'Aux In 1': 8, + 'Aux In 2': 9, + 'Phi Demod 4': 10, + 'Phi Demod 8': 11} + + self.add_parameter('demod{}_signalin'.format(demod), + label='Signal input', + get_cmd=partial(self._getter, 'demods', + demod-1, 0,'adcselect'), + set_cmd=partial(self._setter, 'demods', + demod-1, 0, 'adcselect'), + val_mapping=dmsigins, + vals=vals.Enum(*list(dmsigins.keys())) + ) + + self.add_parameter('demod{}_sinc'.format(demod), + label='Sinc filter', + get_cmd=partial(self._getter, 'demods', + demod-1, 0, 'sinc'), + set_cmd=partial(self._setter, 'demods', + demod-1, 0, 'sinc'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') + ) + + self.add_parameter('demod{}_streaming'.format(demod), + label='Data streaming', + get_cmd=partial(self._getter, 'demods', + demod-1, 0, 'enable'), + set_cmd=partial(self._setter, 'demods', + demod-1, 0, 'enable'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') + ) + + dmtrigs = {'Continuous': 0, + 'Trigger in 3 Rise': 1, + 'Trigger in 3 Fall': 2, + 'Trigger in 3 Both': 3, + 'Trigger in 3 High': 32, + 'Trigger in 3 Low': 16, + 'Trigger in 4 Rise': 4, + 'Trigger in 4 Fall': 8, + 'Trigger in 4 Both': 12, + 'Trigger in 4 High': 128, + 'Trigger in 4 Low': 64, + 'Trigger in 3|4 Rise': 5, + 'Trigger in 3|4 Fall': 10, + 'Trigger in 3|4 Both': 15, + 'Trigger in 3|4 High': 160, + 'Trigger in 3|4 Low': 80} + + self.add_parameter('demod{}_trigger'.format(demod), + label='Trigger', + get_cmd=partial(self._getter, 'demods', + demod-1, 0, 'trigger'), + set_cmd=partial(self._setter, 'demods', + demod-1, 0, 'trigger'), + val_mapping=dmtrigs, + vals=vals.Enum(*list(dmtrigs.keys())) + ) + + ######################################## + # SIGNAL INPUTS + + for sigin in range(1, 3): + + self.add_parameter('signal_input{}_range'.format(sigin), + label='Input range', + set_cmd=partial(self._setter, 'sigins', + sigin-1, 1, 'range'), + get_cmd=partial(self._getter, 'sigins', + sigin-1, 1, 'range'), + unit='V') + + self.add_parameter('signal_input{}_scaling'.format(sigin), + label='Input scaling', + set_cmd=partial(self._setter, 'sigins', + sigin-1, 1, 'scaling'), + get_cmd=partial(self._getter, 'sigins', + sigin-1, 1, 'scaling'), + ) + + self.add_parameter('signal_input{}_AC'.format(sigin), + label='AC coupling', + set_cmd=partial(self._setter,'sigins', + sigin-1, 0, 'ac'), + get_cmd=partial(self._getter, 'sigins', + sigin-1, 0, 'ac'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') + ) + + self.add_parameter('signal_input{}_impedance'.format(sigin), + label='Input impedance', + set_cmd=partial(self._setter, 'sigins', + sigin-1, 0, 'imp50'), + get_cmd=partial(self._getter, 'sigins', + sigin-1, 0, 'imp50'), + val_mapping={50: 1, 1000: 0}, + vals=vals.Enum(50, 1000) + ) + + sigindiffs = {'Off': 0, 'Inverted': 1, 'Input 1 - Input 2': 2, + 'Input 2 - Input 1': 3} + self.add_parameter('signal_input{}_diff'.format(sigin), + label='Input signal subtraction', + set_cmd=partial(self._setter, 'sigins', + sigin-1, 0, 'diff'), + get_cmd=partial(self._getter, 'sigins', + sigin-1, 0, 'diff'), + val_mapping=sigindiffs, + vals=vals.Enum(*list(sigindiffs.keys()))) + + ######################################## + # SIGNAL OUTPUTS + outputamps = {1: 'amplitudes/3', 2: 'amplitudes/7'} + outputampenable = {1: 'enables/3', 2: 'enables/7'} + + for sigout in range(1,3): + + self.add_parameter('signal_output{}_on'.format(sigout), + label='Turn signal output on and off.', + set_cmd=partial(self._sigout_setter, + sigout-1, 0, 'on'), + get_cmd=partial(self._sigout_getter, + sigout-1, 0, 'on'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') ) + + self.add_parameter('signal_output{}_imp50'.format(sigout), + label='Switch to turn on 50 Ohm impedance', + set_cmd=partial(self._sigout_setter, + sigout-1, 0, 'imp50'), + get_cmd=partial(self._sigout_getter, + sigout-1, 0, 'imp50'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') ) + + self.add_parameter('signal_output{}_amplitude'.format(sigout), + label='Signal output amplitude', + set_cmd=partial(self._sigout_setter, + sigout-1, 1, outputamps[sigout]), + get_cmd=partial(self._sigout_getter, + sigout-1, 1, outputamps[sigout]), + unit='V') + + self.add_parameter('signal_output{}_ampdef'.format(sigout), + parameter_class=ManualParameter, + initial_value='Vpk', + label="Signal output amplitude's definition", + unit='V', + vals=vals.Enum('Vpk','Vrms', 'dBm')) + + self.add_parameter('signal_output{}_range'.format(sigout), + label='Signal output range', + set_cmd=partial(self._sigout_setter, + sigout-1, 1, 'range'), + get_cmd=partial(self._sigout_getter, + sigout-1, 1, 'range'), + vals=vals.Enum(0.075, 0.15, 0.75, 1.5)) + + self.add_parameter('signal_output{}_offset'.format(sigout), + label='Signal output offset', + set_cmd=partial(self._sigout_setter, + sigout-1, 1, 'offset'), + get_cmd=partial(self._sigout_getter, + sigout-1, 1, 'offset'), + vals=vals.Numbers(-1.5, 1.5), + unit='V') + + self.add_parameter('signal_output{}_autorange'.format(sigout), + label='Enable signal output range.', + set_cmd=partial(self._sigout_setter, + sigout-1, 0, 'autorange'), + get_cmd=partial(self._sigout_getter, + sigout-1, 0, 'autorange'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') ) + + self.add_parameter('signal_output{}_enable'.format(sigout), + label="Enable signal output's amplitude.", + set_cmd=partial(self._sigout_setter, + sigout-1, 0, + outputampenable[sigout]), + get_cmd=partial(self._sigout_getter, + sigout-1, 0, + outputampenable[sigout]), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') ) + + + + ######################################## + # SWEEPER PARAMETERS + + self.add_parameter('sweeper_BWmode', + label='Sweeper bandwidth control mode', + set_cmd=partial(self._sweep_setter, + 'sweep/bandwidthcontrol'), + get_cmd=partial(self._sweep_getter, + 'sweep/bandwidthcontrol'), + val_mapping={'auto': 2, 'fixed': 1, 'current': 0}, + docstring=""" + For each sweep point, the demodulator + filter bandwidth (time constant) may + be either set automatically, be the + current demodulator bandwidth or be + a fixed number; the sweeper_BW + parameter. + """ + ) + + self.add_parameter('sweeper_BW', + label='Fixed bandwidth sweeper bandwidth (NEP)', + set_cmd=partial(self._sweep_setter, + 'sweep/bandwidth'), + get_cmd=partial(self._sweep_getter, + 'sweep/bandwidth'), + docstring=""" + This is the NEP bandwidth used by the + sweeper if sweeper_BWmode is set to + 'fixed'. If sweeper_BWmode is either + 'auto' or 'current', this value is + ignored. + """ + ) + + self.add_parameter('sweeper_start', + label='Start value of the sweep', + set_cmd=partial(self._sweep_setter, + 'sweep/start'), + get_cmd=partial(self._sweep_getter, + 'sweep/start'), + vals=vals.Numbers(0, 600e6)) + + self.add_parameter('sweeper_stop', + label='Stop value of the sweep', + set_cmd=partial(self._sweep_setter, + 'sweep/stop'), + get_cmd=partial(self._sweep_getter, + 'sweep/stop'), + vals=vals.Numbers(0, 600e6)) + + self.add_parameter('sweeper_samplecount', + label='Length of the sweep (pts)', + set_cmd=partial(self._sweep_setter, + 'sweep/samplecount'), + get_cmd=partial(self._sweep_getter, + 'sweep/samplecount'), + vals=vals.Ints(0, 100000)) + + # val_mapping for sweeper_param parameter + sweepparams = {'Aux Out 1 Offset': 'auxouts/0/offset', + 'Aux Out 2 Offset': 'auxouts/1/offset', + 'Aux Out 3 Offset': 'auxouts/2/offset', + 'Aux Out 4 Offset': 'auxouts/3/offset', + 'Demod 1 Phase Shift': 'demods/0/phaseshift', + 'Demod 2 Phase Shift': 'demods/1/phaseshift', + 'Demod 3 Phase Shift': 'demods/2/phaseshift', + 'Demod 4 Phase Shift': 'demods/3/phaseshift', + 'Demod 5 Phase Shift': 'demods/4/phaseshift', + 'Demod 6 Phase Shift': 'demods/5/phaseshift', + 'Demod 7 Phase Shift': 'demods/6/phaseshift', + 'Demod 8 Phase Shift': 'demods/7/phaseshift', + 'Osc 1 Frequency': 'oscs/0/freq', + 'Osc 2 Frequency': 'oscs/1/freq', + 'Output 1 Amplitude 4': 'sigouts/0/amplitudes/3', + 'Output 1 Offset': 'sigouts/0/offset', + 'Output 2 Amplitude 8': 'sigouts/1/amplitudes/7', + 'Output 2 Offset': 'sigouts/1/offset' + } + + self.add_parameter('sweeper_param', + label='Parameter to sweep (sweep x-axis)', + set_cmd=partial(self._sweep_setter, + 'sweep/gridnode'), + val_mapping=sweepparams, + get_cmd=partial(self._sweep_getter, + 'sweep/gridnode'), + vals=vals.Enum(*list(sweepparams.keys())) + ) + + # val_mapping for sweeper_units parameter + sweepunits = {'Aux Out 1 Offset': 'V', + 'Aux Out 2 Offset': 'V', + 'Aux Out 3 Offset': 'V', + 'Aux Out 4 Offset': 'V', + 'Demod 1 Phase Shift': 'degrees', + 'Demod 2 Phase Shift': 'degrees', + 'Demod 3 Phase Shift': 'degrees', + 'Demod 4 Phase Shift': 'degrees', + 'Demod 5 Phase Shift': 'degrees', + 'Demod 6 Phase Shift': 'degrees', + 'Demod 7 Phase Shift': 'degrees', + 'Demod 8 Phase Shift': 'degrees', + 'Osc 1 Frequency': 'Hz', + 'Osc 2 Frequency': 'Hz', + 'Output 1 Amplitude 4': 'V', + 'Output 1 Offset': 'V', + 'Output 2 Amplitude 8': 'V', + 'Output 2 Offset': 'V' + } + + self.add_parameter('sweeper_units', + label='Units of sweep x-axis', + get_cmd=self.sweeper_param.get, + get_parser=lambda x:sweepunits[x]) + + # val_mapping for sweeper_mode parameter + sweepmodes = {'Sequential': 0, + 'Binary': 1, + 'Biderectional': 2, + 'Reverse': 3} + + self.add_parameter('sweeper_mode', + label='Sweep mode', + set_cmd=partial(self._sweep_setter, + 'sweep/scan'), + get_cmd=partial(self._sweep_getter, 'sweep/scan'), + val_mapping=sweepmodes, + vals=vals.Enum(*list(sweepmodes)) + ) + + self.add_parameter('sweeper_order', + label='Sweeper filter order', + set_cmd=partial(self._sweep_setter, + 'sweep/order'), + get_cmd=partial(self._sweep_getter, + 'sweep/order'), + vals=vals.Ints(1, 8), + docstring=""" + This value is invoked only when the + sweeper_BWmode is set to 'fixed'. + """) + + self.add_parameter('sweeper_settlingtime', + label=('Minimal settling time for the ' + + 'sweeper'), + set_cmd=partial(self._sweep_setter, + 'sweep/settling/time'), + get_cmd=partial(self._sweep_getter, + 'sweep/settling/time'), + vals=vals.Numbers(0), + unit='s', + docstring=""" + This is the minimal waiting time + at each point during a sweep before the + data acquisition starts. Note that the + filter settings may result in a longer + actual waiting/settling time. + """ + ) + + self.add_parameter('sweeper_inaccuracy', + label='Demodulator filter settling inaccuracy', + set_cmd=partial(self._sweep_setter, + 'sweep/settling/inaccuracy'), + docstring=""" + Demodulator filter settling inaccuracy + defining the wait time between a sweep + parameter change and recording of the + next sweep point. The settling time is + calculated as the time required to attain + the specified remaining proportion [1e-13, + 0.1] of an incoming step function. Typical + inaccuracy values: 10m for highest sweep + speed for large signals, 100u for precise + amplitude measurements, 100n for precise + noise measurements. Depending on the + order of the demodulator filter the settling + inaccuracy will define the number of filter + time constants the sweeper has to wait. The + maximum between this value and the settling + time is taken as wait time until the next + sweep point is recorded. + """ + ) + + self.add_parameter('sweeper_settlingtc', + label='Sweep filter settling time', + get_cmd=partial(self._sweep_getter, + 'sweep/settling/tc'), + unit='', + docstring="""This settling time is in units of + the filter time constant.""" + ) + + self.add_parameter('sweeper_averaging_samples', + label=('Minimal no. of samples to average at ' + + 'each sweep point'), + set_cmd=partial(self._sweep_setter, + 'sweep/averaging/sample'), + get_cmd=partial(self._sweep_getter, + 'sweep/averaging/sample'), + vals=vals.Ints(1), + docstring=""" + The actual number of samples is the + maximum of this value and the + sweeper_averaging_time times the + relevant sample rate. + """ + ) + + self.add_parameter('sweeper_averaging_time', + label=('Minimal averaging time'), + set_cmd=partial(self._sweep_setter, + 'sweep/averaging/tc'), + get_cmd=partial(self._sweep_getter, + 'sweep/averaging/tc'), + unit='s', + docstring=""" + The actual number of samples is the + maximum of this value times the + relevant sample rate and the + sweeper_averaging_samples.""" + ) + + self.add_parameter('sweeper_xmapping', + label='Sweeper x mapping', + set_cmd=partial(self._sweep_setter, + 'sweep/xmapping'), + get_cmd=partial(self._sweep_getter, + 'sweep/xmapping'), + val_mapping={'lin': 0, 'log': 1} + ) + + self.add_parameter('sweeper_sweeptime', + label='Expected sweep time', + unit='s', + get_cmd=self._get_sweep_time) + + self.add_parameter('sweeper_timeout', + label='Sweep timeout', + unit='s', + initial_value=600, + parameter_class=ManualParameter) + + ######################################## + # THE SWEEP ITSELF + self.add_parameter('Sweep', + parameter_class=Sweep, + ) + + # A "manual" parameter: a list of the signals for the sweeper + # to subscribe to + self._sweeper_signals = [] + + # This is the dictionary keeping track of the sweeper settings + # These are the default settings + self._sweepdict = {'start': 1e6, + 'stop': 10e6, + 'samplecount': 25, + 'bandwidthcontrol': 1, # fixed mode + 'bandwidth': 50, + 'gridnode': 'oscs/0/freq', + 'scan': 0, # sequential scan + 'order': 1, + 'settling/time': 1e-6, + 'settling/inaccuracy': 10e-3, + 'averaging/sample': 25, + 'averaging/tc': 100e-3, + 'xmapping': 0, # linear + } + # Set up the sweeper with the above settings + self.Sweep.build_sweep() + + ######################################## + # SCOPE PARAMETERS + # default parameters: + + # This parameter corresponds to the Run/Stop button in the GUI + self.add_parameter('scope_runstop', + label='Scope run state', + set_cmd=partial(self._setter, 'scopes', 0, 0, + 'enable'), + get_cmd=partial(self._getter, 'scopes', 0, 0, + 'enable'), + val_mapping={'run': 1, 'stop': 0}, + vals=vals.Enum('run', 'stop'), + docstring=('This parameter corresponds to the ' + 'run/stop button in the GUI.')) + + self.add_parameter('scope_mode', + label="Scope's mode: time or frequency domain.", + set_cmd=partial(self._scope_setter, 1, 0, + 'mode'), + get_cmd=partial(self._scope_getter, 'mode'), + val_mapping={'Time Domain': 1, + 'Freq Domain FFT': 3}, + vals=vals.Enum('Time Domain', 'Freq Domain FFT') + ) + + # 1: Channel 1 on, Channel 2 off. + # 2: Channel 1 off, Channel 2 on, + # 3: Channel 1 on, Channel 2 on. + self.add_parameter('scope_channels', + label='Recorded scope channels', + set_cmd=partial(self._scope_setter, 0, 0, + 'channel'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'channel'), + vals=vals.Enum(1, 2, 3) + ) + + self._samplingrate_codes = {'1.80 GHz': 0, + '900 MHz': 1, + '450 MHz': 2, + '225 MHz': 3, + '113 MHz': 4, + '56.2 MHz': 5, + '28.1 MHz': 6, + '14.0 MHz': 7, + '7.03 MHz': 8, + '3.50 MHz': 9, + '1.75 MHz': 10, + '880 kHz': 11, + '440 kHz': 12, + '220 kHz': 13, + '110 kHz': 14, + '54.9 kHz': 15, + '27.5 kHz': 16} + + self.add_parameter('scope_samplingrate', + label="Scope's sampling rate", + set_cmd=partial(self._scope_setter, 0, 0, + 'time'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'time'), + val_mapping=self._samplingrate_codes, + vals=vals.Enum(*list(self._samplingrate_codes.keys())) + ) + + self.add_parameter('scope_length', + label="Length of scope trace (pts)", + set_cmd=partial(self._scope_setter, 0, 1, + 'length'), + get_cmd=partial(self._getter, 'scopes', 0, + 1, 'length'), + vals=vals.Numbers(4096, 128000000), + get_parser=int + ) + + self.add_parameter('scope_duration', + label="Scope trace duration", + set_cmd=partial(self._scope_setter, 0, 0, + 'duration'), + get_cmd=partial(self._scope_getter, + 'duration'), + vals=vals.Numbers(2.27e-6,4.660e3), + unit='s' + ) + + # Map the possible input sources to LabOne's IDs. + # The IDs can be seen in log file of LabOne UI + inputselect = {'Signal Input 1': 0, + 'Signal Input 2': 1, + 'Trig Input 1': 2, + 'Trig Input 2': 3, + 'Aux Output 1': 4, + 'Aux Output 2': 5, + 'Aux Output 3': 6, + 'Aux Output 4': 7, + 'Aux In 1 Ch 1': 8, + 'Aux In 1 Ch 2': 9, + 'Osc phi Demod 4': 10, + 'Osc phi Demod 8': 11, + 'AU Cartesian 1': 112, + 'AU Cartesian 2': 113, + 'AU Polar 1': 128, + 'AU Polar 2': 129, + } + # Add all 8 demodulators and their respective parameters + # to inputselect as well. + # Numbers correspond to LabOne IDs, taken from UI log. + for demod in range(1,9): + inputselect['Demod {} X'.format(demod)] = 15+demod + inputselect['Demod {} Y'.format(demod)] = 31+demod + inputselect['Demod {} R'.format(demod)] = 47+demod + inputselect['Demod {} Phase'.format(demod)] = 63+demod + + for channel in range(1,3): + self.add_parameter('scope_channel{}_input'.format(channel), + label=("Scope's channel {}".format(channel) + + " input source"), + set_cmd=partial(self._scope_setter, 0, 0, + ('channels/{}/'.format(channel-1) + + 'inputselect')), + get_cmd=partial(self._getter, 'scopes', 0, 0, + ('channels/{}/'.format(channel-1) + + 'inputselect')), + val_mapping=inputselect, + vals=vals.Enum(*list(inputselect.keys())) + ) + + self.add_parameter('scope_average_weight', + label="Scope Averages", + set_cmd=partial(self._scope_setter, 1, 0, + 'averager/weight'), + get_cmd=partial(self._scope_getter, + 'averager/weight'), + vals=vals.Numbers(min_value=1) + ) + + self.add_parameter('scope_trig_enable', + label="Enable triggering for scope readout", + set_cmd=partial(self._setter, 'scopes', 0, + 0, 'trigenable'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'trigenable'), + val_mapping={'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF') + ) + + self.add_parameter('scope_trig_signal', + label="Trigger signal source", + set_cmd=partial(self._setter, 'scopes', 0, + 0, 'trigchannel'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'trigchannel'), + val_mapping=inputselect, + vals=vals.Enum(*list(inputselect.keys())) + ) + + slopes = {'None': 0, 'Rise': 1, 'Fall': 2, 'Both': 3} + + self.add_parameter('scope_trig_slope', + label="Scope's triggering slope", + set_cmd=partial(self._setter, 'scopes', 0, + 0, 'trigslope'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'trigslope'), + val_mapping=slopes, + vals=vals.Enum(*list(slopes.keys())) + ) + + # TODO: figure out how value/percent works for the trigger level + self.add_parameter('scope_trig_level', + label="Scope trigger level", + set_cmd=partial(self._setter, 'scopes', 0, + 1, 'triglevel'), + get_cmd=partial(self._getter, 'scopes', 0, + 1, 'triglevel'), + vals=vals.Numbers() + ) + + self.add_parameter('scope_trig_hystmode', + label="Enable triggering for scope readout.", + set_cmd=partial(self._setter, 'scopes', 0, + 0, 'trighysteresis/mode'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'trighysteresis/mode'), + val_mapping={'absolute': 0, 'relative': 1}, + vals=vals.Enum('absolute', 'relative') + ) + + self.add_parameter('scope_trig_hystrelative', + label="Trigger hysteresis, relative value in %", + set_cmd=partial(self._setter, 'scopes', 0, + 1, 'trighysteresis/relative'), + get_cmd=partial(self._getter, 'scopes', 0, + 1, 'trighysteresis/relative'), + # val_mapping= lambda x: 0.01*x, + vals=vals.Numbers(0) + ) + + self.add_parameter('scope_trig_hystabsolute', + label="Trigger hysteresis, absolute value", + set_cmd=partial(self._setter, 'scopes', 0, + 1, 'trighysteresis/absolute'), + get_cmd=partial(self._getter, 'scopes', 0, + 1, 'trighysteresis/absolute'), + vals=vals.Numbers(0, 20) + ) + + triggates = {'Trigger In 3 High': 0, 'Trigger In 3 Low': 1, + 'Trigger In 4 High': 2, 'Trigger In 4 Low': 3} + self.add_parameter('scope_trig_gating_source', + label='Scope trigger gating source', + set_cmd=partial(self._setter, 'scopes', 0, 0, + 'triggate/inputselect'), + get_cmd=partial(self._getter, 'scopes', 0, 0, + 'triggate/inputselect'), + val_mapping=triggates, + vals=vals.Enum(*list(triggates.keys())) + ) + + self.add_parameter('scope_trig_gating_enable', + label='Scope trigger gating ON/OFF', + set_cmd=partial(self._setter, 'scopes', 0, 0, + 'triggate/enable'), + get_cmd=partial(self._getter, 'scopes', 0, 0, + 'triggate/enable'), + val_mapping = {'ON': 1, 'OFF': 0}, + vals=vals.Enum('ON', 'OFF')) + + # make this a slave parameter off scope_holdoff_seconds + # and scope_holdoff_events + self.add_parameter('scope_trig_holdoffmode', + label="Scope trigger holdoff mode", + set_cmd=partial(self._setter, 'scopes', 0, + 0, 'trigholdoffmode'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'trigholdoffmode'), + val_mapping={'s': 0, 'events': 1}, + vals=vals.Enum('s', 'events') + ) + + self.add_parameter('scope_trig_holdoffseconds', + label='Scope trigger holdoff', + set_cmd=partial(self._scope_setter, 0, 1, + 'trigholdoff'), + get_cmd=partial(self._getter, 'scopes', 0, + 1, 'trigholdoff'), + unit='s', + vals=vals.Numbers(20e-6, 10) + ) + + self.add_parameter('scope_trig_reference', + label='Scope trigger reference', + set_cmd=partial(self._scope_setter, 0, 1, + 'trigreference'), + get_cmd=partial(self._getter, 'scopes', 0, + 1, 'trigreference'), + vals=vals.Numbers(0, 100) + ) + + # TODO: add validation. What's the minimal/maximal delay? + self.add_parameter('scope_trig_delay', + label='Scope trigger delay', + set_cmd=partial(self._scope_setter, 0, 1, + 'trigdelay'), + get_cmd=partial(self._getter, 'scopes', 0, 1, + 'trigdelay'), + unit='s') + + self.add_parameter('scope_segments', + label='Enable/disable segments', + set_cmd=partial(self._scope_setter, 0, 0, + 'segments/enable'), + get_cmd=partial(self._getter, 'scopes', 0, + 0, 'segments/enable'), + val_mapping={'OFF': 0, 'ON': 1}, + vals=vals.Enum('ON', 'OFF') + ) + + self.add_parameter('scope_segments_count', + label='No. of segments returned by scope', + set_cmd=partial(self._setter, 'scopes', 0, 1, + 'segments/count'), + get_cmd=partial(self._getter, 'scopes', 0, 1, + 'segments/count'), + vals=vals.Ints(1, 32768), + get_parser=int + ) + + self.add_function('scope_reset_avg', + call_cmd=partial(self.scope.set, + 'scopeModule/averager/restart', 1), + ) + + ######################################## + # THE SCOPE ITSELF + self.add_parameter('Scope', + parameter_class=Scope, + ) + + + def _setter(self, module, number, mode, setting, value): + """ + General function to set/send settings to the device. + + The module (e.g demodulator, input, output,..) number is counted in a + zero indexed fashion. + + Args: + module (str): The module (eg. demodulator, input, output, ..) + to set. + number (int): Module's index + mode (bool): Indicating whether we are setting an int or double + setting (str): The module's setting to set. + value (int/double): The value to set. + """ + + setstr = '/{}/{}/{}/{}'.format(self.device, module, number, setting) + + if mode == 0: + self.daq.setInt(setstr, value) + if mode == 1: + self.daq.setDouble(setstr, value) + + def _getter(self, module, number, mode, setting): + """ + General get function for generic parameters. Note that some parameters + use more specialised setter/getters. + + The module (e.g demodulator, input, output,..) number is counted in a + zero indexed fashion. + + Args: + module (str): The module (eg. demodulator, input, output, ..) + we want to know the value of. + number (int): Module's index + mode (int): Indicating whether we are asking for an int or double. + 0: Int, 1: double. + setting (str): The module's setting to set. + returns: + inquered value + + """ + + querystr = '/{}/{}/{}/{}'.format(self.device, module, number, setting) + if mode == 0: + value = self.daq.getInt(querystr) + if mode == 1: + value = self.daq.getDouble(querystr) + + # Weird exception, samplingrate returns a string + return value + + def _sigout_setter(self, number, mode, setting, value): + """ + Function to set signal output's settings. A specific setter function is + needed as parameters depend on each other and need to be checked and + updated accordingly. + + Args: + number (int): + mode (bool): Indicating whether we are asking for an int or double + setting (str): The module's setting to set. + value (Union[int, float]): The value to set the setting to. + """ + + # convenient reference + params = self.parameters + + def amp_valid(): + nonlocal value + toget = params['signal_output{}_ampdef'.format(number+1)] + ampdef_val = toget.get() + toget = params['signal_output{}_autorange'.format(number+1)] + autorange_val = toget.get() + + if autorange_val == 'ON': + toget = params['signal_output{}_imp50'.format(number+1)] + imp50_val = toget.get() + imp50_dic = {'OFF': 1.5, 'ON': 0.75} + range_val = imp50_dic[imp50_val] + + else: + so_range = params['signal_output{}_range'.format(number+1)].get() + range_val = round(so_range, 3) + + amp_val_dict={'Vpk': lambda value: value, + 'Vrms': lambda value: value*sqrt(2), + 'dBm': lambda value: 10**((value-10)/20) + } + + if -range_val < amp_val_dict[ampdef_val](value) > range_val: + raise ValueError('Signal Output:' + + ' Amplitude too high for chosen range.') + value = amp_val_dict[ampdef_val](value) + + def offset_valid(): + nonlocal value + nonlocal number + range_val = params['signal_output{}_range'.format(number+1)].get() + range_val = round(range_val, 3) + amp_val = params['signal_output{}_amplitude'.format(number+1)].get() + amp_val = round(amp_val, 3) + if -range_val< value+amp_val > range_val: + raise ValueError('Signal Output: Offset too high for ' + 'chosen range.') + + def range_valid(): + nonlocal value + nonlocal number + toget = params['signal_output{}_autorange'.format(number+1)] + autorange_val = toget.get() + imp50_val = params['signal_output{}_imp50'.format(number+1)].get() + imp50_dic = {'OFF': [1.5, 0.15], 'ON': [0.75, 0.075]} + + if autorange_val == "ON": + raise ValueError('Signal Output :' + ' Cannot set range as autorange is turned on.') + + if value not in imp50_dic[imp50_val]: + raise ValueError('Signal Output: Choose a valid range:' + '[0.75, 0.075] if imp50 is on, [1.5, 0.15]' + ' otherwise.') + + def ampdef_valid(): + # check which amplitude definition you can use. + # dBm is only possible with 50 Ohm imp ON + imp50_val = params['signal_output{}_imp50'.format(number+1)].get() + imp50_ampdef_dict = {'ON': ['Vpk','Vrms', 'dBm'], + 'OFF': ['Vpk','Vrms']} + if value not in imp50_ampdef_dict[imp50_val]: + raise ValueError("Signal Output: Choose a valid amplitude " + "definition; ['Vpk','Vrms', 'dBm'] if imp50 is" + " on, ['Vpk','Vrms'] otherwise.") + + dynamic_validation = {'range': range_valid, + 'ampdef': ampdef_valid, + 'amplitudes/3': amp_valid, + 'amplitudes/7': amp_valid, + 'offset': offset_valid} + + def update_range_offset_amp(): + range_val = params['signal_output{}_range'.format(number+1)].get() + offset_val = params['signal_output{}_offset'.format(number+1)].get() + amp_val = params['signal_output{}_amplitude'.format(number+1)].get() + if -range_val < offset_val + amp_val > range_val: + #The GUI would allow higher values but it would clip the signal. + raise ValueError('Signal Output: Amplitude and/or ' + 'offset out of range.') + + def update_offset(): + self.parameters['signal_output{}_offset'.format(number+1)].get() + + def update_amp(): + self.parameters['signal_output{}_amplitude'.format(number+1)].get() + + def update_range(): + self.parameters['signal_output{}_autorange'.format(number+1)].get() + + # parameters which will potentially change other parameters + changing_param = {'imp50': [update_range_offset_amp, update_range], + 'autorange': [update_range], + 'range': [update_offset, update_amp], + 'amplitudes/3': [update_range, update_amp], + 'amplitudes/7': [update_range, update_amp], + 'offset': [update_range] + } + + setstr = '/{}/sigouts/{}/{}'.format(self.device, number, setting) + + if setting in dynamic_validation: + dynamic_validation[setting]() + + if mode == 0: + self.daq.setInt(setstr, value) + if mode == 1: + self.daq.setDouble(setstr, value) + + if setting in changing_param: + [f() for f in changing_param[setting]] + + def _sigout_getter(self, number, mode, setting): + """ + Function to query the settings of signal outputs. Specific setter + function is needed as parameters depend on each other and need to be + checked and updated accordingly. + + Args: + number (int): + mode (bool): Indicating whether we are asking for an int or double + setting (str): The module's setting to set. + """ + + querystr = '/{}/sigouts/{}/{}'.format(self.device, number, setting) + if mode == 0: + value = self.daq.getInt(querystr) + if mode == 1: + value = self.daq.getDouble(querystr) + + return value + + def _list_nodes(self, node): + """ + Returns a list with all nodes in the sub-tree below the specified node. + + Args: + node (str): Module of which you want to know the parameters. + return: + list of sub-nodes + """ + node_list = self.daq.getList('/{}/{}/'.format(self.device, node)) + return node_list + + @staticmethod + def NEPBW_to_timeconstant(NEPBW, order): + """ + Helper function to translate a NEP BW and a filter order + to a filter time constant. Meant to be used when calculating + sweeper sweep times. + + Note: precise only to within a few percent. + + Args: + NEPBW (float): The NEP bandwidth in Hz + order (int): The filter order + + Returns: + float: The filter time constant in s. + """ + const = {1: 0.249, 2: 0.124, 3: 0.093, 4: 0.078, 5: 0.068, + 6: 0.061, 7: 0.056, 8: 0.052} + tau_c = const[order]/NEPBW + + return tau_c + + def _get_sweep_time(self): + """ + get_cmd for the sweeper_sweeptime parameter. + + Note: this calculation is only an estimate and not precise to more + than a few percent. + + Returns: + Union[float, None]: None if the bandwidthcontrol setting is + 'auto' (then all bets are off), otherwise a time in seconds. + + Raises: + ValueError: if no signals are added to the sweep + """ + + # Possible TODO: cut down on the number of instrument + # queries. + + if self._sweeper_signals == []: + raise ValueError('No signals selected! Can not find sweep time.') + + mode = self.sweeper_BWmode.get() + + # The effective time constant of the demodulator depends on the + # sweeper/bandwidthcontrol setting. + # + # If this setting is 'current', the largest current + # time constant of the involved demodulators is used + # + # If the setting is 'fixed', the NEP BW specified under + # sweep/bandwidth is used. The filter order is needed to convert + # the NEP BW to a time constant + + demods = set([sig.split('/')[3] for sig in self._sweeper_signals]) + rates = [] + for demod in demods: + rates.append(self._getter('demods', demod, 1, 'rate')) + rate = min(rates) + + if mode == 'current': + tcs = [] + for demod in demods: + tcs.append(self._getter('demods', demod, 1, 'timeconstant')) + + tau_c = max(tcs) + + elif mode == 'fixed': + order = self.sweeper_order() + BW = self.sweeper_BW() + + tau_c = self.NEPBW_to_timeconstant(BW, order) + + elif mode == 'auto': + return None + + settlingtime = max(self.sweeper_settlingtc.get()*tau_c, + self.sweeper_settlingtime.get()) + averagingtime = max(self.sweeper_averaging_time.get()*tau_c*rate, + self.sweeper_averaging_samples.get())/rate + + time_est = (settlingtime+averagingtime)*self.sweeper_samplecount.get() + return time_est + + def _sweep_setter(self, setting, value): + """ + set_cmd for all sweeper parameters. The value and setting are saved in + a dictionary which is read by the Sweep parameter's build_sweep method + and only then sent to the instrument. + """ + key = '/'.join(setting.split('/')[1:]) + self._sweepdict[key] = value + self.sweep_correctly_built = False + + def _sweep_getter(self, setting): + """ + General get_cmd for sweeper parameters + + The built-in sweeper.get command returns a dictionary, but we want + single values. + + Args: + setting (str): the path used by ZI to describe the setting, + e.g. 'sweep/settling/time' + """ + # TODO: Should this look up in _sweepdict rather than query the + # instrument? + returndict = self.sweeper.get(setting) # this is a dict + + # The dict may have different 'depths' depending on the parameter. + # The depth is encoded in the setting string (number of '/') + keys = setting.split('/')[1:] + + while keys != []: + key = keys.pop(0) + returndict = returndict[key] + rawvalue = returndict + + if isinstance(rawvalue, np.ndarray) and len(rawvalue) == 1: + value = rawvalue[0] + elif isinstance(rawvalue, list) and len(rawvalue) == 1: + value = rawvalue[0] + else: + value = rawvalue + + return value + + def add_signal_to_sweeper(self, demodulator, attribute): + """ + Add a signal to the output of the sweeper. When the sweeper sweeps, + the signals added to the sweeper are returned. + + Args: + demodulator (int): A number from 1-8 choosing the demodulator. + The same demodulator can be chosen several times for + different attributes, e.g. demod1 X, demod1 phase + attribute (str): The attribute to record, e.g. phase or Y + + Raises: + ValueError: if a demodulator outside the allowed range is + selected + ValueError: if an attribute not in the list of allowed attributes + is selected + """ + + # TODO: implement all possibly returned attributes + valid_attributes = ['X', 'Y', 'R', 'phase', 'Xrms', 'Yrms', 'Rrms'] + + # Validation + if demodulator not in range(1, 9): + raise ValueError('Can not select demodulator' + + ' {}. Only '.format(demodulator) + + 'demodulators 1-8 are available.') + if attribute not in valid_attributes: + raise ValueError('Can not select attribute:'+ + '{}. Only the following attributes are' + + ' available: ' + + ('{}, '*len(attributes)).format(*attributes)) + + # internally, we use strings very similar to the ones used by the + # instrument, but with the attribute added, e.g. + # '/dev2189/demods/0/sample/X' means X of demodulator 1. + signalstring = ('/' + self.device + + '/demods/{}/sample/{}'.format(demodulator-1, + attribute)) + if signalstring not in self._sweeper_signals: + self._sweeper_signals.append(signalstring) + + def remove_signal_from_sweeper(self, demodulator, attribute): + """ + Remove a signal from the output of the sweeper. If the signal + has not previously been added, a warning is logged. + + Args: + demodulator (int): A number from 1-8 choosing the demodulator. + The same demodulator can be chosen several times for + different attributes, e.g. demod1 X, demod1 phase + attribute (str): The attribute to record, e.g. phase or Y + """ + + signalstring = ('/' + self.device + + '/demods/{}/sample/{}'.format(demodulator-1, + attribute)) + if signalstring not in self._sweeper_signals: + log.warning('Can not remove signal with {} of'.format(attribute) + + ' demodulator {}, since it was'.format(demodulator) + + ' not previously added.') + else: + self._sweeper_signals.remove(signalstring) + + def print_sweeper_settings(self): + """ + Pretty-print the current settings of the sweeper. + If Sweep.build_sweep and Sweep.get are called, the sweep described + here will be performed. + """ + print('ACQUISITION') + toprint = ['sweeper_BWmode', 'sweeper_BW', 'sweeper_order', + 'sweeper_averaging_samples', 'sweeper_averaging_time', + 'sweeper_settlingtime', 'sweeper_settlingtc'] + for paramname in toprint: + parameter = self.parameters[paramname] + print(' {}: {} ({})'.format(parameter.label, parameter.get(), + parameter.unit)) + + print('HORISONTAL') + toprint = ['sweeper_start', 'sweeper_stop', + 'sweeper_units', + 'sweeper_samplecount', + 'sweeper_param', 'sweeper_mode', + 'sweeper_timeout'] + for paramname in toprint: + parameter = self.parameters[paramname] + print(' {}: {}'.format(parameter.label, parameter.get())) + + print('VERTICAL') + count = 1 + for signal in self._sweeper_signals: + (_, _, _, dm, _, attr) = signal.split('/') + fmt = (count, int(dm)+1, attr) + print(' Signal {}: Demodulator {}: {}'.format(*fmt)) + count += 1 + + features = ['timeconstant', 'order', 'samplerate'] + print('DEMODULATORS') + demods = [] + for signal in self._sweeper_signals: + demods.append(int(signal.split('/')[3])) + demods = set(demods) + for dm in demods: + for feat in features: + parameter = self.parameters['demod{:d}_{}'.format(dm+1, feat)] + fmt = (dm+1, parameter.label, parameter.get(), parameter.unit) + print(' Demodulator {}: {}: {:.6f} ({})'.format(*fmt)) + print('META') + swptime = self.sweeper_sweeptime() + if swptime is not None: + print(' Expected sweep time: {:.1f} (s)'.format(swptime)) + else: + print(' Expected sweep time: N/A in auto mode') + print(' Sweep timeout: {} ({})'.format(self.sweeper_timeout.get(), + 's')) + ready = self.sweep_correctly_built + print(' Sweep built and ready to execute: {}'.format(ready)) + + def _scope_setter(self, scopemodule, mode, setting, value): + """ + set_cmd for all scope parameters. The value and setting are saved in + a dictionary which is read by the Scope parameter's build_scope method + and only then sent to the instrument. + + Args: + scopemodule (int): Indicates whether this is a setting of the + scopeModule or not. 1: it is a scopeModule setting, + 0: it is not. + mode (int): Indicates whether we are setting an int or a float. + 0: int, 1: float. NOTE: Ignored if scopemodule==1. + setting (str): The setting, e.g. 'length'. + value (Union[int, float, str]): The value to set. + """ + # Because setpoints need to be built + self.scope_correctly_built = False + + # Some parameters are linked to each other in specific ways + # Therefore, we need special actions for setting these parameters + + SRtranslation = {'kHz': 1e3, 'MHz': 1e6, 'GHz': 1e9, + 'khz': 1e3, 'Mhz': 1e6, 'Ghz': 1e9} + + def setlength(value): + # TODO: add validation. The GUI seems to correect this value + self.daq.setDouble('/{}/scopes/0/length'.format(self.device), + value) + SR_str = self.parameters['scope_samplingrate'].get() + (number, unit) = SR_str.split(' ') + SR = float(number)*SRtranslation[unit] + self.parameters['scope_duration']._save_val(value/SR) + self.daq.setInt('/{}/scopes/0/length'.format(self.device), value) + + def setduration(value): + # TODO: validation? + SR_str = self.parameters['scope_samplingrate'].get() + (number, unit) = SR_str.split(' ') + SR = float(number)*SRtranslation[unit] + N = int(np.round(value*SR)) + self.parameters['scope_length']._save_val(N) + self.parameters['scope_duration']._save_val(value) + self.daq.setInt('/{}/scopes/0/length'.format(self.device), N) + + def setholdoffseconds(value): + self.parameters['scope_trig_holdoffmode'].set('s') + self.daq.setDouble('/{}/scopes/0/trigholdoff'.format(self.device), + value) + + def setsamplingrate(value): + # When the sample rate is changed, the number of points of the trace + # remains unchanged and the duration changes accordingly + newSR_str = dict(zip(self._samplingrate_codes.values(), + self._samplingrate_codes.keys()))[value] + (number, unit) = newSR_str.split(' ') + newSR = float(number)*SRtranslation[unit] + oldSR_str = self.parameters['scope_samplingrate'].get() + (number, unit) = oldSR_str.split(' ') + oldSR = float(number)*SRtranslation[unit] + oldduration = self.parameters['scope_duration'].get() + newduration = oldduration*oldSR/newSR + self.parameters['scope_duration']._save_val(newduration) + self.daq.setInt('/{}/scopes/0/time'.format(self.device), value) + + specialcases = {'length': setlength, + 'duration': setduration, + 'scope_trig_holdoffseconds': setholdoffseconds, + 'time': setsamplingrate} + + if setting in specialcases: + specialcases[setting](value) + self.daq.sync() + return + else: + # We have two different parameter types: those under + # /scopes/0/ and those under scopeModule/ + if scopemodule: + self.scope.set('scopeModule/{}'.format(setting), value) + elif mode == 0: + self.daq.setInt('/{}/scopes/0/{}'.format(self.device, + setting), value) + elif mode == 1: + self.daq.setDouble('/{}/scopes/0/{}'.format(self.device, + setting), value) + return + + def _scope_getter(self, setting): + """ + get_cmd for scopeModule parameters + + """ + # There are a few special cases + SRtranslation = {'kHz': 1e3, 'MHz': 1e6, 'GHz': 1e9, + 'khz': 1e3, 'Mhz': 1e6, 'Ghz': 1e9} + + def getduration(): + SR_str = self.parameters['scope_samplingrate'].get() + (number, unit) = SR_str.split(' ') + SR = float(number)*SRtranslation[unit] + N = self.parameters['scope_length'].get() + duration = N/SR + return duration + + specialcases = {'duration': getduration} + + if setting in specialcases: + value = specialcases[setting]() + else: + querystr = 'scopeModule/' + setting + returndict = self.scope.get(querystr) + # The dict may have different 'depths' depending on the parameter. + # The depth is encoded in the setting string (number of '/') + keys = setting.split('/')[1:] + + while keys != []: + key = keys.pop(0) + returndict = returndict[key] + rawvalue = returndict + + if isinstance(rawvalue, np.ndarray) and len(rawvalue) == 1: + value = rawvalue[0] + elif isinstance(rawvalue, list) and len(rawvalue) == 1: + value = rawvalue[0] + else: + value = rawvalue + + return value + + def close(self): + """ + Override of the base class' close function + """ + self.scope.clear() + self.sweeper.clear() + self.daq.disconnect() + super().close() diff --git a/qcodes/instrument_drivers/ZI/__init__.py b/qcodes/instrument_drivers/ZI/__init__.py new file mode 100644 index 000000000000..7b395efcbc52 --- /dev/null +++ b/qcodes/instrument_drivers/ZI/__init__.py @@ -0,0 +1 @@ +# emnpy __init__ file \ No newline at end of file diff --git a/qcodes/instrument_drivers/devices.py b/qcodes/instrument_drivers/devices.py index 0294b3ddf8e9..d368dedd1f34 100644 --- a/qcodes/instrument_drivers/devices.py +++ b/qcodes/instrument_drivers/devices.py @@ -68,7 +68,7 @@ def __init__(self, metadata=self.v1.metadata) # extend metadata - self._meta_attrs.extend(['v1', 'division_value']) + self._meta_attrs.extend(["division_value"]) def set(self, value: Union[int, float]) -> None: instrument_value = value * self.division_value diff --git a/qcodes/instrument_drivers/tektronix/AWG5014.py b/qcodes/instrument_drivers/tektronix/AWG5014.py index 6de75edadb70..147343dd5960 100644 --- a/qcodes/instrument_drivers/tektronix/AWG5014.py +++ b/qcodes/instrument_drivers/tektronix/AWG5014.py @@ -157,12 +157,20 @@ def __init__(self, name, address, timeout=180, **kwargs): vals=vals.Enum('CONT', 'TRIG', 'SEQ', 'GAT'), get_parser=self.newlinestripper ) - self.add_parameter('ref_clock_source', - label='Reference clock source', + self.add_parameter('clock_source', + label='Clock source', get_cmd='AWGControl:CLOCk:SOURce?', set_cmd='AWGControl:CLOCk:SOURce ' + '{}', vals=vals.Enum('INT', 'EXT'), get_parser=self.newlinestripper) + + self.add_parameter('ref_source', + label='Reference source', + get_cmd='SOURce1:ROSCillator:SOURce?', + set_cmd='SOURce1:ROSCillator:SOURce ' + '{}', + vals=vals.Enum('INT', 'EXT'), + get_parser=self.newlinestripper) + self.add_parameter('DC_output', label='DC Output (ON/OFF)', get_cmd='AWGControl:DC:STATe?', @@ -331,8 +339,9 @@ def __init__(self, name, address, timeout=180, **kwargs): get_cmd=filter_cmd + '?', set_cmd=filter_cmd + ' {}', vals=vals.Enum(20e6, 100e6, 9.9e37, + np.float('inf'), 'INF', 'INFinity'), - get_parser=float) + get_parser=self._tek_outofrange_get_parser) self.add_parameter('ch{}_DC_out'.format(i), label='DC output level channel {}'.format(i), unit='V', @@ -352,7 +361,7 @@ def __init__(self, name, address, timeout=180, **kwargs): self.add_parameter( 'ch{}_m{}_del'.format(i, j), label='Channel {} Marker {} delay'.format(i, j), - units='ns', + unit='ns', get_cmd=m_del_cmd + '?', set_cmd=m_del_cmd + ' {:.3f}e-9', vals=vals.Numbers(0, 1), @@ -387,6 +396,14 @@ def newlinestripper(self, string): else: return string + def _tek_outofrange_get_parser(self, string): + val = float(string) + # note that 9.9e37 is used as a generic out of range value + # in tektronix instruments + if val >= 9.9e37: + val = np.float('INF') + return val + # Functions def get_all(self, update=True): """ diff --git a/qcodes/instrument_drivers/tektronix/Keithley_2400.py b/qcodes/instrument_drivers/tektronix/Keithley_2400.py new file mode 100644 index 000000000000..6b99fd94b665 --- /dev/null +++ b/qcodes/instrument_drivers/tektronix/Keithley_2400.py @@ -0,0 +1,118 @@ +from qcodes import VisaInstrument +from qcodes.utils.validators import Strings, Enum + + +class Keithley_2400(VisaInstrument): + """ + QCoDeS driver for the Keithley 2400 voltage source. + """ + def __init__(self, name, address, **kwargs): + super().__init__(name, address, terminator='\n', **kwargs) + + self.add_parameter('rangev', + get_cmd='SENS:VOLT:RANG?', + get_parser=float, + set_cmd='SOUR:VOLT:RANG {:f}', + label='Voltage range') + + self.add_parameter('rangei', + get_cmd='SENS:CURR:RANG?', + get_parser=float, + set_cmd='SOUR:CURR:RANG {:f}', + label='Current range') + + self.add_parameter('compliancev', + get_cmd='SENS:VOLT:PROT?', + get_parser=float, + set_cmd='SENS:VOLT:PROT {:f}', + label='Voltage Compliance') + + self.add_parameter('compliancei', + get_cmd='SENS:CURR:PROT?', + get_parser=float, + set_cmd='SENS:CURR:PROT {:f}', + label='Current Compliance') + + self.add_parameter('volt', + get_cmd=':READ?', + get_parser=self._volt_parser, + set_cmd=':SOUR:VOLT:LEV {:.8f}', + label='Voltage', + unit='V') + + self.add_parameter('curr', + get_cmd=':READ?', + get_parser=self._curr_parser, + set_cmd=':SOUR:CURR:LEV {:.8f}', + label='Current', + unit='A') + + self.add_parameter('mode', + vals=Enum('VOLT', 'CURR'), + get_cmd=':SOUR:FUNC?', + set_cmd=self._set_mode_and_sense, + label='Mode') + + self.add_parameter('sense', + vals=Strings(), + get_cmd=':SENS:FUNC?', + set_cmd=':SENS:FUNC "{:s}"', + label='Sense mode') + + self.add_parameter('output', + get_parser=int, + set_cmd=':OUTP:STAT {:d}', + get_cmd=':OUTP:STAT?') + + self.add_parameter('nplcv', + get_cmd='SENS:VOLT:NPLC?', + get_parser=float, + set_cmd='SENS:VOLT:NPLC {:f}', + label='Voltage integration time') + + self.add_parameter('nplci', + get_cmd='SENS:CURR:NPLC?', + get_parser=float, + set_cmd='SENS:CURR:NPLC {:f}', + label='Current integration time') + + self.add_parameter('resistance', + get_cmd=':READ?', + get_parser=self._resistance_parser, + label='Resistance', + units='Ohm') + + def _set_mode_and_sense(self, msg): + # This helps set the correct read out curr/volt + if msg == 'VOLT': + self.sense('CURR') + elif msg == 'CURR': + self.sense('VOLT') + else: + raise AttributeError('Mode does not exist') + self.write(':SOUR:FUNC {:s}'.format(msg)) + + def reset(self): + """ + Reset the instrument. When the instrument is reset, it performs the + following actions. + + Returns the SourceMeter to the GPIB default conditions. + + Cancels all pending commands. + + Cancels all previously send '*OPC' and '*OPC?' + """ + self.write(':*RST') + + def _volt_parser(self, msg): + fields = [float(x) for x in msg.split(',')] + return fields[0] + + def _curr_parser(self, msg): + fields = [float(x) for x in msg.split(',')] + return fields[1] + + def _resistance_parser(self, msg): + fields = [float(x) for x in msg.split(',')] + return fields[0]/fields[1] diff --git a/qcodes/instrument_drivers/tektronix/Keithley_2600.py b/qcodes/instrument_drivers/tektronix/Keithley_2600.py index ce05422e25e5..7b9128a5b105 100644 --- a/qcodes/instrument_drivers/tektronix/Keithley_2600.py +++ b/qcodes/instrument_drivers/tektronix/Keithley_2600.py @@ -19,20 +19,26 @@ def __init__(self, name, address, channel, **kwargs): super().__init__(name, address, terminator='\n', **kwargs) self._channel = channel - self.add_parameter('volt', get_cmd='measure.v()', - get_parser=float, set_cmd='source.levelv={:.12f}', + self.add_parameter('volt', + get_cmd='measure.v()', + get_parser=float, + set_cmd='source.levelv={:.12f}', label='Voltage', unit='V') - self.add_parameter('curr', get_cmd='measure.i()', - get_parser=float, set_cmd='source.leveli={:.12f}', + self.add_parameter('curr', + get_cmd='measure.i()', + get_parser=float, + set_cmd='source.leveli={:.12f}', label='Current', unit='A') self.add_parameter('mode', get_cmd='source.func', + get_parser=float, set_cmd='source.func={:d}', val_mapping={'current': 0, 'voltage': 1}) self.add_parameter('output', get_cmd='source.output', + get_parser=float, set_cmd='source.output={:d}', val_mapping={'on': 1, 'off': 0}) # Source range diff --git a/qcodes/instrument_drivers/tektronix/Keithley_2700.py b/qcodes/instrument_drivers/tektronix/Keithley_2700.py index 0cde61404fc6..f9de3a845701 100644 --- a/qcodes/instrument_drivers/tektronix/Keithley_2700.py +++ b/qcodes/instrument_drivers/tektronix/Keithley_2700.py @@ -163,50 +163,14 @@ def __init__(self, name, address, reset=False, **kwargs): 'To get the integrationtime as a Number ' 'of PowerLine Cycles, use get_nplc().')) - ''' - self.add_parameter('trigger_source', - flags=Instrument.FLAG_GETSET, - units='') - self.add_parameter('trigger_timer', - flags=Instrument.FLAG_GETSET, - units='s', minval=0.001, maxval=99999.999, type=float) - self.add_parameter('readval', flags=Instrument.FLAG_GET, - units='arb.unit', - type=float, - tags=['measure']) - self.add_parameter('readlastval', flags=Instrument.FLAG_GET, - units='arb.unit', - type=float, - tags=['measure']) - self.add_parameter('readnextval', flags=Instrument.FLAG_GET, - units='arb.unit', - type=float, - tags=['measure']) - self.add_parameter('autozero', flags=Instrument.FLAG_GETSET, - type=bool) - self.add_parameter('averaging_window', - flags=Instrument.FLAG_GETSET, - units='%', type=float, minval=0, maxval=10) - self.add_parameter('averaging_count', - flags=Instrument.FLAG_GETSET, - units='#', type=int, minval=1, maxval=100) - self.add_parameter('averaging_type', - flags=Instrument.FLAG_GETSET, - type=bytes, units='') - self.add_parameter('autorange', - flags=Instrument.FLAG_GETSET, - units='', - type=bool) - ''' - # add functions self.add_parameter('amplitude', - units='arb.unit', + unit='arb.unit', label=name, get_cmd=':DATA:FRESH?', get_parser=float) self.add_parameter('readnext', - units='arb.unit', + unit='arb.unit', label=name, get_cmd=':DATA:FRESH?', get_parser=float) diff --git a/qcodes/loops.py b/qcodes/loops.py index 191bb09dca70..5f1a3c6ee783 100644 --- a/qcodes/loops.py +++ b/qcodes/loops.py @@ -48,18 +48,13 @@ from datetime import datetime import logging -import multiprocessing as mp import time import numpy as np -import warnings -from qcodes import config from qcodes.station import Station -from qcodes.data.data_set import new_data, DataMode +from qcodes.data.data_set import new_data from qcodes.data.data_array import DataArray -from qcodes.data.manager import get_data_manager from qcodes.utils.helpers import wait_secs, full_class, tprint -from qcodes.process.qcodes_process import QcodesProcess from qcodes.utils.metadata import Metadatable from .actions import (_actions_snapshot, Task, Wait, _Measure, _Nest, @@ -67,84 +62,6 @@ log = logging.getLogger(__name__) -# Switches off multiprocessing by default, cant' be altered after module -USE_MP = config.core.legacy_mp -MP_NAME = 'Measurement' - - -def get_bg(return_first=False): - """ - Find the active background measurement process, if any - returns None otherwise. - - Todo: - RuntimeError message is really hard to understand. - Args: - return_first(bool): if there are multiple loops running return the - first anyway. - Raises: - RuntimeError: if multiple loops are active and return_first is False. - Returns: - Union[loop, None]: active loop or none if no loops are active - """ - processes = mp.active_children() - loops = [p for p in processes if getattr(p, 'name', '') == MP_NAME] - - if len(loops) > 1 and not return_first: - raise RuntimeError('Oops, multiple loops are running???') - - if loops: - return loops[0] - - # if we got here, there shouldn't be a loop running. Make sure the - # data manager, if there is one, agrees! - _clear_data_manager() - return None - - -def halt_bg(timeout=5, traceback=True): - """ - Stop the active background measurement process, if any. - - Args: - timeout (int): seconds to wait for a clean exit before forcibly - terminating. - - traceback (bool): whether to print a traceback at the point of - interrupt, for debugging purposes. - """ - loop = get_bg(return_first=True) - if not loop: - print('No loop running') - return - - if traceback: - signal_ = ActiveLoop.HALT_DEBUG - else: - signal_ = ActiveLoop.HALT - - loop.signal_queue.put(signal_) - loop.join(timeout) - - if loop.is_alive(): - loop.terminate() - loop.join(timeout/2) - print('Background loop did not respond to halt signal, terminated') - - _clear_data_manager() - - -def _clear_data_manager(): - dm = get_data_manager(only_existing=True) - if dm and dm.ask('get_measuring'): - dm.ask('finalize_data') - -# TODO(giulioungaretti) remove dead code -# def measure(*actions): -# # measure has been moved into Station -# # TODO - for all-at-once parameters we want to be able to -# # store the output into a DataSet without making a Loop. -# pass class Loop(Metadatable): @@ -314,8 +231,7 @@ def run_temp(self, *args, **kwargs): shortcut to run a loop in the foreground as a temporary dataset using the default measurement set """ - return self.run(*args, background=False, quiet=True, - data_manager=False, location=False, **kwargs) + return self.run(*args, quiet=True, location=False, **kwargs) def then(self, *actions, overwrite=False): """ @@ -404,12 +320,6 @@ class ActiveLoop(Metadatable): The *ActiveLoop* determines what *DataArray*\s it will need to hold the data it collects, and it creates a *DataSet* holding these *DataArray*\s """ - # constants for signal_queue - HALT = 'HALT LOOP' - HALT_DEBUG = 'HALT AND DEBUG' - - # maximum sleep time (secs) between checking the signal_queue for a HALT - signal_period = 1 def __init__(self, sweep_values, delay, *actions, then_actions=(), station=None, progress_interval=None, bg_task=None, @@ -426,26 +336,11 @@ def __init__(self, sweep_values, delay, *actions, then_actions=(), 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 - # if we saved the results, we wouldn't capture nesting - # nor would we be able to reuse an ActiveLoop multiple times - # within one outer Loop. - # TODO: this doesn't work, because _Measure needs the data_set, - # which doesn't exist yet - do we want to make a special "dry run" - # mode, or is it sufficient to let errors wait until .run()? - # self._compile_actions(actions) - # if the first action is another loop, it changes how delays # happen - the outer delay happens *after* the inner var gets # set to its initial value self._nest_first = hasattr(actions[0], 'containers') - # for sending halt signals to the loop - self.signal_queue = mp.Queue() - - self._monitor = None # TODO: how to specify this? - def then(self, *actions, overwrite=False): """ Attach actions to be performed after the loop completes. @@ -556,18 +451,25 @@ def _parameter_arrays(self, action): action_indices = ((),) else: raise ValueError('a gettable parameter must have .name or .names') - + if hasattr(action, 'names') and hasattr(action, 'units'): + units = action.units + elif hasattr(action, 'unit'): + units = (action.unit,) + else: + units = tuple(['']*len(names)) num_arrays = len(names) shapes = getattr(action, 'shapes', None) sp_vals = getattr(action, 'setpoints', None) sp_names = getattr(action, 'setpoint_names', None) sp_labels = getattr(action, 'setpoint_labels', None) + sp_units = getattr(action, 'setpoint_units', None) if shapes is None: shapes = (getattr(action, 'shape', ()),) * num_arrays sp_vals = (sp_vals,) * num_arrays sp_names = (sp_names,) * num_arrays sp_labels = (sp_labels,) * num_arrays + sp_units = (sp_units,) * num_arrays else: sp_blank = (None,) * num_arrays # _fill_blank both supplies defaults and tests length @@ -576,26 +478,28 @@ def _parameter_arrays(self, action): sp_vals = self._fill_blank(sp_vals, sp_blank) sp_names = self._fill_blank(sp_names, sp_blank) sp_labels = self._fill_blank(sp_labels, sp_blank) + sp_units = self._fill_blank(sp_units, sp_blank) # now loop through these all, to make the DataArrays # record which setpoint arrays we've made, so we don't duplicate all_setpoints = {} - for name, full_name, label, shape, i, sp_vi, sp_ni, sp_li in zip( - names, full_names, labels, shapes, action_indices, - sp_vals, sp_names, sp_labels): + for name, full_name, label, unit, shape, i, sp_vi, sp_ni, sp_li, sp_ui in zip( + names, full_names, labels, units, shapes, action_indices, + sp_vals, sp_names, sp_labels, sp_units): if shape is None or shape == (): - shape, sp_vi, sp_ni, sp_li = (), (), (), () + shape, sp_vi, sp_ni, sp_li, sp_ui= (), (), (), (), () else: sp_blank = (None,) * len(shape) sp_vi = self._fill_blank(sp_vi, sp_blank) sp_ni = self._fill_blank(sp_ni, sp_blank) sp_li = self._fill_blank(sp_li, sp_blank) + sp_ui = self._fill_blank(sp_ui, sp_blank) setpoints = () # loop through dimensions of shape to make the setpoint arrays - for j, (vij, nij, lij) in enumerate(zip(sp_vi, sp_ni, sp_li)): - sp_def = (shape[: 1 + j], j, setpoints, vij, nij, lij) + for j, (vij, nij, lij, uij) in enumerate(zip(sp_vi, sp_ni, sp_li, sp_ui)): + sp_def = (shape[: 1 + j], j, setpoints, vij, nij, lij, uij) if sp_def not in all_setpoints: all_setpoints[sp_def] = self._make_setpoint_array(*sp_def) out.append(all_setpoints[sp_def]) @@ -603,7 +507,7 @@ def _parameter_arrays(self, action): # finally, make the output data array with these setpoints out.append(DataArray(name=name, full_name=full_name, label=label, - shape=shape, action_indices=i, + shape=shape, action_indices=i, unit=unit, set_arrays=setpoints, parameter=action)) return out @@ -617,7 +521,7 @@ def _fill_blank(self, inputs, blanks): raise ValueError('Wrong number of inputs supplied') def _make_setpoint_array(self, shape, i, prev_setpoints, vals, name, - label): + label, unit): if vals is None: vals = self._default_setpoints(shape) elif isinstance(vals, DataArray): @@ -645,7 +549,7 @@ def _make_setpoint_array(self, shape, i, prev_setpoints, vals, name, name = 'index{}'.format(i) return DataArray(name=name, label=label, set_arrays=prev_setpoints, - shape=shape, preset_data=vals) + shape=shape, preset_data=vals, unit=unit, is_setpoint=True) def _default_setpoints(self, shape): if len(shape) == 1: @@ -658,7 +562,7 @@ def _default_setpoints(self, shape): return sp - def set_common_attrs(self, data_set, use_threads, signal_queue): + def set_common_attrs(self, data_set, use_threads): """ set a couple of common attributes that the main and nested loops all need to have: @@ -666,23 +570,12 @@ def set_common_attrs(self, data_set, use_threads, signal_queue): - a queue for communicating with the main process """ self.data_set = data_set - self.signal_queue = signal_queue self.use_threads = use_threads for action in self.actions: if hasattr(action, 'set_common_attrs'): - action.set_common_attrs(data_set, use_threads, signal_queue) - - def _check_signal(self): - while not self.signal_queue.empty(): - signal_ = self.signal_queue.get() - if signal_ == self.HALT: - raise _QuietInterrupt('sweep was halted') - elif signal_ == self.HALT_DEBUG: - raise _DebugInterrupt('sweep was halted') - else: - raise ValueError('unknown signal', signal_) + action.set_common_attrs(data_set, use_threads) - def get_data_set(self, data_manager=USE_MP, *args, **kwargs): + def get_data_set(self, *args, **kwargs): """ Return the data set for this loop. @@ -715,22 +608,12 @@ def get_data_set(self, data_manager=USE_MP, *args, **kwargs): 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: - warnings.warn("Multiprocessing is in beta, use at own risk", - UserWarning) - data_mode = DataMode.PUSH_TO_SERVER - - data_set = new_data(arrays=self.containers(), mode=data_mode, - data_manager=data_manager, *args, **kwargs) - + data_set = new_data(arrays=self.containers(), *args, **kwargs) self.data_set = data_set else: has_args = len(kwargs) or len(args) - uses_data_manager = (self.data_set.mode != DataMode.LOCAL) - if has_args or (uses_data_manager != data_manager): + if has_args: raise RuntimeError( 'The DataSet for this loop already exists. ' 'You can only provide DataSet attributes, such as ' @@ -745,23 +628,18 @@ def run_temp(self, **kwargs): especially for use in composite parameters that need to run a Loop as part of their get method """ - return self.run(background=False, quiet=True, - data_manager=False, location=False, **kwargs) + return self.run(quiet=True, location=False, **kwargs) - def run(self, background=USE_MP, use_threads=False, quiet=False, - data_manager=USE_MP, station=None, progress_interval=False, - *args, **kwargs): + def run(self, use_threads=False, quiet=False, station=None, + progress_interval=False, *args, **kwargs): """ Execute this loop. Args: - background: (default False) run this sweep in a separate process - so we can have live plotting and other analysis in the main process use_threads: (default False): whenever there are multiple `get` calls back-to-back, execute them in separate threads so they run in parallel (as long as they don't block each other) quiet: (default False): set True to not print anything except errors - data_manager: set to True to use a DataManager. Default to False. station: a Station instance for snapshots (omit to use a previously provided Station, or the default Station) progress_interval (default None): show progress of the loop every x @@ -794,23 +672,9 @@ def run(self, background=USE_MP, use_threads=False, quiet=False, if progress_interval is not False: self.progress_interval = progress_interval - prev_loop = get_bg() - if prev_loop: - if not quiet: - print('Waiting for the previous background Loop to finish...', - flush=True) - prev_loop.join() - - data_set = self.get_data_set(data_manager, *args, **kwargs) + data_set = self.get_data_set(*args, **kwargs) - if background and not getattr(data_set, 'data_manager', None): - warnings.warn( - 'With background=True you must also set data_manager=True ' - 'or you will not be able to sync your DataSet.', - UserWarning) - - self.set_common_attrs(data_set=data_set, use_threads=use_threads, - signal_queue=self.signal_queue) + self.set_common_attrs(data_set=data_set, use_threads=use_threads) station = station or self.station or Station.default if station: @@ -822,47 +686,16 @@ def run(self, background=USE_MP, use_threads=False, quiet=False, ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') data_set.add_metadata({'loop': { 'ts_start': ts, - 'background': background, 'use_threads': use_threads, - 'use_data_manager': (data_manager is not False) }}) data_set.save_metadata() - if prev_loop and not quiet: - print('...done. Starting ' + (data_set.location or 'new loop'), - flush=True) - try: - if background: - warnings.warn("Multiprocessing is in beta, use at own risk", - UserWarning) - p = QcodesProcess(target=self._run_wrapper, name=MP_NAME) - p.is_sweep = True - p.signal_queue = self.signal_queue - p.start() - self.process = p - - # now that the data_set we created has been put in the loop - # process, this copy turns into a reader - # if you're not using a DataManager, it just stays local - # and sync() reads from disk - if self.data_set.mode == DataMode.PUSH_TO_SERVER: - self.data_set.mode = DataMode.PULL_FROM_SERVER - self.data_set.sync() - else: - if hasattr(self, 'process'): - # in case this ActiveLoop was run before in the background - del self.process - - self._run_wrapper() - - if self.data_set.mode != DataMode.LOCAL: - self.data_set.sync() - + self._run_wrapper() ds = self.data_set - finally: + if not quiet: print(repr(self.data_set)) print(datetime.now().strftime('started at %Y-%m-%d %H:%M:%S')) @@ -906,17 +739,16 @@ def _compile_one(self, action, new_action_indices): return action def _run_wrapper(self, *args, **kwargs): - try: - self._run_loop(*args, **kwargs) - except _QuietInterrupt: - pass - finally: - if hasattr(self, 'data_set'): - # somehow this does not show up in the data_set returned by - # run(), but it is saved to the metadata - ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - self.data_set.add_metadata({'loop': {'ts_end': ts}}) - self.data_set.finalize() + # try: + self._run_loop(*args, **kwargs) + # finally: + if hasattr(self, 'data_set'): + # TODO (giulioungaretti) WTF? + # somehow this does not show up in the data_set returned by + # run(), but it is saved to the metadata + ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.data_set.add_metadata({'loop': {'ts_end': ts}}) + self.data_set.finalize() def _run_loop(self, first_delay=0, action_indices=(), loop_indices=(), current_values=(), @@ -1009,13 +841,6 @@ def _run_loop(self, first_delay=0, action_indices=(), 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() @@ -1031,26 +856,5 @@ def _run_loop(self, first_delay=0, action_indices=(), def _wait(self, delay): if delay: finish_clock = time.perf_counter() + delay - - if self._monitor: - # TODO - perhpas pass self._check_signal in here - # so that we can halt within monitor.call if it - # lasts a very long time? - self._monitor.call(finish_by=finish_clock) - - while True: - self._check_signal() - t = wait_secs(finish_clock) - time.sleep(min(t, self.signal_period)) - if t <= self.signal_period: - break - else: - self._check_signal() - - -class _QuietInterrupt(Exception): - pass - - -class _DebugInterrupt(Exception): - pass + t = wait_secs(finish_clock) + time.sleep(t) diff --git a/qcodes/measure.py b/qcodes/measure.py index 17063fd38c4b..74443d3b5870 100644 --- a/qcodes/measure.py +++ b/qcodes/measure.py @@ -1,7 +1,7 @@ from datetime import datetime from qcodes.instrument.parameter import ManualParameter -from qcodes.loops import Loop, USE_MP +from qcodes.loops import Loop from qcodes.actions import _actions_snapshot from qcodes.utils.helpers import full_class from qcodes.utils.metadata import Metadatable @@ -29,11 +29,9 @@ def run_temp(self, **kwargs): """ Wrapper to run this measurement as a temporary data set """ - return self.run(quiet=True, data_manager=False, location=False, - **kwargs) + return self.run(quiet=True, location=False, **kwargs) - def run(self, use_threads=False, quiet=False, data_manager=USE_MP, - station=None, **kwargs): + def run(self, use_threads=False, quiet=False, station=None, **kwargs): """ Run the actions in this measurement and return their data as a DataSet @@ -71,11 +69,7 @@ def run(self, use_threads=False, quiet=False, data_manager=USE_MP, a DataSet object containing the results of the measurement """ - # background is not configurable, would be weird to run this in the bg - background = False - - data_set = self._dummyLoop.get_data_set(data_manager=data_manager, - **kwargs) + data_set = self._dummyLoop.get_data_set(**kwargs) # set the DataSet to local for now so we don't save it, since # we're going to massage it afterward @@ -83,7 +77,7 @@ def run(self, use_threads=False, quiet=False, data_manager=USE_MP, data_set.location = False # run the measurement as if it were a Loop - self._dummyLoop.run(background=background, use_threads=use_threads, + self._dummyLoop.run(use_threads=use_threads, station=station, quiet=True) # look for arrays that are unnecessarily nested, and un-nest them @@ -128,8 +122,7 @@ def run(self, use_threads=False, quiet=False, data_manager=USE_MP, # puts in a 'loop' section that we need to replace with 'measurement' # but we use the info from 'loop' to ensure consistency and avoid # duplication. - LOOP_SNAPSHOT_KEYS = ['background', 'ts_start', 'ts_end', - 'use_data_manager', 'use_threads'] + LOOP_SNAPSHOT_KEYS = ['ts_start', 'ts_end', 'use_threads'] data_set.add_metadata({'measurement': { k: data_set.metadata['loop'][k] for k in LOOP_SNAPSHOT_KEYS }}) diff --git a/qcodes/plots/base.py b/qcodes/plots/base.py index 724945d7aa2b..00c255c23072 100644 --- a/qcodes/plots/base.py +++ b/qcodes/plots/base.py @@ -1,9 +1,7 @@ """ Live plotting in Jupyter notebooks """ -from IPython.display import display -from qcodes import config class BasePlot: @@ -24,13 +22,7 @@ def __init__(self, interval=1, data_keys='xyz'): self.data_keys = data_keys self.traces = [] self.data_updaters = set() - # only import in name space if the gui is set to noebook - # and there is multiprocessing self.interval = interval - if config['gui']['notebook'] and config['core']['legacy_mp']: - from qcodes.widgets.widgets import HiddenUpdateWidget - self.update_widget = HiddenUpdateWidget(self.update, interval) - display(self.update_widget) def clear(self): """ @@ -81,7 +73,7 @@ def add(self, *args, updater=None, **kwargs): into `x`, `y`, and optionally `z`, these are passed along to self.add_to_plot. To use custom labels and units pass for example: - plot.add(x=set, y=amplitude, + plot.add(x=set, y=amplitude, xlabel="set" xunit="V", ylabel= "Amplitude", diff --git a/qcodes/plots/qcmatplotlib.py b/qcodes/plots/qcmatplotlib.py index 128ec27accf6..7f0aa1fb06f3 100644 --- a/qcodes/plots/qcmatplotlib.py +++ b/qcodes/plots/qcmatplotlib.py @@ -126,13 +126,19 @@ def _update_labels(self, ax, config): # the data array inside the config getter = getattr(ax, "get_{}label".format(axletter)) if axletter in config and not getter(): - # now if we did not have any kwark gor label or unit + # now if we did not have any kwarg for label or unit # fallback to the data_array - if unit is None: + if unit is None: _, unit = self.get_label(config[axletter]) if label is None: label, _ = self.get_label(config[axletter]) - + elif getter(): + # The axis already has label. Assume that is correct + # We should probably check consistent units and error or warn + # if not consistent. It's also not at all clear how to handle + # labels/names as these will in general not be consistent on + # at least one axis + return axsetter = getattr(ax, "set_{}label".format(axletter)) axsetter("{} ({})".format(label, unit)) diff --git a/qcodes/process/helpers.py b/qcodes/process/helpers.py deleted file mode 100644 index 195eb2928fbd..000000000000 --- a/qcodes/process/helpers.py +++ /dev/null @@ -1,63 +0,0 @@ -"""multiprocessing helper functions.""" - -import multiprocessing as mp -import time -import warnings - -MP_ERR = 'context has already been set' - - -def set_mp_method(method, force=False): - """ - An idempotent wrapper for multiprocessing.set_start_method. - - The most important use of this is to force Windows behavior - on a Mac or Linux: set_mp_method('spawn') - args are the same: - - Args: - method (string): one of the following - - - 'fork' (default on unix/mac) - - 'spawn' (default, and only option, on windows) - - 'forkserver' - - force (bool): allow changing context? default False - in the original function, even calling the function again - with the *same* method raises an error, but here we only - raise the error if you *don't* force *and* the context changes - """ - warnings.warn("Multiprocessing is in beta, use at own risk", UserWarning) - try: - mp.set_start_method(method, force=force) - except RuntimeError as err: - if err.args != (MP_ERR, ): - raise - - mp_method = mp.get_start_method() - if mp_method != method: - raise RuntimeError( - 'unexpected multiprocessing method ' - '\'{}\' when trying to set \'{}\''.format(mp_method, method)) - - -def kill_queue(queue): - """Tear down a multiprocessing.Queue to help garbage collection.""" - try: - queue.close() - queue.join_thread() - except: - pass - - -def kill_processes(): - """Kill all running child processes.""" - # TODO: Instrument processes don't appropriately stop in all tests... - for process in mp.active_children(): - try: - process.terminate() - except: - pass - - if mp.active_children(): - time.sleep(0.2) diff --git a/qcodes/process/qcodes_process.py b/qcodes/process/qcodes_process.py deleted file mode 100644 index 7d80bea1d072..000000000000 --- a/qcodes/process/qcodes_process.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Modifications to multiprocessing.Process common to all Qcodes processes.""" - -import multiprocessing as mp -from traceback import print_exc -import signal - -from qcodes.utils.helpers import in_notebook - -from .stream_queue import get_stream_queue - - -class QcodesProcess(mp.Process): - - """ - Modified multiprocessing.Process specialized to Qcodes needs. - - - Nicer repr - - Automatic streaming of stdout and stderr to our StreamQueue singleton - for reporting back to the main process - - Ignore interrupt signals so that commands in the main process can be - canceled without affecting server and background processes. - """ - - def __init__(self, *args, name='QcodesProcess', queue_streams=True, - daemon=True, **kwargs): - """ - Construct the QcodesProcess, but like Process, do not start it. - - name: string to include in repr, and in the StreamQueue - default 'QcodesProcess' - queue_streams: should we connect stdout and stderr to the StreamQueue? - default True - daemon: should this process be treated as daemonic, so it gets - terminated with the parent. - default True, overriding the base inheritance - any other args and kwargs are passed to multiprocessing.Process - """ - # make sure the singleton StreamQueue exists - # prior to launching a new process - if queue_streams and in_notebook(): - self.stream_queue = get_stream_queue() - else: - self.stream_queue = None - super().__init__(*args, name=name, daemon=daemon, **kwargs) - - def run(self): - """Executed in the new process, and calls the target function.""" - # ignore interrupt signals, as they come from `KeyboardInterrupt` - # which we want only to apply to the main process and not the - # server and background processes (which can be halted in different - # ways) - signal.signal(signal.SIGINT, signal.SIG_IGN) - - if self.stream_queue: - self.stream_queue.connect(str(self.name)) - try: - super().run() - except: - # if we let the system print the exception by itself, sometimes - # it disconnects the stream partway through printing. - print_exc() - finally: - if (self.stream_queue and - self.stream_queue.initial_streams is not None): - self.stream_queue.disconnect() - - def __repr__(self): - """Shorter and more helpful repr of our processes.""" - cname = self.__class__.__name__ - r = super().__repr__() - r = r.replace(cname + '(', '').replace(')>', '>') - return r.replace(', started daemon', '') diff --git a/qcodes/process/server.py b/qcodes/process/server.py deleted file mode 100644 index d3a103b6f2a2..000000000000 --- a/qcodes/process/server.py +++ /dev/null @@ -1,399 +0,0 @@ -"""Common Server process and ServerManager architecture.""" - -import multiprocessing as mp -from traceback import format_exc -from uuid import uuid4 -import builtins -import logging - -QUERY_WRITE = 'WRITE' -QUERY_ASK = 'ASK' -RESPONSE_OK = 'OK' -RESPONSE_ERROR = 'ERROR' - -from qcodes.utils.nested_attrs import NestedAttrAccess -from .qcodes_process import QcodesProcess -from .helpers import kill_queue - - -class ServerManager: - - """ - Creates and communicates with a separate server process. - - Starts a *QcodesProcess*, and on that process it constructs a server - object of type *server_class*, which should normally be a subclass of - `BaseServer`. Client processes query the server via: - - - ``manager.ask(func_name, *args, **kwargs)``: if they want a response or want - to wait for confirmation that the query has completed - - ``manager.write(func_name, *args, **kwargs)``: if they want to continue - immediately without blocking for the query. - - The server communicates with this manager via two multiprocessing *Queue*\s. - """ - - def __init__(self, name, server_class, shared_attrs=None, - query_timeout=None): - """ - Construct the ServerManager and start its server. - - Args: - name: the name of the server. Can include .format specs to insert - all or part of the uuid - server_class: the class to create within the new process. - the constructor will be passed arguments: - query_queue, response_queue, shared_attrs - and should start an infinite loop watching query_queue and posting - responses to response_queue. - shared_attrs: any objects that need to be passed to the server on - startup, generally objects like Queues that are picklable only for - inheritance by a new process. - query_timeout: (default None) the default max time to wait for - responses - """ - self._query_queue = mp.Queue() - self._response_queue = mp.Queue() - self._server_class = server_class - self._shared_attrs = shared_attrs - - # query_lock is only used with queries that get responses - # to make sure the process that asked the question is the one - # that gets the response. - # Any query that does NOT expect a response can just dump it in - # and move on. - self.query_lock = mp.RLock() - - # uuid is used to pass references to this object around - # for example, to get it after someone else has sent it to a server - self.uuid = uuid4().hex - - self.name = name.format(self.uuid) - - self.query_timeout = query_timeout - self._start_server() - - def _start_server(self): - self._server = QcodesProcess(target=self._run_server, name=self.name) - self._server.start() - - def _run_server(self): - self._server_class(self._query_queue, self._response_queue, - self._shared_attrs) - - def _check_alive(self): - try: - if not self._server.is_alive(): - logging.warning('restarted {}'.format(self._server)) - self.restart() - except: - # can't test is_alive from outside the main process - pass - - def write(self, func_name, *args, **kwargs): - """ - Send a query to the server that does not expect a response. - - `write(func_name, *args, **kwargs)` proxies to server method: - `server.handle_(*args, **kwargs)` - """ - self._check_alive() - self._query_queue.put((QUERY_WRITE, func_name, args, kwargs)) - - def ask(self, func_name, *args, timeout=None, **kwargs): - """ - Send a query to the server and wait for a response. - - `resp = ask(func_name, *args, **kwargs)` proxies to server method: - `resp = server.handle_(*args, **kwargs)` - - optional timeout (default None) - not recommended, as if we quit - before reading the response, the query queue can get out of sync - """ - self._check_alive() - - timeout = timeout or self.query_timeout - self._expect_error = False - - query = (QUERY_ASK, func_name, args, kwargs) - - with self.query_lock: - # in case a previous query errored and left something on the - # response queue, clear it - while not self._response_queue.empty(): - value = self._get_response() - logging.warning( - 'unexpected data in response queue before ask:\n' + - repr(value)) - - self._query_queue.put(query) - - value = self._get_response(timeout=timeout, query=query) - - while not self._response_queue.empty(): - logging .warning( - 'unexpected multiple responses in queue during ask, ' - 'using the last one. earlier item(s):\n' + - repr(value)) - value = self._get_response(query=query) - - return value - - def _get_response(self, timeout=None, query=None): - res = self._response_queue.get(timeout=timeout) - try: - code, value = res - except (TypeError, ValueError): - code, value = '', res - - if code == RESPONSE_OK: - return value - - self._handle_error(code, value, query) - - def _handle_error(self, code, error_str, query=None): - error_head = '*** error on {} ***'.format(self.name) - - if query: - error_head += '\nwhile executing query: {}'.format(repr(query)) - - if code != RESPONSE_ERROR: - error_head += '\nunrecognized response code: {}'.format(code) - - # try to match the error type, if it's a built-in type - error_type_line = error_str.rstrip().rsplit('\n', 1)[-1] - error_type_str = error_type_line.split(':')[0].strip() - - err_type = getattr(builtins, error_type_str, None) - if err_type is None or not issubclass(err_type, Exception): - err_type = RuntimeError - - raise err_type(error_head + '\n\n' + error_str) - - def halt(self, timeout=2): - """ - Halt the server and end its process. - - Does not tear down, after this the server can still be started again. - """ - try: - if self._server.is_alive(): - self.write('halt') - self._server.join(timeout) - - if self._server.is_alive(): - self._server.terminate() - logging.warning('ServerManager did not respond to halt ' - 'signal, terminated') - self._server.join(timeout) - except AssertionError: - # happens when we get here from other than the main process - # where we shouldn't be able to kill the server anyway - pass - - def restart(self): - """Restart the server.""" - self.halt() - self._start_server() - - def close(self): - """Irreversibly stop the server and manager.""" - self.halt() - for q in ['query', 'response', 'error']: - qname = '_{}_queue'.format(q) - if hasattr(self, qname): - kill_queue(getattr(self, qname)) - del self.__dict__[qname] - if hasattr(self, 'query_lock'): - del self.query_lock - - -class BaseServer(NestedAttrAccess): - - """ - Base class for servers to run in separate processes. - - The server is started inside a `QcodesProcess` by a `ServerManager`, - and unifies the query handling protocol so that we are robust against - deadlocks, out of sync queues, or hidden errors. - - This base class doesn't start the event loop, a subclass should - either call `self.run_event_loop()` at the end of its `__init__` or - provide its own event loop. If making your own event loop, be sure to - call `self.process_query(query)` on any item that arrives in - `self._query_queue`. - - Subclasses should define handlers `handle_`, such that calls: - `response = server_manager.ask(func_name, *args, **kwargs)` - `server_manager.write(func_name, *args, **kwargs)` - map onto method calls: - `response = self.handle_(*args, **kwargs)` - - The actual query passed through the queue and unpacked by `process_query` - has the form `(code, func_name[, args][, kwargs])` where `code` is: - - - `QUERY_ASK` (from `server_manager.ask`): will always send a response, - even if the function returns nothing (None) or throws an error. - - - `QUERY_WRITE` (from `server_manager.write`): will NEVER send a response, - return values are ignored and errors go to the logging framework. - - Three handlers are predefined: - - - `handle_halt` (but override it if your event loop does not use - self.running=False to stop) - - - `handle_get_handlers` (lists all available handler methods) - - - `handle_method_call` (call an arbitrary method on the server) - """ - - # just for testing - how long to allow it to wait on a queue.get - # in real situations this should always be None - timeout = None - - def __init__(self, query_queue, response_queue, shared_attrs=None): - """ - Create the BaseServer. - - Subclasses should match this call signature exactly, even if they - do not need shared_attrs, because it is used by `ServerManager` - to instantiate the server. - The base class does not start the event loop, subclasses should do - this at the end of their own `__init__`. - - query_queue: a multiprocessing.Queue that we listen to - - response_queue: a multiprocessing.Queue where we put responses - - shared_attrs: (default None) any objects (such as other Queues) - that we need to supply on initialization of the server because - they cannot be picked normally to pass through the Queue later. - """ - self._query_queue = query_queue - self._response_queue = response_queue - self._shared_attrs = shared_attrs - - def run_event_loop(self): - """ - The default event loop. When this method returns, the server stops. - - Override this method if you need to do more than just process queries - repeatedly, but make sure your event loop: - - - calls `self.process_query` to ensure robust error handling - - provides a way to halt the server (and override `handle_halt` if - it's not by setting `self.running = False`) - """ - self.running = True - while self.running: - query = self._query_queue.get(timeout=self.timeout) - self.process_query(query) - - def process_query(self, query): - """ - Act on one query received through the query queue. - - query: should have the form `(code, func_name[, args][, kwargs])` - """ - try: - code = None - code, func_name = query[:2] - - func = getattr(self, 'handle_' + func_name) - - args = None - kwargs = None - for part in query[2:]: - if isinstance(part, tuple) and args is None: - args = part - elif isinstance(part, dict) and kwargs is None: - kwargs = part - else: - raise ValueError(part) - - if code == QUERY_ASK: - self._process_ask(func, args or (), kwargs or {}) - elif code == QUERY_WRITE: - self._process_write(func, args or (), kwargs or {}) - else: - raise ValueError(code) - except: - self.report_error(query, code) - - def report_error(self, query, code): - """ - Common error handler for all queries. - - QUERY_ASK puts errors into the response queue for the asker to see. - QUERY_WRITE shouldn't write a response, so it logs errors instead. - Unknown modes do *both*, because we don't know where the user will be - looking and an error that severe it's OK to muck up the queue. - That's the only way you'll get a response without asking for one. - """ - error_str = ( - 'Expected query to be a tuple (code, func_name[, args][, kwargs]) ' - 'where code is QUERY_ASK or QUERY_WRITE, func_name points to a ' - 'method `handle_`, and optionally args is a tuple and ' - 'kwargs is a dict\nquery: ' + repr(query) + '\n' + format_exc()) - - if code != QUERY_ASK: - logging.error(error_str) - if code != QUERY_WRITE: - try: - self._response_queue.put((RESPONSE_ERROR, error_str)) - except: - logging.error('Could not put error on response queue\n' + - error_str) - - def _process_ask(self, func, args, kwargs): - try: - response = func(*args, **kwargs) - self._response_queue.put((RESPONSE_OK, response)) - except: - self._response_queue.put( - (RESPONSE_ERROR, repr((func, args, kwargs)) + '\n' + - format_exc())) - - def _process_write(self, func, args, kwargs): - try: - func(*args, **kwargs) - except: - logging.error(repr((func, args, kwargs)) + '\n' + format_exc()) - - def handle_halt(self): - """ - Quit this server. - - Just sets self.running=False, which the default event loop looks for - between queries. If you provide your own event loop and it does NOT - look for self.running, you should override this handler with a - different way to halt. - """ - self.running = False - - def handle_get_handlers(self): - """List all available query handlers.""" - handlers = [] - for name in dir(self): - if name.startswith('handle_') and callable(getattr(self, name)): - handlers.append(name[len('handle_'):]) - return handlers - - def handle_method_call(self, method_name, *args, **kwargs): - """ - Pass through arbitrary method calls to the server. - - Args: - method_name (str): the method name to call. - Primarily intended for NestedAttrAccess, ie: - ``getattr``, ``setattr``, ``callattr``, ``delattr``. - - *args (Any): passed to the method - - **kwargs (Any): passed to the method - - Returns: - Any: the return value of the method - """ - return getattr(self, method_name)(*args, **kwargs) diff --git a/qcodes/process/stream_queue.py b/qcodes/process/stream_queue.py deleted file mode 100644 index 3dcbc5bff62a..000000000000 --- a/qcodes/process/stream_queue.py +++ /dev/null @@ -1,152 +0,0 @@ -"""StreamQueue: collect subprocess stdout/stderr to a single queue.""" - -import multiprocessing as mp -import sys -import time - -from datetime import datetime - -from .helpers import kill_queue - - -def get_stream_queue(): - """ - Convenience function to get a singleton StreamQueue. - - note that this must be called from the main process before starting any - subprocesses that will use it, otherwise the subprocess will create its - own StreamQueue that no other processes know about - """ - if StreamQueue.instance is None: - StreamQueue.instance = StreamQueue() - return StreamQueue.instance - - -class StreamQueue: - - """ - Manages redirection of child process output for the main process to view. - - Do not instantiate this directly: use get_stream_queue so we only make one. - One StreamQueue should be created in the consumer process, and passed - to each child process. In the child, we call StreamQueue.connect with a - process name that will be unique and meaningful to the user. The consumer - then periodically calls StreamQueue.get() to read these messages. - - inspired by http://stackoverflow.com/questions/23947281/ - """ - - instance = None - - def __init__(self, *args, **kwargs): - """Create a StreamQueue, passing all args & kwargs to Queue.""" - self.queue = mp.Queue(*args, **kwargs) - self.last_read_ts = mp.Value('d', time.time()) - self._last_stream = None - self._on_new_line = True - self.lock = mp.RLock() - self.initial_streams = None - - def connect(self, process_name): - """ - Connect a child process to the StreamQueue. - - After this, stdout and stderr go to a queue rather than being - printed to a console. - - process_name: a short string that will clearly identify this process - to the user. - """ - if self.initial_streams is not None: - raise RuntimeError('StreamQueue is already connected') - - self.initial_streams = (sys.stdout, sys.stderr) - - sys.stdout = _SQWriter(self, process_name) - sys.stderr = _SQWriter(self, process_name + ' ERR') - - def disconnect(self): - """Disconnect a child from the queues and revert stdout & stderr.""" - if self.initial_streams is None: - raise RuntimeError('StreamQueue is not connected') - sys.stdout, sys.stderr = self.initial_streams - self.initial_streams = None - - def get(self): - """Read new messages from the queue and format them for printing.""" - out = '' - while not self.queue.empty(): - timestr, stream_name, msg = self.queue.get() - line_head = '[{} {}] '.format(timestr, stream_name) - - if self._on_new_line: - out += line_head - elif stream_name != self._last_stream: - out += '\n' + line_head - - out += msg[:-1].replace('\n', '\n' + line_head) + msg[-1] - - self._on_new_line = (msg[-1] == '\n') - self._last_stream = stream_name - - self.last_read_ts.value = time.time() - return out - - def __del__(self): - """Tear down the StreamQueue either on the main or a child process.""" - try: - self.disconnect() - except: - pass - - if hasattr(type(self), 'instance'): - # so nobody else tries to use this dismantled stream queue later - type(self).instance = None - - if hasattr(self, 'queue'): - kill_queue(self.queue) - del self.queue - if hasattr(self, 'lock'): - del self.lock - - -class _SQWriter: - MIN_READ_TIME = 3 - - def __init__(self, stream_queue, stream_name): - self.queue = stream_queue.queue - self.last_read_ts = stream_queue.last_read_ts - self.stream_name = stream_name - - def write(self, msg): - try: - if msg: - msgtuple = (datetime.now().strftime('%H:%M:%S.%f')[:-3], - self.stream_name, msg) - self.queue.put(msgtuple) - - queue_age = time.time() - self.last_read_ts.value - if queue_age > self.MIN_READ_TIME and msg != '\n': - # long time since the queue was read? maybe nobody is - # watching it at all - send messages to the terminal too - # but they'll still be in the queue if someone DOES look. - termstr = '[{} {}] {}'.format(*msgtuple) - # we always want a new line this way (so I don't use - # end='' in the print) but we don't want an extra if the - # caller already included a newline. - if termstr[-1] == '\n': - termstr = termstr[:-1] - try: - print(termstr, file=sys.__stdout__) - except ValueError: # pragma: no cover - # ValueError: underlying buffer has been detached - # this may just occur in testing on Windows, not sure. - pass - except: - # don't want to get an infinite loop if there's something wrong - # with the queue - put the regular streams back before handling - sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ - raise - - def flush(self): - pass diff --git a/qcodes/station.py b/qcodes/station.py index 08bd8ba44fb0..7b6cd9a020b5 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -3,8 +3,6 @@ from qcodes.utils.metadata import Metadatable from qcodes.utils.helpers import make_unique, DelegateAttributes -from qcodes.instrument.remote import RemoteInstrument -from qcodes.instrument.remote import RemoteParameter from qcodes.instrument.base import Instrument from qcodes.instrument.parameter import Parameter from qcodes.instrument.parameter import ManualParameter @@ -86,13 +84,12 @@ def snapshot_base(self, update=False): } for name, itm in self.components.items(): - if isinstance(itm, (RemoteInstrument, - Instrument)): + if isinstance(itm, (Instrument)): snap['instruments'][name] = itm.snapshot(update=update) elif isinstance(itm, (Parameter, ManualParameter, - StandardParameter, - RemoteParameter)): + StandardParameter + )): snap['parameters'][name] = itm.snapshot(update=update) else: snap['components'][name] = itm.snapshot(update=update) diff --git a/qcodes/test.py b/qcodes/test.py deleted file mode 100644 index b04c412b06ad..000000000000 --- a/qcodes/test.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Unified qcodes test runners.""" - -import sys - - -def test_core(verbosity=1, failfast=False): - """ - Run the qcodes core tests. - - Args: - verbosity (int, optional): 0, 1, or 2, higher displays more info - Default 1. - failfast (bool, optional): If true, stops running on first failure - Default False. - - Coverage testing is only available from the command line - """ - import qcodes - if qcodes.in_notebook(): - qcodes._IN_NOTEBOOK = True - - _test_core(verbosity=verbosity, failfast=failfast) - - -def _test_core(test_pattern='test*.py', **kwargs): - import unittest - - import qcodes.tests as qctest - import qcodes - - suite = unittest.defaultTestLoader.discover( - qctest.__path__[0], top_level_dir=qcodes.__path__[0], - pattern=test_pattern) - if suite.countTestCases() == 0: - print('found no tests') - sys.exit(1) - print('testing %d cases' % suite.countTestCases()) - - result = unittest.TextTestRunner(**kwargs).run(suite) - return result.wasSuccessful() - - -def test_part(name): - """ - Run part of the qcodes core test suite. - - Args: - name (str): a name within the qcodes.tests directory. May be: - - a module ('test_loop') - - a TestCase ('test_loop.TestLoop') - - a test method ('test_loop.TestLoop.test_nesting') - """ - import unittest - fullname = 'qcodes.tests.' + name - suite = unittest.defaultTestLoader.loadTestsFromName(fullname) - return unittest.TextTestRunner().run(suite).wasSuccessful() - -if __name__ == '__main__': - import argparse - import os - import multiprocessing as mp - - try: - import coverage - coverage_missing = False - except ImportError: - coverage_missing = True - - # make sure coverage looks for .coveragerc in the right place - os.chdir(os.path.dirname(os.path.abspath(__file__))) - - parser = argparse.ArgumentParser( - description=('Core test suite for Qcodes, ' - 'covering everything except instrument drivers')) - - parser.add_argument('-v', '--verbose', action='store_true', - help='increase verbosity') - - parser.add_argument('-q', '--quiet', action='store_true', - help='reduce verbosity (opposite of --verbose)') - - parser.add_argument('-s', '--skip-coverage', action='store_true', - help='skip coverage reporting') - - parser.add_argument('-t', '--test_pattern', type=str, default='test*.py', - help=('regexp for test name to match, ' - 'default "test*.py"')) - - parser.add_argument('-f', '--failfast', action='store_true', - help='halt on first error/failure') - - parser.add_argument('-m', '--mp-spawn', action='store_true', - help=('force "spawn" method of starting child ' - 'processes to emulate Win behavior on Unix')) - - args = parser.parse_args() - - if args.mp_spawn: - mp.set_start_method('spawn') - - args.skip_coverage |= coverage_missing - - if not args.skip_coverage: - cov = coverage.Coverage(source=['qcodes']) - cov.start() - - success = _test_core(verbosity=(1 + args.verbose - args.quiet), - failfast=args.failfast, - test_pattern=args.test_pattern) - - if not args.skip_coverage: - cov.stop() - cov.save() - cov.report() - - # restore unix-y behavior - # exit status 1 on fail - if not success: - sys.exit(1) diff --git a/qcodes/tests/data_mocks.py b/qcodes/tests/data_mocks.py index 73cc7c702306..d561064afe2d 100644 --- a/qcodes/tests/data_mocks.py +++ b/qcodes/tests/data_mocks.py @@ -1,34 +1,10 @@ import numpy -import multiprocessing as mp from qcodes.data.data_array import DataArray from qcodes.data.data_set import new_data from qcodes.data.io import DiskIO -class MockDataManager: - query_lock = mp.RLock() - - def __init__(self): - self.needs_restart = False - - def ask(self, *args, timeout=None): - if args == ('get_data', 'location'): - return self.location - elif args == ('get_data',): - return self.live_data - elif args[0] == 'new_data' and len(args) == 2: - if self.needs_restart: - raise AttributeError('data_manager needs a restart') - else: - self.data_set = args[1] - else: - raise Exception('unexpected query to MockDataManager') - - def restart(self): - self.needs_restart = False - - class MockFormatter: def read(self, data_set): data_set.has_read_data = True diff --git a/qcodes/tests/instrument_mocks.py b/qcodes/tests/instrument_mocks.py index f7bd5927a514..4c757a830e63 100644 --- a/qcodes/tests/instrument_mocks.py +++ b/qcodes/tests/instrument_mocks.py @@ -1,205 +1,10 @@ -import time import numpy as np from qcodes.instrument.base import Instrument -from qcodes.instrument.mock import MockInstrument, MockModel from qcodes.utils.validators import Numbers from qcodes.instrument.parameter import MultiParameter, ManualParameter -class AMockModel(MockModel): - - def __init__(self): - self._memory = {} - self._reset() - super().__init__() - - def _reset(self): - self._gates = [0.0, 0.0, 0.0] - self._excitation = 0.1 - - @staticmethod - def fmt(value): - return '{:.3f}'.format(value) - - def gates_set(self, parameter, value): - if parameter[0] == 'c': - self._gates[int(parameter[1:])] = float(value) - elif parameter == 'rst' and value is None: - # resets gates AND excitation, so we can use gates.reset() to - # reset the entire model - self._reset() - elif parameter[:3] == 'mem': - slot = int(parameter[3:]) - self._memory[slot] = value - else: - raise ValueError - - def gates_get(self, parameter): - if parameter[0] == 'c': - return self.fmt(self._gates[int(parameter[1:])]) - elif parameter[:3] == 'mem': - slot = int(parameter[3:]) - return self._memory[slot] - else: - raise ValueError - - def source_set(self, parameter, value): - if parameter == 'ampl': - try: - self._excitation = float(value) - except ValueError: - # "Off" as in the MultiType sweep step test - self._excitation = None - else: - raise ValueError(parameter, value) - - def source_get(self, parameter): - if parameter == 'ampl': - return self.fmt(self._excitation) - # put mem here too, just so we can be 100% sure it's going through - # the model - elif parameter[:3] == 'mem': - slot = int(parameter[3:]) - return self._memory[slot] - else: - raise ValueError - - def meter_get(self, parameter): - if parameter == 'ampl': - gates = self._gates - # here's my super complex model output! - return self.fmt(self._excitation * - (gates[0] + gates[1]**2 + gates[2]**3)) - elif parameter[:5] == 'echo ': - return self.fmt(float(parameter[5:])) - - # alias because we need new names when we instantiate an instrument - # locally at the same time as remotely - def gateslocal_set(self, parameter, value): - return self.gates_set(parameter, value) - - def gateslocal_get(self, parameter): - return self.gates_get(parameter) - - def sourcelocal_set(self, parameter, value): - return self.source_set(parameter, value) - - def sourcelocal_get(self, parameter): - return self.source_get(parameter) - - def meterlocal_get(self, parameter): - return self.meter_get(parameter) - - -class ParamNoDoc: - - def __init__(self, name, *args, **kwargs): - self.name = name - - def get_attrs(self): - return [] - - -class MockInstTester(MockInstrument): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.attach_adder() - - def attach_adder(self): - """ - this function attaches a closure to the object, so can only be - executed after creating the server because a closure is not - picklable - """ - a = 5 - - def f(b): - """ - not the same function as the original method - """ - return a + b - self.add5 = f - - def add5(self, b): - """ - The class copy of this should not get run, because it should - be overwritten on the server by the closure version. - """ - raise RuntimeError('dont run this one!') - - -class MockGates(MockInstTester): - - def __init__(self, name='gates', model=None, **kwargs): - super().__init__(name, model=model, delay=0.001, **kwargs) - - for i in range(3): - cmdbase = 'c{}'.format(i) - self.add_parameter('chan{}'.format(i), get_cmd=cmdbase + '?', - set_cmd=cmdbase + ':{:.4f}', - get_parser=float, - vals=Numbers(-10, 10)) - self.add_parameter('chan{}step'.format(i), - get_cmd=cmdbase + '?', - set_cmd=cmdbase + ':{:.4f}', - get_parser=float, - vals=Numbers(-10, 10), - step=0.1, delay=0.005) - - self.add_parameter('chan0slow', get_cmd='c0?', - set_cmd=self.slow_neg_set, get_parser=float, - vals=Numbers(-10, 10), step=0.2, - delay=0.02) - self.add_parameter('chan0slow2', get_cmd='c0?', - set_cmd=self.slow_neg_set, get_parser=float, - vals=Numbers(-10, 10), step=0.2, - delay=0.01, max_delay=0.02) - self.add_parameter('chan0slow3', get_cmd='c0?', - set_cmd=self.slow_neg_set, get_parser=float, - vals=Numbers(-10, 10), step=0.2, - delay=0.01, max_delay=0.08) - self.add_parameter('chan0slow4', get_cmd='c0?', - set_cmd=self.slow_neg_set, get_parser=float, - vals=Numbers(-10, 10), - delay=0.01, max_delay=0.02) - self.add_parameter('chan0slow5', get_cmd='c0?', - set_cmd=self.slow_neg_set, get_parser=float, - vals=Numbers(-10, 10), - delay=0.01, max_delay=0.08) - - self.add_function('reset', call_cmd='rst') - - self.add_parameter('foo', parameter_class=ParamNoDoc) - - def slow_neg_set(self, val): - if val < 0: - time.sleep(0.05) - self.chan0.set(val) - - -class MockSource(MockInstTester): - - def __init__(self, name='source', model=None, **kwargs): - super().__init__(name, model=model, delay=0.001, **kwargs) - - self.add_parameter('amplitude', get_cmd='ampl?', - set_cmd='ampl:{:.4f}', get_parser=float, - vals=Numbers(0, 1), - step=0.2, delay=0.005) - - -class MockMeter(MockInstTester): - - def __init__(self, name='meter', model=None, **kwargs): - super().__init__(name, model=model, delay=0.001, **kwargs) - - self.add_parameter('amplitude', get_cmd='ampl?', get_parser=float) - self.add_function('echo', call_cmd='echo {:.2f}?', - args=[Numbers(0, 1000)], return_parser=float) - - class MockParabola(Instrument): ''' Holds dummy parameters which are get and set able as well as provides @@ -250,6 +55,7 @@ class MockMetaParabola(Instrument): ''' Test for a meta instrument, has a tunable gain knob ''' + # TODO (giulioungaretti) remove unneded shared_kwargs shared_kwargs = ['mock_parabola_inst'] def __init__(self, name, mock_parabola_inst, **kw): @@ -297,7 +103,8 @@ def __init__(self, name='dummy', gates=['dac1', 'dac2', 'dac3'], **kwargs): self.add_parameter(g, parameter_class=ManualParameter, initial_value=0, - label='Gate {} (arb. units)'.format(g), + label='Gate {}'.format(g), + unit="V", vals=Numbers(-800, 400)) @@ -324,3 +131,31 @@ def __init__(self, **kwargs): def get(self): return self._return + + +class MultiSetPointParam(MultiParameter): + """ + Multiparameter which only purpose it to test that units, setpoints + and so on are copied correctly to the individual arrays in the datarray. + """ + def __init__(self): + name = 'testparameter' + shapes = ((5,), (5,)) + names = ('this', 'that') + labels = ('this label', 'that label') + units = ('this unit', 'that unit') + sp_base = tuple(np.linspace(5, 9, 5)) + setpoints = ((sp_base,), (sp_base,)) + setpoint_names = (('this_setpoint',), ('this_setpoint',)) + setpoint_labels = (('this setpoint',), ('this setpoint',)) + setpoint_units = (('this setpointunit',), ('this setpointunit',)) + super().__init__(name, names, shapes, + labels=labels, + units=units, + setpoints=setpoints, + setpoint_labels=setpoint_labels, + setpoint_names=setpoint_names, + setpoint_units=setpoint_units) + + def get(self): + return np.zeros(5), np.ones(5) diff --git a/qcodes/tests/test_data.py b/qcodes/tests/test_data.py index adfc19688e34..d000346f5b1b 100644 --- a/qcodes/tests/test_data.py +++ b/qcodes/tests/test_data.py @@ -1,21 +1,18 @@ from unittest import TestCase -from unittest.mock import patch import numpy as np import os import pickle import logging from qcodes.data.data_array import DataArray -from qcodes.data.manager import get_data_manager, NoData from qcodes.data.io import DiskIO -from qcodes.data.data_set import load_data, new_data, DataMode, DataSet -from qcodes.process.helpers import kill_processes +from qcodes.data.data_set import load_data, new_data, DataSet from qcodes.utils.helpers import LogCapture -from qcodes import active_children -from .data_mocks import (MockDataManager, MockFormatter, MatchIO, - MockLive, MockArray, DataSet2D, DataSet1D, +from .data_mocks import (MockFormatter, MatchIO, + DataSet2D, DataSet1D, DataSetCombined, RecordingMockFormatter) + from .common import strip_qc @@ -284,22 +281,6 @@ def test_fraction_complete(self): class TestLoadData(TestCase): - def setUp(self): - kill_processes() - - def test_no_live_data(self): - # live data with no DataManager at all - with self.assertRaises(RuntimeError): - load_data() - self.assertEqual(len(active_children()), 0) - - # now make a DataManager and try again - get_data_manager() - self.assertEqual(len(active_children()), 1) - # same result but different code path - with self.assertRaises(RuntimeError): - load_data() - def test_no_saved_data(self): with self.assertRaises(IOError): load_data('_no/such/file_') @@ -308,34 +289,8 @@ def test_load_false(self): with self.assertRaises(ValueError): load_data(False) - def test_get_live(self): - loc = 'live from New York!' - - class MockLive: - pass - - live_data = MockLive() - - dm = MockDataManager() - dm.location = loc - dm.live_data = live_data - - data = load_data(data_manager=dm, location=loc) - self.assertEqual(data, live_data) - - for nd in (None, NoData()): - dm.live_data = nd - with self.assertRaises(RuntimeError): - load_data(data_manager=dm, location=loc) - with self.assertRaises(RuntimeError): - load_data(data_manager=dm) - def test_get_read(self): - dm = MockDataManager() - dm.location = 'somewhere else' - - data = load_data(formatter=MockFormatter(), data_manager=dm, - location='here!') + data = load_data(formatter=MockFormatter(), location='here!') self.assertEqual(data.has_read_data, True) self.assertEqual(data.has_read_metadata, True) @@ -382,7 +337,6 @@ class TestNewData(TestCase): @classmethod def setUpClass(cls): - kill_processes() cls.original_lp = DataSet.location_provider @classmethod @@ -393,16 +347,11 @@ def test_overwrite(self): io = MatchIO([1]) with self.assertRaises(FileExistsError): - new_data(location='somewhere', io=io, data_manager=False) + new_data(location='somewhere', io=io) - data = new_data(location='somewhere', io=io, overwrite=True, - data_manager=False) + data = new_data(location='somewhere', io=io, overwrite=True,) self.assertEqual(data.location, 'somewhere') - def test_mode_error(self): - with self.assertRaises(ValueError): - new_data(mode=DataMode.PUSH_TO_SERVER, data_manager=False) - def test_location_functions(self): def my_location(io, record): return 'data/{}'.format((record or {}).get('name') or 'LOOP!') @@ -413,22 +362,17 @@ def my_location2(io, record): DataSet.location_provider = my_location - self.assertEqual(new_data(data_manager=False).location, 'data/LOOP!') - self.assertEqual(new_data(data_manager=False, name='cheese').location, - 'data/cheese') + self.assertEqual(new_data().location, 'data/LOOP!') + self.assertEqual(new_data(name='cheese').location, 'data/cheese') - data = new_data(data_manager=False, location=my_location2) + data = new_data(location=my_location2) self.assertEqual(data.location, 'data/loop?/folder') - data = new_data(data_manager=False, location=my_location2, - name='iceCream') + data = new_data(location=my_location2, name='iceCream') self.assertEqual(data.location, 'data/iceCream/folder') class TestDataSet(TestCase): - def tearDown(self): - kill_processes() - def test_constructor_errors(self): # no location - only allowed with load_data with self.assertRaises(ValueError): @@ -437,87 +381,6 @@ def test_constructor_errors(self): with self.assertRaises(ValueError): DataSet(location=42) - # OK to have location=False, but wrong mode - with self.assertRaises(ValueError): - DataSet(location=False, mode='happy') - - @patch('qcodes.data.data_set.get_data_manager') - def test_from_server(self, gdm_mock): - mock_dm = MockDataManager() - gdm_mock.return_value = mock_dm - mock_dm.location = 'Mars' - mock_dm.live_data = MockLive() - - # wrong location or False location - converts to local - data = DataSet(location='Jupiter', data_manager=True, mode=DataMode.PULL_FROM_SERVER) - self.assertEqual(data.mode, DataMode.LOCAL) - - data = DataSet(location=False, data_manager=True, mode=DataMode.PULL_FROM_SERVER) - self.assertEqual(data.mode, DataMode.LOCAL) - - # location matching server - stays in server mode - data = DataSet(location='Mars', data_manager=True, mode=DataMode.PULL_FROM_SERVER, - formatter=MockFormatter()) - self.assertEqual(data.mode, DataMode.PULL_FROM_SERVER) - self.assertEqual(data.arrays, MockLive.arrays) - - # cannot write except in LOCAL mode - with self.assertRaises(RuntimeError): - data.write() - - # cannot finalize in PULL_FROM_SERVER mode - with self.assertRaises(RuntimeError): - data.finalize() - - # now test when the server says it's not there anymore - mock_dm.location = 'Saturn' - data.sync() - self.assertEqual(data.mode, DataMode.LOCAL) - self.assertEqual(data.has_read_data, True) - - # now it's LOCAL so we *can* write. - data.write() - self.assertEqual(data.has_written_data, True) - - # location=False: write, read and sync are noops. - data.has_read_data = False - data.has_written_data = False - data.location = False - data.write() - data.read() - data.sync() - self.assertEqual(data.has_read_data, False) - self.assertEqual(data.has_written_data, False) - - @patch('qcodes.data.data_set.get_data_manager') - def test_to_server(self, gdm_mock): - mock_dm = MockDataManager() - mock_dm.needs_restart = True - gdm_mock.return_value = mock_dm - - data = DataSet(location='Venus', data_manager=True, mode=DataMode.PUSH_TO_SERVER) - self.assertEqual(mock_dm.needs_restart, False, data) - self.assertEqual(mock_dm.data_set, data) - self.assertEqual(data.data_manager, mock_dm) - self.assertEqual(data.mode, DataMode.PUSH_TO_SERVER) - - # cannot write except in LOCAL mode - with self.assertRaises(RuntimeError): - data.write() - - # now do what the DataServer does with this DataSet: init_on_server - # fails until there is an array - with self.assertRaises(RuntimeError): - data.init_on_server() - - data.add_array(MockArray()) - data.init_on_server() - self.assertEqual(data.noise.ready, True) - - # we can only add a given array_id once - with self.assertRaises(ValueError): - data.add_array(MockArray()) - def test_write_copy(self): data = DataSet1D(location=False) mockbase = os.path.abspath('some_folder') diff --git a/qcodes/tests/test_driver_testcase.py b/qcodes/tests/test_driver_testcase.py deleted file mode 100644 index b91b04411c5a..000000000000 --- a/qcodes/tests/test_driver_testcase.py +++ /dev/null @@ -1,58 +0,0 @@ -import unittest - -from qcodes.instrument_drivers.test import DriverTestCase -from qcodes.instrument.mock import MockInstrument, MockModel - - -class EmptyModel(MockModel): - pass - - -class MockMock(MockInstrument): - pass - - -@unittest.skip('just need this definition') -class HasNoDriver(DriverTestCase): - noskip = True - - -class MockMock2(MockInstrument): - pass - - -@unittest.skip('just need this definition') -class HasNoInstances(DriverTestCase): - noskip = True - driver = MockMock2 - - -class TestDriverTestCase(DriverTestCase): - driver = MockMock - noskip = True - - @classmethod - def setUpClass(cls): - cls.an_empty_model = EmptyModel() - cls.an_instrument = MockMock('a', model=cls.an_empty_model, server_name='') - super().setUpClass() - - @classmethod - def tearDownClass(cls): - cls.an_empty_model.close() - cls.an_instrument._manager.close() - - def test_instance_found(self): - self.assertEqual(self.instrument, self.an_instrument) - - def test_no_driver(self): - with self.assertRaises(TypeError): - HasNoDriver.setUpClass() - - def test_no_instances(self): - baseMock = MockInstrument('not the same class', - model=self.an_empty_model) - self.assertIn(baseMock, MockInstrument.instances()) - - with self.assertRaises(ValueError): - HasNoInstances.setUpClass() diff --git a/qcodes/tests/test_format.py b/qcodes/tests/test_format.py index d20b75ebd5f3..c9a635ae316b 100644 --- a/qcodes/tests/test_format.py +++ b/qcodes/tests/test_format.py @@ -333,8 +333,8 @@ def test_incremental_write(self): # we wrote to a second location without the stars, so we can read # back in and make sure that we get the right last_saved_index # for the amount of data we've read. - reread_data = load_data(location=location2, data_manager=False, - formatter=formatter, io=data.io) + reread_data = load_data(location=location2, formatter=formatter, + io=data.io) self.assertEqual(repr(reread_data.x_set.tolist()), repr(data.x_set.tolist())) self.assertEqual(repr(reread_data.y.tolist()), diff --git a/qcodes/tests/test_hdf5formatter.py b/qcodes/tests/test_hdf5formatter.py index d1506998f18d..ac813edc3cf9 100644 --- a/qcodes/tests/test_hdf5formatter.py +++ b/qcodes/tests/test_hdf5formatter.py @@ -136,8 +136,7 @@ def test_loop_writing(self): # # added to station to test snapshot at a later stage loop = Loop(MockPar.x[-100:100:20]).each(MockPar.skewed_parabola) data1 = loop.run(name='MockLoop_hdf5_test', - formatter=self.formatter, - background=False, data_manager=False) + formatter=self.formatter) data2 = DataSet(location=data1.location, formatter=self.formatter) data2.read() for key in data2.arrays.keys(): @@ -158,8 +157,7 @@ def test_loop_writing_2D(self): loop = Loop(MockPar.x[-100:100:20]).loop( MockPar.y[-50:50:10]).each(MockPar.skewed_parabola) data1 = loop.run(name='MockLoop_hdf5_test', - formatter=self.formatter, - background=False, data_manager=False) + formatter=self.formatter) data2 = DataSet(location=data1.location, formatter=self.formatter) data2.read() for key in data2.arrays.keys(): diff --git a/qcodes/tests/test_helpers.py b/qcodes/tests/test_helpers.py index 483792db060a..f55827d0c9c3 100644 --- a/qcodes/tests/test_helpers.py +++ b/qcodes/tests/test_helpers.py @@ -167,6 +167,10 @@ def test_bad_calls(self): permissive_range(*args) def test_good_calls(self): + # TODO(giulioungaretti) + # not sure what we are testing here. + # in pyhton 1.0 and 1 are actually the same + # https://docs.python.org/3.5/library/functions.html#hash good_args = { (1, 7, 2): [1, 3, 5], (1, 7, 4): [1, 5], diff --git a/qcodes/tests/test_instrument.py b/qcodes/tests/test_instrument.py index 434f0ae1148c..1af54e4197bb 100644 --- a/qcodes/tests/test_instrument.py +++ b/qcodes/tests/test_instrument.py @@ -1,993 +1,95 @@ """ Test suite for instument.* """ -from datetime import datetime, timedelta from unittest import TestCase -import time - from qcodes.instrument.base import Instrument -from qcodes.instrument.mock import MockInstrument +from .instrument_mocks import DummyInstrument, MockParabola from qcodes.instrument.parameter import ManualParameter -from qcodes.instrument.server import get_instrument_server_manager - -from qcodes.utils.validators import Numbers, Ints, Strings, MultiType, Enum -from qcodes.utils.command import NoCommandError -from qcodes.utils.helpers import LogCapture -from qcodes.process.helpers import kill_processes - -from .instrument_mocks import (AMockModel, MockInstTester, - MockGates, MockSource, MockMeter, - DummyInstrument) -from .common import strip_qc - - -class GatesBadDelayType(MockGates): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.add_parameter('chan0bad', get_cmd='c0?', - set_cmd=self.slow_neg_set, - get_parser=float, - vals=Numbers(-10, 10), step=0.2, - delay=0.01, - max_delay='forever') - - -class GatesBadDelayValue(MockGates): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.add_parameter('chan0bad', get_cmd='c0?', - set_cmd=self.slow_neg_set, - get_parser=float, - vals=Numbers(-10, 10), step=0.2, - delay=0.05, - max_delay=0.03) +import gc class TestInstrument(TestCase): - @classmethod - def setUpClass(cls): - cls.model = AMockModel() - - cls.gates = MockGates(model=cls.model, server_name='') - cls.source = MockSource(model=cls.model, server_name='') - cls.meter = MockMeter( - model=cls.model, keep_history=False, server_name='') - def setUp(self): - # reset the model state via the gates function - self.gates.reset() - - # then reset each instrument's state, so we can avoid the time to - # completely reinstantiate with every test case - for inst in (self.gates, self.source, self.meter): - inst.restart() - self.init_ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - @classmethod - def tearDownClass(cls): - try: - cls.model.close() - for instrument in [cls.gates, cls.source, cls.meter]: - instrument.close() - # do it twice - should not error, though the second is - # irrelevant - instrument.close() - except: - pass - - # TODO: when an error occurs during constructing an instrument, - # we don't have the instrument but its server doesn't know to stop. - # should figure out a way to remove it. (I thought I had but it - # doesn't seem to have worked...) - # for test_mock_instrument_errors - kill_processes() - - def test_unpicklable(self): - self.assertEqual(self.gates.add5(6), 11) - # compare docstrings to make sure we're really calling add5 - # on the server, and seeing its docstring - self.assertIn('The class copy of this should not get run', - MockInstTester.add5.__doc__) - self.assertIn('not the same function as the original method', - self.gates.add5.__doc__) - - def test_slow_set(self): - # at least for now, need a local instrument to test logging - gatesLocal = MockGates(model=self.model, server_name=None, - name='gateslocal') - for param, logcount in (('chan0slow', 2), ('chan0slow2', 2), - ('chan0slow3', 0), ('chan0slow4', 1), - ('chan0slow5', 0)): - gatesLocal.chan0.set(-0.5) - - with LogCapture() as logs: - if param in ('chan0slow', 'chan0slow2', 'chan0slow3'): - # these are the stepped parameters - gatesLocal.set(param, 0.5) - else: - # these are the non-stepped parameters that - # still have delays - gatesLocal.set(param, -1) - gatesLocal.set(param, 1) - - loglines = logs.value.split('\n')[:-1] - # TODO: occasional extra negative delays here - self.assertEqual(len(loglines), logcount, (param, logs.value)) - for line in loglines: - self.assertTrue(line.startswith('negative delay'), line) - - def test_max_delay_errors(self): - with self.assertRaises(TypeError): - # add_parameter works remotely with string commands, but - # function commands are not going to be picklable, since they - # need to talk to the hardware, so these need to be included - # from the beginning when the instrument is created on the - # server. - GatesBadDelayType(model=self.model, name='gatesBDT') - - with self.assertRaises(ValueError): - GatesBadDelayValue(model=self.model, name='gatesBDV') - - def check_ts(self, ts_str): - now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - self.assertTrue(self.init_ts <= ts_str <= now) - - def test_instances(self): - instruments = [self.gates, self.source, self.meter] - for instrument in instruments: - for other_instrument in instruments: - instances = instrument.instances() - # check that each instrument is in only its own - # instances list - # also test type checking in find_instrument, - # but we need to use find_component so it executes - # on the server - if other_instrument is instrument: - self.assertIn(instrument, instances) - - name2 = other_instrument.find_component( - instrument.name + '.name', - other_instrument._instrument_class) - self.assertEqual(name2, instrument.name) - else: - self.assertNotIn(other_instrument, instances) - - with self.assertRaises(TypeError): - other_instrument.find_component( - instrument.name + '.name', - other_instrument._instrument_class) - - # check that we can find each instrument from any other - # find_instrument is explicitly mapped in RemoteInstrument - # so this call gets executed in the main process - self.assertEqual( - instrument, - other_instrument.find_instrument(instrument.name)) - - # but find_component is not, so it executes on the server - self.assertEqual( - instrument.name, - other_instrument.find_component(instrument.name + '.name')) - - # check that we can find this instrument from the base class - self.assertEqual(instrument, - Instrument.find_instrument(instrument.name)) - - # somehow instances never go away... there are always 3 - # extra references to every instrument object, so del doesn't - # work. For this reason, instrument tests should take - # the *last* instance to test. - # so we can't test that the list of defined instruments is actually - # *only* what we want to see defined. - - def test_instance_name_uniqueness(self): - with self.assertRaises(KeyError): - MockGates(model=self.model) - - def test_remove_instance(self): - self.gates.close() - self.assertEqual(self.gates.instances(), []) - with self.assertRaises(KeyError): - Instrument.find_instrument('gates') - - type(self).gates = MockGates(model=self.model, server_name="") - self.assertEqual(self.gates.instances(), [self.gates]) - self.assertEqual(Instrument.find_instrument('gates'), self.gates) + self.instrument = DummyInstrument( + name='testdummy', gates=['dac1', 'dac2', 'dac3']) + self.instrument2 = MockParabola("parabola") - def test_creation_failure(self): - # this we already know should fail (see test_max_delay_errors) - name = 'gatesFailing' - with self.assertRaises(ValueError): - GatesBadDelayValue(model=self.model, name=name, server_name='') + def tearDown(self): + # force gc run + del self.instrument + del self.instrument2 + gc.collect() - # this instrument should not be in the instance list - with self.assertRaises(KeyError): - Instrument.find_instrument(name) + def test_validate_function(self): + instrument = self.instrument + instrument.validate_status() # test the instrument has valid values - # now do the same with a local instrument - name = 'gatesFailing2' - with self.assertRaises(ValueError): - GatesBadDelayValue(model=self.model, name=name, server_name=None) + instrument.dac1._save_val(1000) # overrule the validator + with self.assertRaises(Exception): + instrument.validate_status() - # this instrument should not be in the instance list + def test_check_instances(self): with self.assertRaises(KeyError): - Instrument.find_instrument(name) - - def test_mock_instrument(self): - gates, source, meter = self.gates, self.source, self.meter - - # initial state - # short form of getter - self.assertEqual(gates.get('chan0'), 0) - # shortcut to the parameter, longer form of get - self.assertEqual(gates['chan0'].get(), 0) - # explicit long form of getter - self.assertEqual(gates.parameters['chan0'].get(), 0) - # all 3 should produce the same history entry - hist = gates.getattr('history') - self.assertEqual(len(hist), 3) - for item in hist: - self.assertEqual(item[1:], ('ask', 'c0')) - - # errors trying to set (or validate) invalid param values - # put here so we ensure that these errors don't make it to - # the history (ie they don't result in hardware commands) - with self.assertRaises(TypeError): - gates.set('chan1', '1') - with self.assertRaises(TypeError): - gates.parameters['chan1'].validate('1') - - # change one param at a time - gates.set('chan0', 0.5) - self.assertEqual(gates.get('chan0'), 0.5) - self.assertEqual(meter.get('amplitude'), 0.05) - - gates.set('chan1', 2) - self.assertEqual(gates.get('chan1'), 2) - self.assertEqual(meter.get('amplitude'), 0.45) - - gates.set('chan2', -3.2) - self.assertEqual(gates.get('chan2'), -3.2) - self.assertEqual(meter.get('amplitude'), -2.827) - - source.set('amplitude', 0.6) - self.assertEqual(source.get('amplitude'), 0.6) - self.assertEqual(meter.get('amplitude'), -16.961) - - gatehist = gates.getattr('history') - sourcehist = source.getattr('history') - meterhist = meter.getattr('history') - # check just the size and timestamps of histories - for entry in gatehist + sourcehist + meterhist: - self.check_ts(entry[0]) - self.assertEqual(len(gatehist), 9) - self.assertEqual(len(sourcehist), 5) - # meter does not keep history but should still have a history attr - self.assertEqual(len(meterhist), 0) - - # plus enough setters to check the parameter sweep - # first source has to get the starting value - self.assertEqual(sourcehist[0][1:], ('ask', 'ampl')) - # then it writes each - self.assertEqual(sourcehist[1][1:], ('write', 'ampl', '0.3000')) - self.assertEqual(sourcehist[2][1:], ('write', 'ampl', '0.5000')) - self.assertEqual(sourcehist[3][1:], ('write', 'ampl', '0.6000')) - - source.set('amplitude', 0.8) - self.assertEqual(source.get('amplitude'), 0.8) - gates.set('chan1', -2) - self.assertEqual(gates.get('chan1'), -2) - - # test functions - self.assertEqual(meter.call('echo', 1.2345), 1.23) # model returns .2f - # too many ways to do this... - self.assertEqual(meter.echo.call(1.2345), 1.23) - self.assertEqual(meter.echo(1.2345), 1.23) - self.assertEqual(meter['echo'].call(1.2345), 1.23) - self.assertEqual(meter['echo'](1.2345), 1.23) - with self.assertRaises(TypeError): - meter.call('echo', 1, 2) - with self.assertRaises(TypeError): - meter.call('echo', '1') - - # validating before actually trying to call - with self.assertRaises(TypeError): - meter.functions['echo'].validate(1, 2) - with self.assertRaises(TypeError): - meter.functions['echo'].validate('1') - gates.call('reset') - self.assertEqual(gates.get('chan0'), 0) - - self.assertEqual(meter.call('echo', 4.567), 4.57) - gates.set('chan0', 1) - self.assertEqual(gates.get('chan0'), 1) - gates.call('reset') - self.assertEqual(gates.get('chan0'), 0) + DummyInstrument(name='testdummy', gates=['dac1', 'dac2', 'dac3']) - def test_mock_idn(self): - self.assertEqual(self.gates.IDN(), { - 'vendor': None, - 'model': 'MockGates', - 'serial': 'gates', - 'firmware': None - }) + self.assertEqual(Instrument.instances(), []) + self.assertEqual(DummyInstrument.instances(), [self.instrument]) + self.assertEqual(self.instrument.instances(), [self.instrument]) - def test_mock_set_sweep(self): - gates = self.gates - gates.set('chan0step', 0.5) - gatehist = gates.getattr('history') - self.assertEqual(len(gatehist), 6) - self.assertEqual( - [float(h[3]) for h in gatehist if h[1] == 'write'], - [0.1, 0.2, 0.3, 0.4, 0.5]) + def test_attr_access(self): + instrument = self.instrument - def test_mock_instrument_errors(self): - gates, meter = self.gates, self.meter - with self.assertRaises(ValueError): - gates.ask('no question') - with self.assertRaises(ValueError): - gates.ask('question?yes but more after') + # test the instrument works + instrument.dac1.set(10) + val = instrument.dac1.get() + self.assertEqual(val, 10) - with self.assertRaises(ValueError): - gates.ask('ampl?') + # close the instrument + instrument.close() - with self.assertRaises(TypeError): - MockInstrument('mockbaddelay1', delay='forever') - with self.assertRaises(TypeError): - # TODO: since this instrument didn't work, it should be OK - # to use the same name again... how do we allow that? - MockInstrument('mockbaddelay2', delay=-1) + # make sure we can still print the instrument + instrument.__repr__() - # TODO: when an error occurs during constructing an instrument, - # we don't have the instrument but its server doesn't know to stop. - # should figure out a way to remove it. (I thought I had but it - # doesn't seem to have worked...) - get_instrument_server_manager('MockInstruments').close() - time.sleep(0.5) + # make sure the gate is removed + self.assertEqual(hasattr(instrument, 'dac1'), False) - with self.assertRaises(AttributeError): - MockInstrument('', model=None) + def test_repr(self): + idn = dict(zip(('vendor', 'model', 'serial', 'firmware'), + [None, self.instrument.name, None, None])) + self.assertEqual(idn, self.instrument.get_idn()) + def test_add_remove_f_p(self): with self.assertRaises(KeyError): - gates.add_parameter('chan0', get_cmd='boo') + self.instrument.add_parameter('dac1', get_cmd='foo') + self.instrument.add_function('function', call_cmd='foo') with self.assertRaises(KeyError): - gates.add_function('reset', call_cmd='hoo') - - with self.assertRaises(NotImplementedError): - meter.set('amplitude', 0.5) - meter.add_parameter('gain', set_cmd='gain {:.3f}') - with self.assertRaises(NotImplementedError): - meter.get('gain') - - with self.assertRaises(TypeError): - gates.add_parameter('fugacity', set_cmd='f {:.4f}', vals=[1, 2, 3]) - - def check_set_amplitude2(self, val, log_count, history_count): - source = self.sourceLocal - with LogCapture() as logs: - source.amplitude2.set(val) - - loglines = logs.value.split('\n')[:-1] - - self.assertEqual(len(loglines), log_count, logs.value) - for line in loglines: - self.assertIn('cannot sweep', line.lower()) - hist = source.getattr('history') - self.assertEqual(len(hist), history_count) - - def test_sweep_steps_edge_case(self): - # MultiType with sweeping is weird - not sure why one would do this, - # but we should handle it - # at least for now, need a local instrument to check logging - source = self.sourceLocal = MockSource(model=self.model, - server_name=None, - name='sourcelocal') - source.add_parameter('amplitude2', get_cmd='ampl?', - set_cmd='ampl:{}', get_parser=float, - vals=MultiType(Numbers(0, 1), Strings()), - step=0.2, delay=0.02) - self.assertEqual(len(source.getattr('history')), 0) - - # 2 history items - get then set, and one warning (cannot sweep - # number to string value) - self.check_set_amplitude2('Off', log_count=1, history_count=2) - - # one more history item - single set, and one warning (cannot sweep - # string to number) - self.check_set_amplitude2(0.2, log_count=1, history_count=3) - - # the only real sweep (0.2 to 0.8) adds 3 set's to history and no logs - self.check_set_amplitude2(0.8, log_count=0, history_count=6) - - # single set added to history, and another sweep warning num->string - self.check_set_amplitude2('Off', log_count=1, history_count=7) - - def test_set_sweep_errors(self): - gates = self.gates - - # for reference, some add_parameter's that should work - gates.add_parameter('t0', set_cmd='{}', vals=Numbers(), - step=0.1, delay=0.01) - gates.add_parameter('t2', set_cmd='{}', vals=Ints(), - step=1, delay=0.01, - max_val_age=0) - - with self.assertRaises(TypeError): - # can't sweep non-numerics - gates.add_parameter('t1', set_cmd='{}', vals=Strings(), - step=1, delay=0.01) - with self.assertRaises(TypeError): - # need a numeric step too - gates.add_parameter('t1', set_cmd='{}', vals=Numbers(), - step='a skosh', delay=0.01) - with self.assertRaises(TypeError): - # Ints requires and int step - gates.add_parameter('t1', set_cmd='{}', vals=Ints(), - step=0.1, delay=0.01) - with self.assertRaises(ValueError): - # need a non-negative step - gates.add_parameter('t1', set_cmd='{}', vals=Numbers(), - step=-0.1, delay=0.01) - with self.assertRaises(TypeError): - # need a numeric delay - gates.add_parameter('t1', set_cmd='{}', vals=Numbers(), - step=0.1, delay='a tad') - with self.assertRaises(ValueError): - # need a non-negative delay - gates.add_parameter('t1', set_cmd='{}', vals=Numbers(), - step=0.1, delay=-0.01) - with self.assertRaises(TypeError): - # need a numeric max_val_age - gates.add_parameter('t1', set_cmd='{}', vals=Numbers(), - step=0.1, delay=0.01, - max_val_age='an hour') - with self.assertRaises(ValueError): - # need a non-negative max_val_age - gates.add_parameter('t1', set_cmd='{}', vals=Numbers(), - step=0.1, delay=0.01, - max_val_age=-1) - - def getmem(self, key): - return self.source.ask('mem{}?'.format(key)) - - def test_val_mapping(self): - gates = self.gates - - # memraw has no mappings - it just sets and gets what the instrument - # uses to encode this parameter - gates.add_parameter('memraw', set_cmd='mem0:{}', get_cmd='mem0?', - vals=Enum('zero', 'one')) - - # memcoded maps the instrument codes ('zero' and 'one') into nicer - # user values 0 and 1 - gates.add_parameter('memcoded', set_cmd='mem0:{}', get_cmd='mem0?', - val_mapping={0: 'zero', 1: 'one'}) - - gates.memcoded.set(0) - self.assertEqual(gates.memraw.get(), 'zero') - self.assertEqual(gates.memcoded.get(), 0) - self.assertEqual(self.getmem(0), 'zero') - - gates.memraw.set('one') - self.assertEqual(gates.memcoded.get(), 1) - self.assertEqual(gates.memraw.get(), 'one') - self.assertEqual(self.getmem(0), 'one') - - with self.assertRaises(ValueError): - gates.memraw.set(0) - - with self.assertRaises(ValueError): - gates.memcoded.set('zero') - - def test_val_mapping_ints(self): - gates = self.gates - - gates.add_parameter('moderaw', set_cmd='mem0:{}', get_cmd='mem0?', - vals=Enum('0', '1')) - - # modecoded maps the instrument codes ('0' and '1') into nicer - # user values 'AC' and 'DC' - # Here we're using integers in the mapping, rather than turning - # them into strings. - gates.add_parameter('modecoded', set_cmd='mem0:{}', get_cmd='mem0?', - val_mapping={'DC': 0, 'AC': 1}) - - gates.modecoded.set('AC') - self.assertEqual(gates.moderaw.get(), '1') - self.assertEqual(gates.modecoded.get(), 'AC') - self.assertEqual(self.getmem(0), '1') - - gates.moderaw.set('0') - self.assertEqual(gates.modecoded.get(), 'DC') - self.assertEqual(gates.moderaw.get(), '0') - self.assertEqual(self.getmem(0), '0') - - with self.assertRaises(ValueError): - gates.modecoded.set(0) - - with self.assertRaises(ValueError): - gates.modecoded.set('0') - - with self.assertRaises(ValueError): - gates.moderaw.set('DC') - - def test_val_mapping_parsers(self): - gates = self.gates - - gates.add_parameter('moderaw', set_cmd='mem0:{}', get_cmd='mem0?', - vals=Enum('0', '1')) - - with self.assertRaises(TypeError): - # set_parser is not allowed with val_mapping - gates.add_parameter('modecoded', set_cmd='mem0:{}', - get_cmd='mem0?', - val_mapping={'DC': 0, 'AC': 1}, - set_parser=float) + self.instrument.add_function('function', call_cmd='foo') - gates.add_parameter('modecoded', set_cmd='mem0:{:.0f}', - get_cmd='mem0?', - val_mapping={'DC': 0.0, 'AC': 1.0}, - get_parser=float) - - gates.modecoded.set('AC') - self.assertEqual(gates.moderaw.get(), '1') - self.assertEqual(gates.modecoded.get(), 'AC') - self.assertEqual(self.getmem(0), '1') - - gates.moderaw.set('0') - self.assertEqual(gates.modecoded.get(), 'DC') - self.assertEqual(gates.moderaw.get(), '0') - self.assertEqual(self.getmem(0), '0') - - with self.assertRaises(ValueError): - gates.modecoded.set(0) - - with self.assertRaises(ValueError): - gates.modecoded.set('0') - - def test_standard_snapshot(self): - self.maxDiff = None - snap = self.meter.snapshot() - strip_qc(snap) - for psnap in snap['parameters'].values(): - strip_qc(psnap) - - self.assertEqual(snap, { - '__class__': 'tests.instrument_mocks.MockMeter', - 'name': 'meter', - 'parameters': { - 'IDN': { - '__class__': ( - 'qcodes.instrument.parameter.StandardParameter'), - 'instrument': 'tests.instrument_mocks.MockMeter', - 'instrument_name': 'meter', - 'label': 'IDN', - 'name': 'IDN', - 'ts': None, - 'unit': '', - 'value': None, - 'vals': '' - }, - 'amplitude': { - '__class__': ( - 'qcodes.instrument.parameter.StandardParameter'), - 'instrument': 'tests.instrument_mocks.MockMeter', - 'instrument_name': 'meter', - 'label': 'amplitude', - 'name': 'amplitude', - 'ts': None, - 'unit': '', - 'value': None, - 'vals': '' - } - }, - 'functions': {'echo': {}} - }) - - ampsnap = self.meter.snapshot(update=True)['parameters']['amplitude'] - amp = self.meter.get('amplitude') - self.assertEqual(ampsnap['value'], amp) - amp_ts = datetime.strptime(ampsnap['ts'], '%Y-%m-%d %H:%M:%S') - self.assertLessEqual(amp_ts, datetime.now()) - self.assertGreater(amp_ts, datetime.now() - timedelta(seconds=1.1)) - - def test_manual_snapshot(self): - self.source.add_parameter('noise', parameter_class=ManualParameter) - noise = self.source.noise - - noisesnap = self.source.snapshot()['parameters']['noise'] - strip_qc(noisesnap) - self.assertEqual(noisesnap, { - '__class__': 'qcodes.instrument.parameter.ManualParameter', - 'instrument': 'tests.instrument_mocks.MockSource', - 'instrument_name': 'source', - 'label': 'noise', - 'name': 'noise', - 'ts': None, - 'unit': '', - 'value': None, - 'vals': '' - }) - - noise.set(100) - noisesnap = self.source.snapshot()['parameters']['noise'] - self.assertEqual(noisesnap['value'], 100) - - noise_ts = datetime.strptime(noisesnap['ts'], '%Y-%m-%d %H:%M:%S') - self.assertLessEqual(noise_ts, datetime.now()) - self.assertGreater(noise_ts, datetime.now() - timedelta(seconds=1.1)) - - def tests_get_latest(self): - self.source.add_parameter('noise', parameter_class=ManualParameter) - noise = self.source.noise - - self.assertIsNone(noise.get_latest()) - - noise.set(100) - - mock_ts = datetime(2000, 3, 4) - ts_str = mock_ts.strftime('%Y-%m-%d %H:%M:%S') - noise.setattr('_latest_ts', mock_ts) - self.assertEqual(noise.snapshot()['ts'], ts_str) - - self.assertEqual(noise.get_latest(), 100) - self.assertEqual(noise.get_latest.get(), 100) - - # get_latest should not update ts - self.assertEqual(noise.snapshot()['ts'], ts_str) - - # get_latest is not settable - with self.assertRaises(AttributeError): - noise.get_latest.set(50) - - def test_base_instrument_errors(self): - b = Instrument('silent', server_name=None) - - with self.assertRaises(NotImplementedError): - b.write('hello!') - with self.assertRaises(NotImplementedError): - b.ask('how are you?') - - with self.assertRaises(TypeError): - b.add_function('skip', call_cmd='skip {}', - args=['not a validator']) - with self.assertRaises(NoCommandError): - b.add_function('jump') - with self.assertRaises(NoCommandError): - b.add_parameter('height') - - def test_manual_parameter(self): - self.source.add_parameter('bias_resistor', - parameter_class=ManualParameter, - initial_value=1000) - res = self.source.bias_resistor - self.assertEqual(res.get(), 1000) - - res.set(1e9) - self.assertEqual(res.get(), 1e9) - # default vals is all numbers - # set / get with __call__ shortcut - res(-1) - self.assertEqual(res(), -1) - - self.source.add_parameter('alignment', - parameter_class=ManualParameter, - vals=Enum('lawful', 'neutral', 'chaotic')) - alignment = self.source.alignment - - # a ManualParameter can have initial_value=None (default) even if - # that's not a valid value to set later - self.assertIsNone(alignment.get()) - with self.assertRaises(ValueError): - alignment.set(None) - - alignment.set('lawful') - self.assertEqual(alignment.get(), 'lawful') - - # None is the only invalid initial_value you can use - with self.assertRaises(TypeError): - self.source.add_parameter('alignment2', - parameter_class=ManualParameter, - initial_value='nearsighted') - - def test_deferred_ops(self): - gates = self.gates - c0, c1, c2 = gates.chan0, gates.chan1, gates.chan2 - - c0.set(0) - c1.set(1) - c2.set(2) - - self.assertEqual((c0 + c1 + c2)(), 3) - self.assertEqual((10 + (c0**2) + (c1**2) + (c2**2))(), 15) - - d = c1.get_latest / c0.get_latest - with self.assertRaises(ZeroDivisionError): - d() - - def test_attr_access(self): - instrument = self.gates - - # set one attribute with nested levels - instrument.setattr('d1', {'a': {1: 2}}) - - # get the whole dict - self.assertEqual(instrument.getattr('d1'), {'a': {1: 2}}) - self.assertEqual(instrument.getattr('d1', 55), {'a': {1: 2}}) - - # get parts - self.assertEqual(instrument.getattr('d1["a"]'), {1: 2}) - self.assertEqual(instrument.getattr("d1['a'][1]"), 2) - self.assertEqual(instrument.getattr('d1["a"][1]', 3), 2) - - # add an attribute inside, then delete it again - instrument.setattr('d1["a"][2]', 23) - self.assertEqual(instrument.getattr('d1'), {'a': {1: 2, 2: 23}}) - instrument.delattr('d1["a"][2]') - self.assertEqual(instrument.getattr('d1'), {'a': {1: 2}}) - - # test restarting the InstrumentServer - this clears these attrs - instrument._manager.restart() - self.assertIsNone(instrument.getattr('d1', None)) - - def test_component_attr_access(self): - instrument = self.gates - method = instrument.add5 - parameter = instrument.chan1 - function = instrument.reset - - # RemoteMethod objects have no attributes besides __doc__, so test - # that this gets appropriately decorated - self.assertIn('RemoteMethod add5 in RemoteInstrument', method.__doc__) - # and also contains the remote doc - self.assertIn('not the same function as the original method', - method.__doc__) - - # unit is a remote attribute of parameters - # this one is initially blank - self.assertEqual(parameter.unit, '') - parameter.unit = 'Smoots' - self.assertEqual(parameter.unit, 'Smoots') - self.assertNotIn('unit', parameter.__dict__) - self.assertEqual(instrument.getattr(parameter.name + '.unit'), - 'Smoots') - # we can delete it remotely, and this is reflected in dir() - self.assertIn('unit', dir(parameter)) - del parameter.unit - self.assertNotIn('unit', dir(parameter)) - with self.assertRaises(AttributeError): - parameter.unit - - # and set it again, it's still remote. - parameter.unit = 'Furlongs per fortnight' - self.assertIn('unit', dir(parameter)) - self.assertEqual(parameter.unit, 'Furlongs per fortnight') - self.assertNotIn('unit', parameter.__dict__) - self.assertEqual(instrument.getattr(parameter.name + '.unit'), - 'Furlongs per fortnight') - # we get the correct result if someone else sets it on the server - instrument._write_server('setattr', parameter.name + '.unit', 'T') - self.assertEqual(parameter.unit, 'T') - self.assertEqual(parameter.getattr('unit'), 'T') - - # attributes not specified as remote are local - with self.assertRaises(AttributeError): - parameter.something - parameter.something = 42 - self.assertEqual(parameter.something, 42) - self.assertEqual(parameter.__dict__['something'], 42) - with self.assertRaises(AttributeError): - instrument.getattr(parameter.name + '.something') - with self.assertRaises(AttributeError): - # getattr method is only for remote attributes - parameter.getattr('something') - self.assertIn('something', dir(parameter)) - del parameter.something - self.assertNotIn('something', dir(parameter)) - with self.assertRaises(AttributeError): - parameter.something - - # call a remote method - self.assertEqual(set(parameter.callattr('get_attrs')), - parameter._attrs) - - # functions have remote attributes too - self.assertEqual(function._args, []) - self.assertNotIn('_args', function.__dict__) - function._args = 'args!' - self.assertEqual(function._args, 'args!') - - # a component with no docstring still gets the decoration - foo = instrument.foo - self.assertEqual(foo.__doc__, - 'RemoteParameter foo in RemoteInstrument gates') - - def test_update_components(self): - gates = self.gates - - gates.delattr('chan0.label') - gates.setattr('chan0.cheese', 'gorgonzola') - # we've altered the server copy, but not the RemoteParameter - self.assertIn('label', gates.chan0._attrs) - self.assertNotIn('cheese', gates.chan0._attrs) - # keep a reference to the original chan0 RemoteParameter to make sure - # it is still the same object later - chan0_original = gates.chan0 - - gates.update() - - self.assertIs(gates.chan0, chan0_original) - # now the RemoteParameter should have the updates - self.assertNotIn('label', gates.chan0._attrs) - self.assertIn('cheese', gates.chan0._attrs) - - def test_add_delete_components(self): - gates = self.gates - - # rather than call gates.add_parameter, which has a special proxy - # on the remote so it updates the components immediately, we'll call - # the server version directly - attr_list = gates.callattr('add_parameter', 'chan0X', get_cmd='c0?', - set_cmd='c0:{:.4f}', get_parser=float) - gates.delattr('parameters["chan0"]') - - # the RemoteInstrument does not have these changes yet - self.assertIn('chan0', gates.parameters) - self.assertNotIn('chan0X', gates.parameters) - - gates.update() - - # now the RemoteInstrument has the changes - self.assertNotIn('chan0', gates.parameters) - self.assertIn('chan0X', gates.parameters) - self.assertEqual(gates.chan0X._attrs, set(attr_list)) - - def test_reprs(self): - gates = self.gates - self.assertIn(gates.name, repr(gates)) - self.assertIn('chan1', repr(gates.chan1)) - self.assertIn('reset', repr(gates.reset)) - - def test_remote_sweep_values(self): - chan1 = self.gates.chan1 - - sv1 = chan1[1:4:1] - self.assertEqual(len(sv1), 3) - self.assertIn(2, sv1) - - sv2 = chan1.sweep(start=2, stop=3, num=6) - self.assertEqual(len(sv2), 6) - self.assertIn(2.2, sv2) - - def test_add_function(self): - gates = self.gates - # add a function remotely - gates.add_function('reset2', call_cmd='rst') - gates.chan1(4) - self.assertEqual(gates.chan1(), 4) - gates.reset2() - self.assertEqual(gates.chan1(), 0) - - -class TestLocalMock(TestCase): - - @classmethod - def setUpClass(cls): - cls.model = AMockModel() - - cls.gates = MockGates(model=cls.model, server_name=None) - cls.source = MockSource(model=cls.model, server_name=None) - cls.meter = MockMeter(model=cls.model, server_name=None) - - @classmethod - def tearDownClass(cls): - cls.model.close() - for instrument in [cls.gates, cls.source, cls.meter]: - instrument.close() - - def test_local(self): - self.gates.chan1.set(3.33) - self.assertEqual(self.gates.chan1.get(), 3.33) - - self.gates.reset() - self.assertEqual(self.gates.chan1.get(), 0) - - with self.assertRaises(ValueError): - self.gates.ask('knock knock? Oh never mind.') + self.instrument.add_function('dac1', call_cmd='foo') + # test custom __get_attr__ + self.instrument['function'] + # by desgin one gets the parameter if a function exists and has same + # name + dac1 = self.instrument['dac1'] + self.assertTrue(isinstance(dac1, ManualParameter)) def test_instances(self): - # copied from the main (server-based) version - # make sure it all works the same here - instruments = [self.gates, self.source, self.meter] + instruments = [self.instrument, self.instrument2] for instrument in instruments: for other_instrument in instruments: instances = instrument.instances() # check that each instrument is in only its own - # instances list if other_instrument is instrument: self.assertIn(instrument, instances) else: self.assertNotIn(other_instrument, instances) # check that we can find each instrument from any other - # use find_component here to test that it rolls over to - # find_instrument if only a name is given self.assertEqual( instrument, - other_instrument.find_component(instrument.name)) - - self.assertEqual( - instrument.name, - other_instrument.find_component(instrument.name + '.name')) + other_instrument.find_instrument(instrument.name)) # check that we can find this instrument from the base class self.assertEqual(instrument, Instrument.find_instrument(instrument.name)) - - -class TestModelAttrAccess(TestCase): - - def setUp(self): - self.model = AMockModel() - - def tearDown(self): - self.model.close() - - def test_attr_access(self): - model = self.model - - model.a = 'local' - with self.assertRaises(AttributeError): - model.getattr('a') - - self.assertEqual(model.getattr('a', 'dflt'), 'dflt') - - model.setattr('a', 'remote') - self.assertEqual(model.a, 'local') - self.assertEqual(model.getattr('a'), 'remote') - - model.delattr('a') - self.assertEqual(model.getattr('a', 'dflt'), 'dflt') - - model.fmt = 'local override of a remote method' - self.assertEqual(model.callattr('fmt', 42), '42.000') - self.assertEqual(model.callattr('fmt', value=12.4), '12.400') - - -class TestInstrument2(TestCase): - - def setUp(self): - self.instrument = DummyInstrument( - name='testdummy', gates=['dac1', 'dac2', 'dac3'], server_name=None) - - def tearDown(self): - # TODO (giulioungaretti) remove ( does nothing ?) - pass - - def test_validate_function(self): - instrument = self.instrument - instrument.validate_status() # test the instrument has valid values - - instrument.dac1._save_val(1000) # overrule the validator - with self.assertRaises(Exception): - instrument.validate_status() - - def test_attr_access(self): - instrument = self.instrument - - # test the instrument works - instrument.dac1.set(10) - val = instrument.dac1.get() - self.assertEqual(val, 10) - - # close the instrument - instrument.close() - - # make sure we can still print the instrument - _ = instrument.__repr__() - - # make sure the gate is removed - self.assertEqual(hasattr(instrument, 'dac1'), False) diff --git a/qcodes/tests/test_instrument_server.py b/qcodes/tests/test_instrument_server.py deleted file mode 100644 index 1284e03d0b70..000000000000 --- a/qcodes/tests/test_instrument_server.py +++ /dev/null @@ -1,157 +0,0 @@ -from unittest import TestCase -import time -import multiprocessing as mp - -from qcodes.instrument.server import InstrumentServer -from qcodes.instrument.base import Instrument -from qcodes.process.server import (QUERY_WRITE, QUERY_ASK, RESPONSE_OK, - RESPONSE_ERROR) -from qcodes.utils.helpers import LogCapture - - -def schedule(queries, query_queue): - """ - Args: - queries: is a sequence of (delay, args) - query_queue: is a queue to push these queries to, with each one waiting - its delay after sending the previous one - """ - for delay, args in queries: - time.sleep(delay) - query_queue.put(args) - - -def run_schedule(queries, query_queue): - p = mp.Process(target=schedule, args=(queries, query_queue)) - p.start() - return p - - -def get_results(response_queue): - time.sleep(0.05) # wait for any lingering messages to the queues - responses = [] - while not response_queue.empty(): - responses.append(response_queue.get()) - - return responses - - -class Holder: - shared_kwargs = ['where'] - name = 'J Edgar' - parameters = {} - functions = {} - - def __init__(self, server_name=None, **kwargs): - self.kwargs = kwargs - self.d = {} - - def get(self, key): - return self.d[key] - - def set(self, key, val): - self.d[key] = val - - def get_extras(self): - return self.kwargs - - def _get_method_attrs(self): - return {} - - def close(self): - pass - - def connection_attrs(self, new_id): - return Instrument.connection_attrs(self, new_id) - - -class TimedInstrumentServer(InstrumentServer): - timeout = 2 - - -class TestInstrumentServer(TestCase): - maxDiff = None - - @classmethod - def setUpClass(cls): - cls.query_queue = mp.Queue() - cls.response_queue = mp.Queue() - cls.error_queue = mp.Queue() - - @classmethod - def tearDownClass(cls): - del cls.query_queue - del cls.response_queue - del cls.error_queue - - def test_normal(self): - # we really only need to test local here - as a server it's already - # used in other tests, but only implicitly (and not covered as it's - # in a subprocess) - queries = ( - # add an "instrument" to the server - (0.5, (QUERY_ASK, 'new_id',)), - (0.01, (QUERY_ASK, 'new', (Holder, 0))), - - # some sets and gets that work - (0.01, (QUERY_WRITE, 'cmd', - (0, 'set', 'happiness', 'a warm gun'), {})), - (0.01, (QUERY_WRITE, 'cmd', - (0, 'set'), {'val': 42, 'key': 'the answer'})), - (0.01, (QUERY_ASK, 'cmd', (0, 'get'), {'key': 'happiness'})), - (0.01, (QUERY_ASK, 'cmd', (0, 'get', 'the answer',), {})), - - # then some that make errors - # KeyError - (0.01, (QUERY_ASK, 'cmd', (0, 'get', 'Carmen Sandiego',), {})), - # TypeError (too many args) shows up in logs - (0.01, (QUERY_WRITE, 'cmd', (0, 'set', 1, 2, 3), {})), - # TypeError (unexpected kwarg) shows up in logs - (0.01, (QUERY_WRITE, 'cmd', (0, 'set', 'do'), {'c': 'middle'})), - - # and another good one, just so we know it still works - (0.01, (QUERY_ASK, 'cmd', (0, 'get_extras'), {})), - - # delete the instrument and stop the server - # (no need to explicitly halt) - (0.01, (QUERY_ASK, 'delete', (0,))) - ) - extras = {'where': 'infinity and beyond'} - - run_schedule(queries, self.query_queue) - - try: - with LogCapture() as logs: - TimedInstrumentServer(self.query_queue, self.response_queue, - extras) - except TypeError: - from traceback import format_exc - print(format_exc()) - - self.assertEqual(logs.value.count('TypeError'), 2) - for item in ('1, 2, 3', 'middle'): - self.assertIn(item, logs.value) - - responses = get_results(self.response_queue) - - expected_responses = [ - (RESPONSE_OK, 0), - (RESPONSE_OK, { - 'functions': {}, - 'id': 0, - 'name': 'J Edgar', - '_methods': {}, - 'parameters': {} - }), - (RESPONSE_OK, 'a warm gun'), - (RESPONSE_OK, 42), - (RESPONSE_ERROR, ('KeyError', 'Carmen Sandiego')), - (RESPONSE_OK, extras) - ] - for response, expected in zip(responses, expected_responses): - if expected[0] == RESPONSE_OK: - self.assertEqual(response, expected) - else: - self.assertEqual(response[0], expected[0]) - for item in expected[1]: - self.assertIn(item, response[1]) diff --git a/qcodes/tests/test_loop.py b/qcodes/tests/test_loop.py index b05276ba4ef1..6409e67e1cff 100644 --- a/qcodes/tests/test_loop.py +++ b/qcodes/tests/test_loop.py @@ -1,274 +1,18 @@ from datetime import datetime -import logging -import multiprocessing as mp -import numpy as np import time from unittest import TestCase +import numpy as np from unittest.mock import patch -from qcodes.loops import (Loop, MP_NAME, get_bg, halt_bg, ActiveLoop, - _DebugInterrupt) -from qcodes.actions import Task, Wait, BreakIf +from qcodes.loops import Loop +from qcodes.actions import Task, Wait, BreakIf, _QcodesBreak from qcodes.station import Station -from qcodes.data.io import DiskIO from qcodes.data.data_array import DataArray -from qcodes.data.manager import get_data_manager -from qcodes.instrument.mock import ArrayGetter -from qcodes.instrument.parameter import Parameter, ManualParameter -from qcodes.process.helpers import kill_processes -from qcodes.process.qcodes_process import QcodesProcess +from qcodes.instrument.parameter import ManualParameter from qcodes.utils.validators import Numbers from qcodes.utils.helpers import LogCapture -from .instrument_mocks import (AMockModel, MockGates, MockSource, MockMeter, - MultiGetter) - - -class TestMockInstLoop(TestCase): - def setUp(self): - get_data_manager().restart(force=True) - kill_processes() - # TODO: figure out what's leaving DataManager in a weird state - # and fix it - get_data_manager().restart(force=True) - time.sleep(0.1) - - self.model = AMockModel() - - self.gates = MockGates(model=self.model, server_name='') - self.source = MockSource(model=self.model, server_name='') - self.meter = MockMeter(model=self.model, server_name='') - self.location = '_loop_test_' - self.location2 = '_loop_test2_' - self.io = DiskIO('.') - - c1 = self.gates.chan1 - self.loop = Loop(c1[1:5:1], 0.001).each(c1) - self.loop_progress = Loop(c1[1:5:1], 0.001, - progress_interval=1).each(c1) - - self.assertFalse(self.io.list(self.location)) - self.assertFalse(self.io.list(self.location2)) - - def tearDown(self): - for instrument in [self.gates, self.source, self.meter]: - instrument.close() - - get_data_manager().close() - self.model.close() - - self.io.remove_all(self.location) - self.io.remove_all(self.location2) - - def check_empty_data(self, data): - expected = repr([float('nan')] * 4) - self.assertEqual(repr(data.gates_chan1.tolist()), expected) - self.assertEqual(repr(data.gates_chan1_set.tolist()), expected) - - def check_loop_data(self, data): - self.assertEqual(data.gates_chan1.tolist(), [1, 2, 3, 4]) - self.assertEqual(data.gates_chan1_set.tolist(), [1, 2, 3, 4]) - - self.assertTrue(self.io.list(self.location)) - - def test_background_and_datamanager(self): - # make sure that an unpicklable instrument can indeed run in a loop - # because the instrument itself is in a server - - # TODO: if we don't save the dataset (location=False) then we can't - # sync it when we're done. Should fix that - for now that just means - # you can only do in-memory loops if you set data_manager=False - # TODO: this is the one place we don't do quiet=True - test that we - # really print stuff? - data = self.loop.run(location=self.location, background=True, data_manager=True) - self.check_empty_data(data) - - # wait for process to finish (ensures that this was run in the bg, - # because otherwise there *is* no loop.process) - self.loop.process.join() - - data.sync() - self.check_loop_data(data) - - def test_local_instrument(self): - # a local instrument should work in a foreground loop, but - # not in a background loop (should give a RuntimeError) - self.gates.close() # so we don't have two gates with same name - gates_local = MockGates(model=self.model, server_name=None) - self.gates = gates_local - c1 = gates_local.chan1 - loop_local = Loop(c1[1:5:1], 0.001).each(c1) - - # if spawn, pickle will happen - if mp.get_start_method() == "spawn": - with self.assertRaises(RuntimeError): - loop_local.run(location=self.location, - quiet=True, - background=True) - # allow for *nix - # TODO(giulioungaretti) see what happens ? - # what is the expected beavhiour ? - # The RunimError will never be raised here, as the forkmethod - # won't try to pickle anything at all. - else: - logging.error("this should not be allowed, but for now we let it be") - loop_local.run(location=self.location, quiet=True) - - data = loop_local.run(location=self.location2, background=False, - quiet=True) - self.check_loop_data(data) - - def test_background_no_datamanager(self): - # We don't support syncing data from a background process - # if not using a datamanager. See warning in ActiveLoop.run() - # So we expect the data to be empty even after running. - data = self.loop.run(location=self.location, - background=True, - data_manager=False, - quiet=True) - self.check_empty_data(data) - - self.loop.process.join() - - data.sync() - self.check_empty_data(data) - - def test_foreground_and_datamanager(self): - data = self.loop.run(location=self.location, background=False, - quiet=True) - self.assertFalse(hasattr(self.loop, 'process')) - - self.check_loop_data(data) - - def test_foreground_no_datamanager_progress(self): - data = self.loop_progress.run(location=self.location, background=False, - data_manager=False, quiet=True) - self.assertFalse(hasattr(self.loop, 'process')) - - self.check_loop_data(data) - - @patch('qcodes.loops.tprint') - def test_progress_calls(self, tprint_mock): - data = self.loop_progress.run(location=self.location, background=False, - data_manager=False, quiet=True) - self.assertFalse(hasattr(self.loop, 'process')) - - self.check_loop_data(data) - expected_calls = len(self.loop_progress.sweep_values) + 1 - self.assertEqual(tprint_mock.call_count, expected_calls) - - # now run again with no progress interval and check that we get no - # additional calls - data = self.loop_progress.run(location=False, background=False, - data_manager=False, quiet=True, - progress_interval=None) - self.assertFalse(hasattr(self.loop, 'process')) - - self.check_loop_data(data) - self.assertEqual(tprint_mock.call_count, expected_calls) - - def test_foreground_no_datamanager(self): - data = self.loop.run(location=self.location, background=False, - data_manager=False, quiet=True) - self.assertFalse(hasattr(self.loop, 'process')) - - self.check_loop_data(data) - - def test_enqueue(self): - c1 = self.gates.chan1 - loop = Loop(c1[1:5:1], 0.01).each(c1) - data1 = loop.run(location=self.location, - quiet=True, - background=True, - data_manager=True) - - # second running of the loop should be enqueued, blocks until - # the first one finishes. - # TODO: check what it prints? - data2 = loop.run(location=self.location2, - quiet=True, - background=True, - data_manager=True) - - data1.sync() - data2.sync() - self.assertEqual(data1.gates_chan1.tolist(), [1, 2, 3, 4]) - for v in data2.gates_chan1: - self.assertTrue(np.isnan(v)) - - loop.process.join() - data2.sync() - self.assertEqual(data2.gates_chan1.tolist(), [1, 2, 3, 4]) - - # and while we're here, check that running a loop in the - # foreground *after* the background clears its .process - self.assertTrue(hasattr(loop, 'process')) - loop.run_temp() - self.assertFalse(hasattr(loop, 'process')) - - def test_sync_no_overwrite(self): - # Test fix for 380, this tests that the setpoints are not incorrectly - # overwritten by data_set.sync() for this to happen with the original code - # the delay must be larger than the write period otherwise sync is a no opt. - - loop = Loop(self.gates.chan1.sweep(0, 1, 1), delay=0.1).each(ArrayGetter(self.meter.amplitude, - self.gates.chan2[0:1:1], 0.000001)) - data = loop.get_data_set(name='testsweep', write_period=0.01) - _ = loop.with_bg_task(data.sync).run() - assert not np.isnan(data.chan2).any() - -def sleeper(t): - time.sleep(t) - - -class TestBG(TestCase): - def test_get_halt(self): - kill_processes() - self.assertIsNone(get_bg()) - - p1 = QcodesProcess(name=MP_NAME, target=sleeper, args=(10, )) - p1.start() - p2 = QcodesProcess(name=MP_NAME, target=sleeper, args=(10, )) - p2.start() - p1.signal_queue = p2.signal_queue = mp.Queue() - qcodes_processes = [p for p in mp.active_children() - if isinstance(p, QcodesProcess)] - self.assertEqual(len(qcodes_processes), 2, mp.active_children()) - - with self.assertRaises(RuntimeError): - get_bg() - bg1 = get_bg(return_first=True) - self.assertIn(bg1, [p1, p2]) - - halt_bg(timeout=0.05) - bg2 = get_bg() - self.assertIn(bg2, [p1, p2]) - # is this robust? requires that active_children always returns the same - # order, even if it's not the order you started processes in - self.assertNotEqual(bg1, bg2) - - self.assertEqual(len(mp.active_children()), 1) - - halt_bg(timeout=0.05) - self.assertIsNone(get_bg()) - - self.assertEqual(len(mp.active_children()), 0) - - # TODO - test that we print "no loops running"? - # at least this shows that it won't raise an error - halt_bg() - - -class FakeMonitor: - ''' - when attached to an ActiveLoop as _monitor, records how long - the monitor was given to measure - ''' - def __init__(self, delay_array): - self.delay_array = delay_array - - def call(self, finish_by=None): - self.delay_array.append(finish_by - time.perf_counter()) +from .instrument_mocks import MultiGetter class TestLoop(TestCase): @@ -279,9 +23,6 @@ def setUpClass(cls): cls.p3 = ManualParameter('p3', vals=Numbers(-10, 10)) Station().set_measurement(cls.p2, cls.p3) - def setUp(self): - kill_processes() - def test_nesting(self): loop = Loop(self.p1[1:3:1], 0.001).loop( self.p2[3:5:1], 0.001).loop( @@ -342,7 +83,6 @@ def test_repr(self): active_loop = loop data = active_loop.run_temp() expected = ('DataSet:\n' - ' mode = DataMode.LOCAL\n' ' location = False\n' ' | | | \n' ' Setpoint | p1_set | p1 | (2,)\n' @@ -391,36 +131,6 @@ def test_func(*args, **kwargs): self.assertEqual(data.p2.tolist(), [2]) - def test_tasks_waits(self): - delay0 = 0.01 - delay1 = 0.03 - loop = Loop(self.p1[1:3:1], delay0).each( - Task(self.p2.set, -1), - Wait(delay1), - self.p2, - Task(self.p2.set, 1), - self.p2) - delay_array = [] - loop._monitor = FakeMonitor(delay_array) - - # give it a "process" as if it was run in the bg before, - # check that this gets cleared - loop.process = 'TDD' - - data = loop.run_temp() - - self.assertFalse(hasattr(loop, 'process')) - - self.assertEqual(data.p1_set.tolist(), [1, 2]) - self.assertEqual(data.p2_2.tolist(), [-1, -1]) - self.assertEqual(data.p2_4.tolist(), [1, 1]) - - self.assertEqual(len(delay_array), 4) - for i, delay in enumerate(delay_array): - target = delay1 if i % 2 else delay0 - self.assertLessEqual(delay, target) - self.assertGreater(delay, target - 0.001) - @patch('time.sleep') def test_delay0(self, sleep_mock): self.p2.set(3) @@ -467,7 +177,7 @@ def test_composite_params(self): self.assertEqual(data.p1_set.tolist(), [1, 2]) self.assertEqual(data.one.tolist(), [1, 1]) self.assertEqual(data.onetwo.tolist(), [[1, 2]] * 2) - self.assertEqual(data.index0.tolist(), [[0, 1]] * 2) + self.assertEqual(data.index0_set.tolist(), [[0, 1]] * 2) # give it setpoints, names, and labels mg.setpoints = (None, ((10, 11),)) @@ -478,8 +188,8 @@ def test_composite_params(self): data = loop.run_temp() - self.assertEqual(data.highest.tolist(), [[10, 11]] * 2) - self.assertEqual(data.highest.label, sp_label) + self.assertEqual(data.highest_set.tolist(), [[10, 11]] * 2) + self.assertEqual(data.highest_set.label, sp_label) # setpoints as DataArray - name and label here override # setpoint_names and setpoint_labels attributes @@ -490,8 +200,8 @@ def test_composite_params(self): mg.setpoints = (None, (sp_dataarray,)) data = loop.run_temp() - self.assertEqual(data.bgn.tolist(), [[6, 7]] * 2) - self.assertEqual(data.bgn.label, new_sp_label) + self.assertEqual(data.bgn_set.tolist(), [[6, 7]] * 2) + self.assertEqual(data.bgn_set.label, new_sp_label) # muck things up and test for errors mg.setpoints = (None, ((1, 2, 3),)) @@ -522,7 +232,7 @@ def test_composite_params(self): self.assertEqual(data.p1_set.tolist(), [1, 2]) self.assertEqual(data.arr.tolist(), [[4, 5, 6]] * 2) - self.assertEqual(data.index0.tolist(), [[0, 1, 2]] * 2) + self.assertEqual(data.index0_set.tolist(), [[0, 1, 2]] * 2) mg = MultiGetter(arr2d=((21, 22), (23, 24))) loop = Loop(self.p1[1:3:1], 0.001).each(mg) @@ -530,8 +240,8 @@ def test_composite_params(self): self.assertEqual(data.p1_set.tolist(), [1, 2]) self.assertEqual(data.arr2d.tolist(), [[[21, 22], [23, 24]]] * 2) - self.assertEqual(data.index0.tolist(), [[0, 1]] * 2) - self.assertEqual(data.index1.tolist(), [[[0, 1]] * 2] * 2) + self.assertEqual(data.index0_set.tolist(), [[0, 1]] * 2) + self.assertEqual(data.index1_set.tolist(), [[[0, 1]] * 2] * 2) def test_bad_actors(self): def f(): @@ -690,9 +400,7 @@ def g(): 'default_measurement': [p2snap, p3snap] }, 'loop': { - 'background': False, 'use_threads': False, - 'use_data_manager': False, '__class__': 'qcodes.loops.ActiveLoop', 'sweep_values': { 'parameter': p1snap, @@ -741,70 +449,39 @@ def g(): class AbortingGetter(ManualParameter): - ''' - A manual parameter that can only be measured a couple of times + """ + A manual parameter that can only be measured n times before it aborts the loop that's measuring it. - - You have to attach the queue after construction with set_queue - so you can grab it from the loop that uses the parameter. - ''' + """ def __init__(self, *args, count=1, msg=None, **kwargs): self._count = self._initial_count = count - self.msg = msg # also need a _signal_queue, but that has to be added later super().__init__(*args, **kwargs) def get(self): self._count -= 1 if self._count <= 0: - self._signal_queue.put(self.msg) + raise _QcodesBreak return super().get() - def set_queue(self, queue): - self._signal_queue = queue - def reset(self): self._count = self._initial_count -class TestSignal(TestCase): +class Test_halt(TestCase): def test_halt(self): - p1 = AbortingGetter('p1', count=2, vals=Numbers(-10, 10), - msg=ActiveLoop.HALT_DEBUG) - loop = Loop(p1[1:6:1], 0.005).each(p1) + abort_after = 3 + self.res = list(np.arange(0, abort_after-1, 1.)) + [self.res.append(float('nan')) for i in range(0, abort_after-1)] + + p1 = AbortingGetter('p1', count=abort_after, vals=Numbers(-10, 10)) + loop = Loop(p1.sweep(0, abort_after, 1), 0.005).each(p1) # we want to test what's in data, so get it ahead of time # because loop.run will not return. data = loop.get_data_set(location=False) - p1.set_queue(loop.signal_queue) - - with self.assertRaises(_DebugInterrupt): - # need to use explicit loop.run rather than run_temp - # so we can avoid providing location=False twice, which - # is an error. - loop.run(background=False, data_manager=False, quiet=True) - - self.check_data(data) - def test_halt_quiet(self): - p1 = AbortingGetter('p1', count=2, vals=Numbers(-10, 10), - msg=ActiveLoop.HALT) - loop = Loop(p1[1:6:1], 0.005).each(p1) - p1.set_queue(loop.signal_queue) - - # does not raise, just quits, but the data set looks the same - # as in test_halt - data = loop.run_temp() - self.check_data(data) - - def check_data(self, data): - nan = float('nan') - self.assertEqual(data.p1.tolist()[:2], [1, 2]) - # when NaN is involved, I'll just compare reprs, because NaN!=NaN - self.assertEqual(repr(data.p1.tolist()[-2:]), repr([nan, nan])) - # because of the way the waits work out, we can get an extra - # point measured before the interrupt is registered. But the - # test would be valid either way. - self.assertIn(repr(data.p1[2]), (repr(nan), repr(3), repr(3.0))) + loop.run(quiet=True) + self.assertEqual(repr(data.p1.tolist()), repr(self.res)) class TestMetaData(TestCase): diff --git a/qcodes/tests/test_measure.py b/qcodes/tests/test_measure.py index e72f3880167e..28e8953887b1 100644 --- a/qcodes/tests/test_measure.py +++ b/qcodes/tests/test_measure.py @@ -4,8 +4,10 @@ from qcodes.instrument.parameter import ManualParameter from qcodes.measure import Measure -from .instrument_mocks import MultiGetter +from .instrument_mocks import MultiGetter, MultiSetPointParam +import numpy as np +from numpy.testing import assert_array_equal class TestMeasure(TestCase): def setUp(self): @@ -23,8 +25,6 @@ def test_simple_scalar(self): meta = data.metadata['measurement'] self.assertEqual(meta['__class__'], 'qcodes.measure.Measure') self.assertEqual(len(meta['actions']), 1) - self.assertFalse(meta['background']) - self.assertFalse(meta['use_data_manager']) self.assertFalse(meta['use_threads']) ts_start = datetime.strptime(meta['ts_start'], '%Y-%m-%d %H:%M:%S') @@ -34,7 +34,7 @@ def test_simple_scalar(self): def test_simple_array(self): data = Measure(MultiGetter(arr=(1.2, 3.4))).run_temp() - self.assertEqual(data.index0.tolist(), [0, 1]) + self.assertEqual(data.index0_set.tolist(), [0, 1]) self.assertEqual(data.arr.tolist(), [1.2, 3.4]) self.assertEqual(len(data.arrays), 2, data.arrays) @@ -44,6 +44,35 @@ def test_array_and_scalar(self): self.assertEqual(data.single_set.tolist(), [0]) self.assertEqual(data.P1.tolist(), [42]) - self.assertEqual(data.index0.tolist(), [0, 1]) + self.assertEqual(data.index0_set.tolist(), [0, 1]) self.assertEqual(data.arr.tolist(), [5, 6]) self.assertEqual(len(data.arrays), 4, data.arrays) + + +class TestMeasureMulitParameter(TestCase): + def setUp(self): + self.p1 = MultiSetPointParam() + + + def test_metadata(self): + c = Measure(self.p1).run() + self.assertEqual(c.metadata['arrays']['this']['unit'], 'this unit') + self.assertEqual(c.metadata['arrays']['this']['name'], 'this') + self.assertEqual(c.metadata['arrays']['this']['label'], 'this label') + self.assertEqual(c.metadata['arrays']['this']['is_setpoint'], False) + self.assertEqual(c.metadata['arrays']['this']['shape'], (5,)) + assert_array_equal(c.this.ndarray, np.zeros(5)) + + self.assertEqual(c.metadata['arrays']['that']['unit'],'that unit') + self.assertEqual(c.metadata['arrays']['that']['name'], 'that') + self.assertEqual(c.metadata['arrays']['that']['label'], 'that label') + self.assertEqual(c.metadata['arrays']['that']['is_setpoint'], False) + self.assertEqual(c.metadata['arrays']['that']['shape'], (5,)) + assert_array_equal(c.that.ndarray, np.ones(5)) + + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['unit'], 'this setpointunit') + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['name'], 'this_setpoint') + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['label'], 'this setpoint') + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['is_setpoint'], True) + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['shape'], (5,)) + assert_array_equal(c.this_setpoint_set.ndarray, np.linspace(5, 9, 5)) \ No newline at end of file diff --git a/qcodes/tests/test_multiprocessing.py b/qcodes/tests/test_multiprocessing.py deleted file mode 100644 index 5d9d0ee8233b..000000000000 --- a/qcodes/tests/test_multiprocessing.py +++ /dev/null @@ -1,471 +0,0 @@ -from unittest import TestCase, skipIf -import time -import re -import sys -import multiprocessing as mp -from queue import Empty -from unittest.mock import patch - -import qcodes -from qcodes.process.helpers import set_mp_method, kill_queue -from qcodes.process.qcodes_process import QcodesProcess -from qcodes.process.stream_queue import get_stream_queue, _SQWriter -from qcodes.process.server import ServerManager, RESPONSE_OK, RESPONSE_ERROR -import qcodes.process.helpers as qcmp -from qcodes.utils.helpers import in_notebook, LogCapture -from qcodes.utils.timing import calibrate - -BREAK_SIGNAL = '~~BREAK~~' - - -class sqtest_echo: - def __init__(self, name, delay=0.01, has_q=True): - self.q_out = mp.Queue() - self.q_err = mp.Queue() - self.p = QcodesProcess(target=sqtest_echo_f, - args=(name, delay, self.q_out, self.q_err, - has_q), - name=name) - self.p.start() - self.delay = delay - self.resp_delay = delay * 2 + 0.03 - - def send_out(self, msg): - self.q_out.put(msg) - time.sleep(self.resp_delay) - - def send_err(self, msg): - self.q_err.put(msg) - time.sleep(self.resp_delay) - - def halt(self): - if not (hasattr(self, 'p') and self.p.is_alive()): - return - self.q_out.put(BREAK_SIGNAL) - self.p.join() - time.sleep(self.resp_delay) - for q in ['q_out', 'q_err']: - if hasattr(self, q): - queue = getattr(self, q) - kill_queue(queue) - kill_queue(queue) # repeat just to make sure it doesn't error - - def __del__(self): - self.halt() - - -def sqtest_echo_f(name, delay, q_out, q_err, has_q): - while True: - time.sleep(delay) - - if not q_out.empty(): - out = q_out.get() - - if out == BREAK_SIGNAL: - # now test that disconnect works, and reverts to - # regular stdout and stderr - if has_q: - try: - get_stream_queue().disconnect() - except RuntimeError: - pass - print('stdout ', end='', flush=True) - print('stderr ', file=sys.stderr, end='', flush=True) - break - - print(out, end='', flush=True) - - if not q_err.empty(): - print(q_err.get(), file=sys.stderr, end='', flush=True) - - -def sqtest_exception(): - raise RuntimeError('Boo!') - - -class TestMpMethod(TestCase): - def test_set_mp_method(self): - start_method = mp.get_start_method() - self.assertIn(start_method, ('fork', 'spawn', 'forkserver')) - - # multiprocessing's set_start_method is NOT idempotent - with self.assertRaises(RuntimeError): - mp.set_start_method(start_method) - - # but ours is - set_mp_method(start_method) - - # it will still error on gibberish, but different errors depending - # on whether you force or not - with self.assertRaises(RuntimeError): - set_mp_method('spoon') - with self.assertRaises(ValueError): - set_mp_method('spoon', force=True) - - # change the error we look for to test strange error handling - mp_err_normal = qcmp.MP_ERR - qcmp.MP_ERR = 'who cares?' - with self.assertRaises(RuntimeError): - set_mp_method('start_method') - qcmp.MP_ERR = mp_err_normal - - -class TestQcodesProcess(TestCase): - def setUp(self): - mp_stats = calibrate(quiet=True) - self.MP_START_DELAY = mp_stats['mp_start_delay'] - self.MP_FINISH_DELAY = mp_stats['mp_finish_delay'] - self.SLEEP_DELAY = mp_stats['sleep_delay'] - self.BLOCKING_TIME = mp_stats['blocking_time'] - self.sq = get_stream_queue() - - @skipIf(getattr(qcodes, '_IN_NOTEBOOK', False), - 'called from notebook') - def test_not_in_notebook(self): - # below we'll patch this to True, but make sure that it's False - # in the normal test runner. - self.assertEqual(in_notebook(), False) - - # and make sure that processes run this way do not use the queue - with self.sq.lock: - p = sqtest_echo('hidden', has_q=False) - time.sleep(self.MP_START_DELAY) - p.send_out('should go to stdout;') - p.send_err('should go to stderr;') - p.halt() - - self.assertEqual(self.sq.get(), '') - - @patch('qcodes.process.qcodes_process.in_notebook') - def test_qcodes_process_exception(self, in_nb_patch): - in_nb_patch.return_value = True - - with self.sq.lock: - name = 'Hamlet' - p = QcodesProcess(target=sqtest_exception, name=name) - - initial_outs = (sys.stdout, sys.stderr) - - # normally you call p.start(), but for this test we want - # the function to actually run in the main process - # it will run the actual target, but will print the exception - # (to the queue) rather than raising it. - p.run() - - # output streams are back to how they started - self.assertEqual((sys.stdout, sys.stderr), initial_outs) - time.sleep(0.01) - exc_text = self.sq.get() - # but we have the exception in the queue - self.maxDiff = None - self.assertGreaterEqual(exc_text.count(name + ' ERR'), 5) - self.assertEqual(exc_text.count('Traceback'), 1, exc_text) - self.assertEqual(exc_text.count('RuntimeError'), 2) - self.assertEqual(exc_text.count('Boo!'), 2) - - @patch('qcodes.process.qcodes_process.in_notebook') - def test_qcodes_process(self, in_nb_patch): - in_nb_patch.return_value = True - - queue_format = re.compile( - '^\[\d\d:\d\d:\d\d\.\d\d\d p\d( ERR)?\] [^\[\]]*$') - - with self.sq.lock: - p1 = sqtest_echo('p1') - p2 = sqtest_echo('p2') - time.sleep(self.MP_START_DELAY + p1.delay + p2.delay) - - self.assertEqual(self.sq.get(), '') - - procNames = ['<{}>'.format(name) - for name in ('p1', 'p2')] - - reprs = [repr(p) for p in mp.active_children()] - for name in procNames: - self.assertIn(name, reprs) - - # test each individual stream to send several messages on same - # and different lines - - for sender, label, term in ([[p1.send_out, 'p1] ', ''], - [p1.send_err, 'p1 ERR] ', '\n'], - [p2.send_out, 'p2] ', '\n'], - [p2.send_err, 'p2 ERR] ', '']]): - sender('row row ') - sender('row your boat\n') - sender('gently down ') - time.sleep(0.01) - data = [line for line in self.sq.get().split('\n') if line] - expected = [ - label + 'row row row your boat', - label + 'gently down ' - ] - # TODO - intermittent error here - self.assertEqual(len(data), len(expected), data) - for line, expected_line in zip(data, expected): - self.assertIsNotNone(queue_format.match(line), data) - self.assertEqual(line[14:], expected_line, data) - - sender(' the stream' + term) - # no label/header as we're continuing the previous line - self.assertEqual(self.sq.get(), ' the stream' + term) - - p1.send_out('marco') - p2.send_out('polo\n') # we don't see these single terminators - p1.send_out('marco\n') # when we change streams - p2.send_out('polo') - time.sleep(0.01) - - data = self.sq.get().split('\n') - for line in data: - if line: - self.assertIsNotNone(queue_format.match(line)) - - data_msgs = [line[14:] for line in data] - expected = [ - '', - 'p1] marco', - 'p2] polo', - 'p1] marco', - 'p2] polo' - ] - self.assertEqual(data_msgs, expected) - - # Some OS's start more processes just for fun... so don't test - # that p1 and p2 are the only ones. - # self.assertEqual(len(reprs), 2, reprs) - - p1.halt() - p2.halt() - # both p1 and p2 should have finished now, and ended. - reprs = [repr(p) for p in mp.active_children()] - for name in procNames: - self.assertNotIn(name, reprs) - - -class TestStreamQueue(TestCase): - def test_connection(self): - sq = get_stream_queue() - sq.connect('') - # TODO: do we really want double-connect to raise? or maybe - # only raise if the process name changes? - with self.assertRaises(RuntimeError): - sq.connect('') - sq.disconnect() - with self.assertRaises(RuntimeError): - sq.disconnect() - - def test_del(self): - sq = get_stream_queue() - self.assertTrue(hasattr(sq, 'queue')) - self.assertTrue(hasattr(sq, 'lock')) - self.assertIsNotNone(sq.instance) - - sq.__del__() - - self.assertFalse(hasattr(sq, 'queue')) - self.assertFalse(hasattr(sq, 'lock')) - self.assertIsNone(sq.instance) - - sq.__del__() # just to make sure it doesn't error - - # this is basically tested in TestQcodesProcess, but the test happens - # in a subprocess so coverage doesn't know about it. Anyway, there are - # a few edge cases left that we have to test locally. - def test_sq_writer(self): - sq = get_stream_queue() - with sq.lock: - sq_clearer = _SQWriter(sq, 'Someone else') - sq_clearer.write('Boo!\n') - - # apparently we need a little delay to make sure the queue - # properly appears populated. - time.sleep(0.01) - - sq.get() - sq_name = 'A Queue' - sqw = _SQWriter(sq, sq_name) - - # flush should exist, but does nothing - sqw.flush() - - lines = [ - 'Knock knock.\nWho\'s there?\n', - 'Interrupting cow.\n', - 'Interr-', - 'MOO!\n', - '' - ] - - for line in lines: - sqw.write(line) - - time.sleep(0.01) - - # _SQWriter doesn't do any transformations to the messages, beyond - # adding a time string and the stream name - for line in lines: - if not line: - self.assertEqual(sq.queue.empty(), True) - continue - - self.assertEqual(sq.queue.empty(), False) - timestr, stream_name, msg = sq.queue.get() - self.assertEqual(msg, line) - self.assertEqual(stream_name, sq_name) - - # now test that even if the queue is unwatched the messages still - # go there. If we're feeling adventurous maybe we can test if - # something was actually printed. - sqw.MIN_READ_TIME = -1 - new_message = 'should get printed\n' - sqw.write(new_message) - - time.sleep(0.01) - - self.assertEqual(sq.queue.empty(), False) - self.assertEqual(sq.queue.get()[2], new_message) - - # test that an error in writing resets stdout and stderr - # nose uses its own stdout and stderr... so keep them (as we will - # force stdout and stderr to several other things) so we can put - # them back at the end - nose_stdout = sys.stdout - nose_stderr = sys.stderr - sys.stdout = _SQWriter(sq, 'mock_stdout') - sys.stderr = _SQWriter(sq, 'mock_stderr') - self.assertNotIn(sys.stdout, (sys.__stdout__, nose_stdout)) - self.assertNotIn(sys.stderr, (sys.__stderr__, nose_stderr)) - - sqw.MIN_READ_TIME = 'not a number' - with self.assertRaises(TypeError): - sqw.write('trigger an error') - - self.assertEqual(sys.stdout, sys.__stdout__) - self.assertEqual(sys.stderr, sys.__stderr__) - - sys.stdout = nose_stdout - sys.stderr = nose_stderr - time.sleep(0.01) - sq.get() - - -class ServerManagerTest(ServerManager): - def _start_server(self): - # don't really start the server - we'll test its pieces separately, - # in the main process - pass - - -class EmptyServer: - def __init__(self, query_queue, response_queue, extras): - query_queue.put('why?') - response_queue.put(extras) - - -class CustomError(Exception): - pass - - -def delayed_put(queue, val, delay): - time.sleep(delay) - queue.put(val) - - -class TestServerManager(TestCase): - def check_error(self, manager, error_str, error_class): - manager._response_queue.put(RESPONSE_ERROR, error_str) - with self.assertRaises(error_class): - manager.ask('which way does the wind blow?') - - def test_mechanics(self): - extras = 'super secret don\'t tell anyone' - - sm = ServerManagerTest(name='test', server_class=EmptyServer, - shared_attrs=extras) - sm._run_server() - - self.assertEqual(sm._query_queue.get(timeout=1), 'why?') - self.assertEqual(sm._response_queue.get(timeout=1), extras) - - # builtin errors we propagate to the server - builtin_error_str = ('traceback\n lines\n and then\n' - ' OSError: your hard disk went floppy.') - sm._response_queue.put((RESPONSE_ERROR, builtin_error_str)) - with self.assertRaises(OSError): - sm.ask('which way does the wind blow?') - - # non-built-in errors we fall back on RuntimeError - custom_error_str = ('traceback\nlines\nand then\n' - 'CustomError: the Balrog is loose!') - extra_resp1 = 'should get tossed by the error checker' - extra_resp2 = 'so should this.' - sm._response_queue.put((RESPONSE_OK, extra_resp1)) - sm._response_queue.put((RESPONSE_OK, extra_resp2)) - sm._response_queue.put((RESPONSE_ERROR, custom_error_str)) - - # TODO: we have an intermittent failure below, but only when running - # the full test suite (including pyqt and matplotlib?), not if we - # run just this module, or at least not nearly as frequently. - time.sleep(0.2) - - with LogCapture() as logs: - with self.assertRaises(RuntimeError): - sm.ask('something benign') - self.assertTrue(sm._response_queue.empty()) - self.assertIn(extra_resp1, logs.value) - self.assertIn(extra_resp2, logs.value) - - # extra responses to a query, only the last should be taken - extra_resp1 = 'boo!' - extra_resp2 = 'a barrel of monkeys!' - sm._response_queue.put((RESPONSE_OK, extra_resp1)) - sm._response_queue.put((RESPONSE_OK, extra_resp2)) - time.sleep(0.05) - p = mp.Process(target=delayed_put, - args=(sm._response_queue, (RESPONSE_OK, 42), 0.05)) - p.start() - - with LogCapture() as logs: - self.assertEqual(sm.ask('what is the answer'), 42) - self.assertIn(extra_resp1, logs.value) - self.assertIn(extra_resp2, logs.value) - - # no response to a query - with self.assertRaises(Empty): - sm.ask('A sphincter says what?', timeout=0.05) - - # test halting an unresponsive server - sm._server = mp.Process(target=time.sleep, args=(1000,)) - sm._server.start() - - self.assertIn(sm._server, mp.active_children()) - - with LogCapture() as logs: - sm.halt(0.01) - self.assertIn('ServerManager did not respond ' - 'to halt signal, terminated', logs.value) - - self.assertNotIn(sm._server, mp.active_children()) - - def test_pathological_edge_cases(self): - # kill_queue should never fail - kill_queue(None) - - # and halt should ignore AssertionErrors, which arise in - # subprocesses when trying to kill a different subprocess - sm = ServerManagerTest(name='test', server_class=None) - - class HorribleProcess: - def is_alive(self): - raise AssertionError - - def write(self): - raise AssertionError - - def join(self): - raise AssertionError - - sm._server = HorribleProcess() - - sm.halt() diff --git a/qcodes/tests/test_nested_attrs.py b/qcodes/tests/test_nested_attrs.py deleted file mode 100644 index 0cf0dc197443..000000000000 --- a/qcodes/tests/test_nested_attrs.py +++ /dev/null @@ -1,100 +0,0 @@ -from unittest import TestCase -from qcodes.utils.nested_attrs import NestedAttrAccess - - -class TestNestedAttrAccess(TestCase): - def test_simple(self): - obj = NestedAttrAccess() - - # before setting attr1 - self.assertEqual(obj.getattr('attr1', 99), 99) - with self.assertRaises(AttributeError): - obj.getattr('attr1') - - with self.assertRaises(TypeError): - obj.setattr('attr1') - - self.assertFalse(hasattr(obj, 'attr1')) - - # set it to a value - obj.setattr('attr1', 98) - self.assertTrue(hasattr(obj, 'attr1')) - - self.assertEqual(obj.getattr('attr1', 99), 98) - self.assertEqual(obj.getattr('attr1'), 98) - - # then delete it - obj.delattr('attr1') - - with self.assertRaises(AttributeError): - obj.delattr('attr1') - - with self.assertRaises(AttributeError): - obj.getattr('attr1') - - # make and call a method - def f(a, b=0): - return a + b - - obj.setattr('m1', f) - self.assertEqual(obj.callattr('m1', 4, 1), 5) - self.assertEqual(obj.callattr('m1', 21, b=42), 63) - - def test_nested(self): - obj = NestedAttrAccess() - - self.assertFalse(hasattr(obj, 'd1')) - - with self.assertRaises(TypeError): - obj.setattr('d1') - - # set one attribute that creates nesting - obj.setattr('d1', {'a': {1: 2, 'l': [5, 6]}}) - - # can't nest inside a non-container - with self.assertRaises(TypeError): - obj.setattr('d1["a"][1]["secret"]', 42) - - # get the whole dict - self.assertEqual(obj.getattr('d1'), {'a': {1: 2, 'l': [5, 6]}}) - self.assertEqual(obj.getattr('d1', 55), {'a': {1: 2, 'l': [5, 6]}}) - - # get parts - self.assertEqual(obj.getattr('d1["a"]'), {1: 2, 'l': [5, 6]}) - self.assertEqual(obj.getattr('d1["a"][1]'), 2) - self.assertEqual(obj.getattr('d1["a"][1]', 3), 2) - with self.assertRaises(KeyError): - obj.getattr('d1["b"]') - - # add an attribute inside, then delete it again - obj.setattr('d1["a"][2]', 4) - self.assertEqual(obj.getattr('d1'), {'a': {1: 2, 2: 4, 'l': [5, 6]}}) - obj.delattr('d1["a"][2]') - self.assertEqual(obj.getattr('d1'), {'a': {1: 2, 'l': [5, 6]}}) - self.assertEqual(obj.d1, {'a': {1: 2, 'l': [5, 6]}}) - - # list access - obj.setattr('d1["a"]["l"][0]', 7) - obj.callattr('d1["a"]["l"].extend', [5, 3]) - obj.delattr('d1["a"]["l"][1]') - # while we're at it test single quotes - self.assertEqual(obj.getattr("d1['a']['l'][1]"), 5) - self.assertEqual(obj.d1['a']['l'], [7, 5, 3]) - - def test_bad_attr(self): - obj = NestedAttrAccess() - obj.d = {} - # this one works - obj.setattr('d["x"]', 1) - - bad_attrs = [ - '', '.', '[', 'x.', '[]', # simply malformed - '.x' # don't put a dot at the start - '["hi"]', # can't set an item at the top level - 'd[x]', 'd["x]', 'd["x\']' # quoting errors - ] - - for attr in bad_attrs: - with self.subTest(attr=attr): - with self.assertRaises(ValueError): - obj.setattr(attr, 1) diff --git a/qcodes/tests/test_visa.py b/qcodes/tests/test_visa.py index 07a7e0638b1e..bbe6e92141a2 100644 --- a/qcodes/tests/test_visa.py +++ b/qcodes/tests/test_visa.py @@ -60,12 +60,6 @@ def ask(self, cmd): class TestVisaInstrument(TestCase): - def test_default_server_name(self): - dsn = VisaInstrument.default_server_name - self.assertEqual(dsn(), 'VisaServer') - self.assertEqual(dsn(address='Gpib::10'), 'GPIBServer') - self.assertEqual(dsn(address='aSRL4'), 'SerialServer') - # error args for set(-10) args1 = [ 'be more positive!', @@ -87,7 +81,7 @@ def test_default_server_name(self): ] def test_ask_write_local(self): - mv = MockVisa('Joe', server_name=None) + mv = MockVisa('Joe') # test normal ask and write behavior mv.state.set(2) @@ -116,43 +110,6 @@ def test_ask_write_local(self): mv.close() - def test_ask_write_server(self): - # same thing as above but Joe is on a server now... - mv = MockVisa('Joe', server_name='') - - # test normal ask and write behavior - mv.state.set(2) - self.assertEqual(mv.state.get(), 2) - mv.state.set(3.4567) - self.assertEqual(mv.state.get(), 3.457) # driver rounds to 3 digits - - # test ask and write errors - with self.assertRaises(ValueError) as e: - mv.state.set(-10) - for arg in self.args1: - self.assertIn(repr(arg), e.exception.args[0]) - self.assertEqual(mv.state.get(), -10) # set still happened - - # only built-in errors get propagated to the main process as the - # same type. Perhaps we could include some more common ones like - # this (visa.VisaIOError) in the future... - with self.assertRaises(RuntimeError) as e: - mv.state.set(0) - for arg in self.args2: - self.assertIn(repr(arg), e.exception.args[0]) - # the error type isn't VisaIOError, but it should be in the message - self.assertIn('VisaIOError', e.exception.args[0]) - self.assertIn('VI_ERROR_TMO', e.exception.args[0]) - self.assertEqual(mv.state.get(), 0) - - mv.state.set(15) - with self.assertRaises(ValueError) as e: - mv.state.get() - for arg in self.args3: - self.assertIn(repr(arg), e.exception.args[0]) - - mv.close() - @patch('qcodes.instrument.visa.visa.ResourceManager') def test_visa_backend(self, rm_mock): address_opened = [None] @@ -167,18 +124,17 @@ def open_resource(self, address): rm_mock.return_value = MockRM() - MockBackendVisaInstrument('name', server_name=None) + MockBackendVisaInstrument('name') self.assertEqual(rm_mock.call_count, 1) self.assertEqual(rm_mock.call_args, ((),)) self.assertEqual(address_opened[0], None) - MockBackendVisaInstrument('name2', server_name=None, address='ASRL2') + MockBackendVisaInstrument('name2', address='ASRL2') self.assertEqual(rm_mock.call_count, 2) self.assertEqual(rm_mock.call_args, ((),)) self.assertEqual(address_opened[0], 'ASRL2') - MockBackendVisaInstrument('name3', server_name=None, - address='ASRL3@py') + MockBackendVisaInstrument('name3', address='ASRL3@py') self.assertEqual(rm_mock.call_count, 3) self.assertEqual(rm_mock.call_args, (('@py',),)) self.assertEqual(address_opened[0], 'ASRL3') diff --git a/qcodes/utils/nested_attrs.py b/qcodes/utils/nested_attrs.py deleted file mode 100644 index 66c9063fa3fe..000000000000 --- a/qcodes/utils/nested_attrs.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Nested attribute / item access for use by remote proxies.""" - -import re - - -class _NoDefault: - - """Empty class to provide a missing default to getattr.""" - - -class NestedAttrAccess: - - """ - A Mixin class to provide nested access to attributes and their items. - - Primarily for use by remote proxies, so we don't need to separately - proxy all the components, and all of their components, and worry about - which are picklable, etc. - """ - - def getattr(self, attr, default=_NoDefault): - """ - Get a (possibly nested) attribute of this object. - - If there is no ``.`` or ``[]`` in ``attr``, this exactly matches - the ``getattr`` function, but you can also access smaller pieces. - - Args: - attr (str): An attribute or accessor string, like: - ``'attr.subattr[item]'``. ``item`` can be an integer or a - string. If it's a string it must be quoted. - default (any): If the attribute does not exist (at any level of - nesting), we return this. If no default is provided, throws - an ``AttributeError``. - - Returns: - The value of this attribute. - - Raises: - ValueError: If ``attr`` could not be understood. - AttributeError: If the attribute is missing and no default is - provided. - KeyError: If the item cannot be found and no default is provided. - """ - parts = self._split_attr(attr) - - try: - return self._follow_parts(parts) - - except (AttributeError, KeyError): - if default is _NoDefault: - raise - else: - return default - - def setattr(self, attr, value): - """ - Set a (possibly nested) attribute of this object. - - If there is no ``.`` or ``[]`` in ``attr``, this exactly matches - the ``setattr`` function, but you can also access smaller pieces. - - Args: - attr (str): An attribute or accessor string, like: - ``'attr.subattr[item]'``. ``item`` can be an integer or a - string; If it's a string it must be quoted as usual. - - value (any): The object to store in this attribute. - - Raises: - ValueError: If ``attr`` could not be understood - - TypeError: If an intermediate nesting level is not a container - and the next level is an item. - - AttributeError: If an attribute with this name cannot be set. - """ - parts = self._split_attr(attr) - obj = self._follow_parts(parts[:-1]) - leaf = parts[-1] - - if str(leaf).startswith('.'): - setattr(obj, leaf[1:], value) - else: - obj[leaf] = value - - def delattr(self, attr): - """ - Delete a (possibly nested) attribute of this object. - - If there is no ``.`` or ``[]`` in ``attr``, this exactly matches - the ``delattr`` function, but you can also access smaller pieces. - - Args: - attr (str): An attribute or accessor string, like: - ``'attr.subattr[item]'``. ``item`` can be an integer or a - string; If it's a string it must be quoted as usual. - - Raises: - ValueError: If ``attr`` could not be understood - """ - parts = self._split_attr(attr) - obj = self._follow_parts(parts[:-1]) - leaf = parts[-1] - - if str(leaf).startswith('.'): - delattr(obj, leaf[1:]) - else: - del obj[leaf] - - def callattr(self, attr, *args, **kwargs): - """ - Call a (possibly nested) method of this object. - - Args: - attr (str): An attribute or accessor string, like: - ``'attr.subattr[item]'``. ``item`` can be an integer or a - string; If it's a string it must be quoted as usual. - - *args: Passed on to the method. - - **kwargs: Passed on to the method. - - Returns: - any: Whatever the method returns. - - Raises: - ValueError: If ``attr`` could not be understood - """ - func = self.getattr(attr) - return func(*args, **kwargs) - - _PARTS_RE = re.compile(r'([\.\[])') - _ITEM_RE = re.compile(r'\[(?P[^\[\]]+)\]') - _QUOTED_RE = re.compile(r'(?P[\'"])(?P[^\'"]*)(?P=q)') - - def _split_attr(self, attr): - """ - Return attr as a list of parts. - - Items in the list are: - str '.attr' for attribute access, - str 'item' for string dict keys, - integers for integer dict/sequence keys. - Other key formats are not supported - """ - # the first item is implicitly an attribute - parts = ('.' + self._PARTS_RE.sub(r'~\1', attr)).split('~') - for i, part in enumerate(parts): - item_match = self._ITEM_RE.fullmatch(part) - if item_match: - item = item_match.group('item') - quoted_match = self._QUOTED_RE.fullmatch(item) - if quoted_match: - parts[i] = quoted_match.group('str') - else: - try: - parts[i] = int(item) - except ValueError: - raise ValueError('unrecognized item: ' + item) - elif part[0] != '.' or len(part) < 2: - raise ValueError('unrecognized attribute part: ' + part) - - return parts - - def _follow_parts(self, parts): - obj = self - - for key in parts: - if str(key).startswith('.'): - obj = getattr(obj, key[1:]) - else: - obj = obj[key] - - return obj diff --git a/qcodes/utils/reload_code.py b/qcodes/utils/reload_code.py deleted file mode 100644 index 08cedf5d1dd6..000000000000 --- a/qcodes/utils/reload_code.py +++ /dev/null @@ -1,98 +0,0 @@ -import os -import sys -import imp -from traceback import format_exc - -# Routine to reload modules during execution, for development only. -# This is finicky and SHOULD NOT be used in regular experiments, -# as they can cause non-intuitive errors later on. It is not included in -# the base qcodes import, nor tested; Use at your own risk. - - -# see http://stackoverflow.com/questions/22195382/ -# how-to-check-if-a-module-library-package-is-part-of-the-python-standard-library -syspaths = [os.path.abspath(p) for p in sys.path] -stdlib = tuple(p for p in syspaths - if p.startswith((sys.prefix, sys.base_prefix)) - and 'site-packages' not in p) -# a few things in site-packages we will consider part of the standard lib -# it causes problems if we reload some of these, others are just stable -# dependencies - this is mainly for reloading our own code. -# could even whitelist site-packages items to allow, rather than to ignore? -otherlib = ('jupyter', 'ipy', 'IPy', 'matplotlib', 'numpy', 'scipy', 'pyvisa', - 'traitlets', 'zmq', 'tornado', 'dateutil', 'six', 'pexpect') -otherpattern = tuple('site-packages/' + n for n in otherlib) - - -def reload_code(pattern=None, lib=False, site=False): - ''' - reload all modules matching a given pattern - or all (non-built-in) modules if pattern is omitted - if lib is False (default), ignore the standard library and major packages - if site is False (default), ignore everything in site-packages, only reload - files in nonstandard paths - ''' - reloaded_files = [] - - for i in range(2): - # sometimes we need to reload twice to propagate all links, - # even though we reload the deepest modules first. Not sure if - # twice is always sufficient, but we'll try it. - for module in sys.modules.values(): - if (pattern is None or pattern in module.__name__): - reload_recurse(module, reloaded_files, lib, site) - - return reloaded_files - - -def is_good_module(module, lib=False, site=False): - ''' - is an object (module) a module we can reload? - if lib is False (default), ignore the standard library and major packages - ''' - # take out non-modules and underscore modules - name = getattr(module, '__name__', '_') - if name[0] == '_' or not isinstance(module, type(sys)): - return False - - # take out modules we can't find and built-ins - if name in sys.builtin_module_names or not hasattr(module, '__file__'): - return False - - path = os.path.abspath(module.__file__) - - if 'site-packages' in path and not site: - return False - - if not lib: - if path.startswith(stdlib) and 'site-packages' not in path: - return False - - for pattern in otherpattern: - if pattern in path: - return False - - return True - - -def reload_recurse(module, reloaded_files, lib, site): - ''' - recursively search module for its own dependencies to reload, - ignoring those already in reloaded_files - if lib is False (default), ignore the standard library and major packages - ''' - if (not is_good_module(module, lib, site) or - module.__file__ in reloaded_files): - return - - reloaded_files.append(module.__file__) - - try: - for name in dir(module): - module2 = getattr(module, name) - reload_recurse(module2, reloaded_files, lib, site) - imp.reload(module) - - except: - print('error reloading "{}"'.format(getattr(module, '__name__', '?'))) - print(format_exc()) diff --git a/qcodes/utils/timing.py b/qcodes/utils/timing.py deleted file mode 100644 index a624bb640aac..000000000000 --- a/qcodes/utils/timing.py +++ /dev/null @@ -1,89 +0,0 @@ -import time -import multiprocessing as mp - - -sleep_time = 0.001 - -_calibration = { - 'sleep_delay': None, - 'async_sleep_delay': None, - 'mp_start_delay': None -} - - -def calibrate(quiet=False): - if _calibration['mp_start_delay'] is None: - if not quiet: # pragma: no cover - print('multiprocessing startup delay and regular sleep delays:') - mp_res = mptest(quiet=quiet) - _calibration['blocking_time'] = abs(mp_res['blocking_time']) - _calibration['mp_start_delay'] = abs(mp_res['startup_time']) - _calibration['mp_finish_delay'] = abs(mp_res['finish_time']) - _calibration['sleep_delay'] = abs(mp_res['median']) - - return _calibration - - -def report(startup_time, deviations, - queue=None, quiet=False): # pragma: no cover - deviations.sort() - mindev = deviations[0] - avgdev = sum(deviations) / len(deviations) - meddev = deviations[len(deviations) // 2] - maxdev = deviations[-1] - if not quiet: - print('startup time: {:.3e}'.format(startup_time)) - print('min/med/avg/max dev: {:.3e}, {:.3e}, {:.3e}, {:.3e}'.format( - mindev, meddev, avgdev, maxdev)) - - out = { - 'startup_time': startup_time, - 'min': mindev, - 'max': maxdev, - 'avg': avgdev, - 'median': meddev, - 'finish_time': time.time() - } - if queue: - queue.put(out) - return out - - -def sleeper(n, d, t0, timer, queue, quiet): # pragma: no cover - times = [] - startup_time = time.time() - t0 - for i in range(n): - times.append(timer()) - time.sleep(d) - - deviations = [times[i] - times[i - 1] - d for i in range(1, len(times))] - return report(startup_time, deviations, queue, quiet) - - -def mptest(n=100, d=0.001, timer=time.perf_counter, quiet=False): - ''' - test time.sleep performance, and the time to start a multiprocessing - Process. start time uses time.time() because some other timers start - from zero in each new process - - n: how many asyncio.sleep calls to use - default 100 - d: delay per sleep - default 0.001 - timer: which system timer to use - default time.perf_counter - quiet: don't print anything - default False - ''' - - q = mp.Queue() - start_time = time.time() - p = mp.Process(target=sleeper, args=(n, d, start_time, timer, q, quiet)) - p.start() - blocking_time = time.time() - start_time - p.join() - - out = q.get() - out['finish_time'] = time.time() - out['finish_time'] - out['blocking_time'] = blocking_time - return out diff --git a/qcodes/widgets/__init__.py b/qcodes/widgets/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/qcodes/widgets/display.py b/qcodes/widgets/display.py deleted file mode 100644 index 6a705dbb2cd1..000000000000 --- a/qcodes/widgets/display.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Helper for adding content stored in a file to a jupyter notebook.""" -import os -from pkg_resources import resource_string -from IPython.display import display, Javascript, HTML - - -# Originally I implemented this using regular open() and read(), so it -# could use relative paths from the importing file. -# -# But for distributable packages, pkg_resources.resource_string is the -# best way to load data files, because it works even if the package is -# in an egg or zip file. See: -# http://pythonhosted.org/setuptools/setuptools.html#accessing-data-files-at-runtime - -def display_auto(qcodes_path, file_type=None): - """ - Display some javascript, css, or html content in a jupyter notebook. - - Content comes from a package-relative file path. Will use the file - extension to determine file type unless overridden by file_type - - Args: - qcodes_path (str): the path to the target file within the qcodes - package, like 'widgets/widgets.js' - - file_type (Optional[str]): Override the file extension to determine - what type of file this is. Case insensitive, supported values - are 'js', 'css', and 'html' - """ - contents = resource_string('qcodes', qcodes_path).decode('utf-8') - - if file_type is None: - ext = os.path.splitext(qcodes_path)[1].lower() - elif 'js' in file_type.lower(): - ext = '.js' - elif 'css' in file_type.lower(): - ext = '.css' - else: - ext = '.html' - - if ext == '.js': - display(Javascript(contents)) - elif ext == '.css': - display(HTML('')) - else: - # default to html. Anything else? - display(HTML(contents)) diff --git a/qcodes/widgets/widgets.css b/qcodes/widgets/widgets.css deleted file mode 100644 index ab4204ae5e4d..000000000000 --- a/qcodes/widgets/widgets.css +++ /dev/null @@ -1,77 +0,0 @@ -.qcodes-output-view:not(.ui-draggable) { - bottom: 0; - right: 5px; -} -.qcodes-output-view { - position: fixed; - z-index: 999; - background-color: #fff; - box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2); -} - -.qcodes-output-header { - float: right; -} - -.qcodes-highlight { - animation: pulse 1s linear; - background-color: #fa4; -} - -@keyframes pulse { - 0% { - background-color: #f00; - } - 100% { - background-color: #fa4; - } -} - -.qcodes-process-list { - float: left; - max-width: 780px; - margin: 3px 5px 3px 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.qcodes-output-view[qcodes-state=minimized] .qcodes-process-list { - max-width: 300px; -} - -.qcodes-output-view span { - padding: 2px 6px 3px 12px; -} - -.qcodes-output-view .btn { - margin: 0 3px 0 0; -} - -.qcodes-output-view[qcodes-state=docked] .qcodes-docked, -.qcodes-output-view[qcodes-state=floated] .qcodes-floated, -.qcodes-output-view[qcodes-state=minimized] .qcodes-minimized, -.qcodes-output-view[qcodes-state=minimized] .qcodes-content { - display: none; -} - -.qcodes-output-view .disabled { - opacity: 0.4; -} - -.qcodes-abort-loop { - background-color: #844; - color: #fff; -} - -.qcodes-output-view pre { - clear: both; - margin: 0; - border: 0; - border-top: 1px solid #ccc; - background-color: #ffe; - min-height: 50px; - max-height: 400px; - min-width: 400px; - max-width: 1000px; -} \ No newline at end of file diff --git a/qcodes/widgets/widgets.js b/qcodes/widgets/widgets.js deleted file mode 100644 index c93f0882d382..000000000000 --- a/qcodes/widgets/widgets.js +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Qcodes Jupyter/IPython widgets - */ -require([ - 'nbextensions/widgets/widgets/js/widget', - 'nbextensions/widgets/widgets/js/manager' -], function (widget, manager) { - - var UpdateView = widget.DOMWidgetView.extend({ - render: function() { - window.MYWIDGET = this; - this._interval = 0; - this.update(); - }, - update: function() { - this.display(this.model.get('_message')); - this.setInterval(); - }, - display: function(message) { - /* - * display method: override this for custom display logic - */ - this.el.innerHTML = message; - }, - remove: function() { - clearInterval(this._updater); - }, - setInterval: function(newInterval) { - var me = this; - if(newInterval===undefined) newInterval = me.model.get('interval'); - if(newInterval===me._interval) return; - - me._interval = newInterval; - - if(me._updater) clearInterval(me._updater); - - if(me._interval) { - me._updater = setInterval(function() { - me.send({myupdate: true}); - if(!me.model.comm_live) { - console.log('missing comm, canceling widget updates', me); - clearInterval(me._updater); - } - }, me._interval * 1000); - } - } - }); - manager.WidgetManager.register_widget_view('UpdateView', UpdateView); - - var HiddenUpdateView = UpdateView.extend({ - display: function(message) { - this.$el.hide(); - } - }); - manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView); - - var SubprocessView = UpdateView.extend({ - render: function() { - var me = this; - me._interval = 0; - me._minimize = ''; - me._restore = ''; - - // max lines of output to show - me.maxOutputLength = 500; - - // in case there is already an outputView present, - // like from before restarting the kernel - $('.qcodes-output-view').not(me.$el).remove(); - - me.$el - .addClass('qcodes-output-view') - .attr('qcodes-state', 'docked') - .html( - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '
'
-                );
-
-            me.clearButton = me.$el.find('.qcodes-clear-output');
-            me.minButton = me.$el.find('.qcodes-minimize');
-            me.outputArea = me.$el.find('pre');
-            me.subprocessList = me.$el.find('.qcodes-process-list');
-            me.abortButton = me.$el.find('.qcodes-abort-loop');
-            me.processLinesButton = me.$el.find('.qcodes-processlines')
-
-            me.outputLines = [];
-
-            me.clearButton.click(function() {
-                me.outputArea.html('');
-                me.clearButton.addClass('disabled');
-            });
-
-            me.abortButton.click(function() {
-                me.send({abort: true});
-            });
-
-            me.processLinesButton.click(function() {
-                // toggle multiline process list display
-                me.subprocessesMultiline = !me.subprocessesMultiline;
-                me.showSubprocesses();
-            });
-
-            me.$el.find('.js-state').click(function() {
-                var state = this.className.substr(this.className.indexOf('qcodes'))
-                        .split('-')[1].split(' ')[0];
-                me.model.set('_state', state);
-            });
-
-            $(window)
-                .off('resize.qcodes')
-                .on('resize.qcodes', function() {me.clipBounds();});
-
-            me.update();
-        },
-
-        updateState: function() {
-            var me = this,
-                oldState = me.$el.attr('qcodes-state'),
-                state = me.model.get('_state');
-
-            if(state === oldState) return;
-
-            setTimeout(function() {
-                // not sure why I can't pop it out of the widgetarea in render, but it seems that
-                // some other bit of code resets the parent after render if I do it there.
-                // To be safe, just do it on every state click.
-                me.$el.appendTo('body');
-
-                if(oldState === 'floated') {
-                    console.log('here');
-                    me.$el.draggable('destroy').css({left:'', top: ''});
-                }
-
-                me.$el.attr('qcodes-state', state);
-
-                if(state === 'floated') {
-                    me.$el
-                        .draggable({stop: function() { me.clipBounds(); }})
-                        .css({
-                            left: window.innerWidth - me.$el.width() - 15,
-                            top: window.innerHeight - me.$el.height() - 10
-                        });
-                }
-
-                // any previous highlighting is now moot
-                me.$el.removeClass('qcodes-highlight');
-            }, 0);
-
-        },
-
-        clipBounds: function() {
-            var me = this;
-            if(me.$el.attr('qcodes-state') === 'floated') {
-                var bounds = me.$el[0].getBoundingClientRect(),
-                    minVis = 40,
-                    maxLeft = window.innerWidth - minVis,
-                    minLeft = minVis - bounds.width,
-                    maxTop = window.innerHeight - minVis;
-
-                if(bounds.left > maxLeft) me.$el.css('left', maxLeft);
-                else if(bounds.left < minLeft) me.$el.css('left', minLeft);
-
-                if(bounds.top > maxTop) me.$el.css('top', maxTop);
-                else if(bounds.top < 0) me.$el.css('top', 0);
-            }
-        },
-
-        display: function(message) {
-            var me = this;
-            if(message) {
-                var initialScroll = me.outputArea.scrollTop();
-                me.outputArea.scrollTop(me.outputArea.prop('scrollHeight'));
-                var scrollBottom = me.outputArea.scrollTop();
-
-                if(me.$el.attr('qcodes-state') === 'minimized') {
-                    // if we add text and the box is minimized, highlight the
-                    // title bar to alert the user that there are new messages.
-                    // remove then add the class, so we get the animation again
-                    // if it's already highlighted
-                    me.$el.removeClass('qcodes-highlight');
-                    setTimeout(function(){
-                        me.$el.addClass('qcodes-highlight');
-                    }, 0);
-                }
-
-                var newLines = message.split('\n'),
-                    out = me.outputLines,
-                    outLen = out.length;
-                if(outLen) out[outLen - 1] += newLines[0];
-                else out.push(newLines[0]);
-
-                for(var i = 1; i < newLines.length; i++) {
-                    out.push(newLines[i]);
-                }
-
-                if(out.length > me.maxOutputLength) {
-                    out.splice(0, out.length - me.maxOutputLength + 1,
-                        '<<< Output clipped >>>');
-                }
-
-                me.outputArea.text(out.join('\n'));
-                me.clearButton.removeClass('disabled');
-
-                // if we were scrolled to the bottom initially, make sure
-                // we stay that way.
-                me.outputArea.scrollTop(initialScroll === scrollBottom ?
-                    me.outputArea.prop('scrollHeight') : initialScroll);
-            }
-
-            me.showSubprocesses();
-            me.updateState();
-        },
-
-        showSubprocesses: function() {
-            var me = this,
-                replacer = me.subprocessesMultiline ? '
' : ', ', - processes = (me.model.get('_processes') || '') - .replace(/\n/g, '>' + replacer + '<'); - - if(processes) processes = '<' + processes + '>'; - else processes = 'No subprocesses'; - - me.abortButton.toggleClass('disabled', processes.indexOf('Measurement')===-1); - - me.subprocessList.html(processes); - } - }); - manager.WidgetManager.register_widget_view('SubprocessView', SubprocessView); -}); diff --git a/qcodes/widgets/widgets.py b/qcodes/widgets/widgets.py deleted file mode 100644 index 56ea36dd45c6..000000000000 --- a/qcodes/widgets/widgets.py +++ /dev/null @@ -1,215 +0,0 @@ -"""Qcodes-specific widgets for jupyter notebook.""" -from IPython.display import display -from ipywidgets import widgets -from multiprocessing import active_children -from traitlets import Unicode, Float, Enum - -from qcodes.process.stream_queue import get_stream_queue -from .display import display_auto -from qcodes.loops import MP_NAME, halt_bg - -display_auto('widgets/widgets.js') -display_auto('widgets/widgets.css') - - -class UpdateWidget(widgets.DOMWidget): - - """ - Execute a callable periodically, and display its return in the output area. - - The Javascript portion of this is in widgets.js with the same name. - - Args: - fn (callable): To be called (with no parameters) periodically. - - interval (number): The call period, in seconds. Can be changed later - by setting the ``interval`` attribute. ``interval=0`` or the - ``halt()`` method disables updates. - - first_call (bool): Whether to call the update function immediately - or only after the first interval. Default True. - """ - - _view_name = Unicode('UpdateView', sync=True) # see widgets.js - _message = Unicode(sync=True) - interval = Float(sync=True) - - def __init__(self, fn, interval, first_call=True): - super().__init__() - - self._fn = fn - self.interval = interval - self.previous_interval = interval - - # callbacks send the widget (self) as the first arg - # so bind to __func__ and we can leave the duplicate out - # of the method signature - self.on_msg(self.do_update.__func__) - - if first_call: - self.do_update({}, []) - - def do_update(self, content=None, buffers=None): - """ - Execute the callback and send its return value to the notebook. - - Args: - content: required by DOMWidget, unused - buffers: required by DOMWidget, unused - """ - self._message = str(self._fn()) - - def halt(self): - """ - Stop future updates. - - Keeps a record of the interval so we can ``restart()`` later. - You can also restart by explicitly setting ``self.interval`` to a - positive value. - """ - if self.interval: - self.previous_interval = self.interval - self.interval = 0 - - def restart(self, **kwargs): - """ - Reinstate updates with the most recent interval. - - TODO: why did I include kwargs? - """ - if not hasattr(self, 'previous_interval'): - self.previous_interval = 1 - - if self.interval != self.previous_interval: - self.interval = self.previous_interval - - -class HiddenUpdateWidget(UpdateWidget): - - """ - A variant on UpdateWidget that hides its section of the output area. - - The Javascript portion of this is in widgets.js with the same name. - - Just lets the front end periodically execute code that takes care of its - own display. By default, first_call is False here, unlike UpdateWidget, - because it is assumed this widget is created to update something that - has been displayed by other means. - - Args: - fn (callable): To be called (with no parameters) periodically. - - interval (number): The call period, in seconds. Can be changed later - by setting the ``interval`` attribute. ``interval=0`` or the - ``halt()`` method disables updates. - - first_call (bool): Whether to call the update function immediately - or only after the first interval. Default False. - """ - - _view_name = Unicode('HiddenUpdateView', sync=True) # see widgets.js - - def __init__(self, *args, first_call=False, **kwargs): - super().__init__(*args, first_call=first_call, **kwargs) - - -def get_subprocess_widget(**kwargs): - """ - Convenience function to get a singleton SubprocessWidget. - - Restarts widget updates if it has been halted. - - Args: - **kwargs: passed to SubprocessWidget constructor - - Returns: - SubprocessWidget - """ - if SubprocessWidget.instance is None: - w = SubprocessWidget(**kwargs) - else: - w = SubprocessWidget.instance - - w.restart() - - return w - - -def show_subprocess_widget(**kwargs): - """ - Display the subprocess widget, creating it if needed. - - Args: - **kwargs: passed to SubprocessWidget constructor - """ - display(get_subprocess_widget(**kwargs)) - - -class SubprocessWidget(UpdateWidget): - - """ - Display subprocess output in a box in the jupyter notebook window. - - Output is collected from each process's stdout and stderr by the - ``StreamQueue`` and read periodically from the main process, triggered - by Javascript. - - The Javascript portion of this is in widgets.js with the same name. - - Args: - interval (number): The call period, in seconds. Can be changed later - by setting the ``interval`` attribute. ``interval=0`` or the - ``halt()`` method disables updates. Default 0.5. - state (str): starting window state of the widget. Options are - 'docked' (default), 'minimized', 'floated' - """ - - _view_name = Unicode('SubprocessView', sync=True) # see widgets.js - _processes = Unicode(sync=True) - _state = Enum(('minimized', 'docked', 'floated'), sync=True) - - instance = None - - # max seconds to wait for a measurement to abort - abort_timeout = 30 - - def __init__(self, interval=0.5, state='docked'): - if self.instance is not None: - raise RuntimeError( - 'Only one instance of SubprocessWidget should exist at ' - 'a time. Use the function get_subprocess_output to find or ' - 'create it.') - - self.stream_queue = get_stream_queue() - self._state = state - super().__init__(fn=None, interval=interval) - - self.__class__.instance = self - - def do_update(self, content=None, buffers=None): - """ - Update the information to be displayed in the widget. - - Send any new messages to the notebook, and update the list of - active processes. - - Args: - content: required by DOMWidget, unused - buffers: required by DOMWidget, unused - """ - self._message = self.stream_queue.get() - - loops = [] - others = [] - - for p in active_children(): - if getattr(p, 'name', '') == MP_NAME: - # take off the <> on the ends, just to shorten the names - loops.append(str(p)[1:-1]) - else: - others.append(str(p)[1:-1]) - - self._processes = '\n'.join(loops + others) - - if content.get('abort'): - halt_bg(timeout=self.abort_timeout, traceback=False) diff --git a/specs/DataSet.rst b/specs/DataSet.rst new file mode 100644 index 000000000000..89eae2ddb3f3 --- /dev/null +++ b/specs/DataSet.rst @@ -0,0 +1,316 @@ +===================== +DataSet Specification +===================== + +Introduction +============ + +The DataSet class is used in QCoDeS to hold measurement results. +It is the destination for measurement loops and the source for plotting and data analysis. +As such, it is a central component of QCoDeS. + +The DataSet class should be usable on its own, without other QCoDeS components. +In particular, the DataSet class should not require the use of Loop and parameters, although it should integrate with those components seamlessly. +This will significantly improve the modularity of QCoDeS by allowing users to plug into and extend the package in many different ways. +As long as a DataSet is used for data storage, users can freely select the QCoDeS components they want to use. + +Terminology +================ + +Metadata + Many items in this spec have metadata associated with them. + In all cases, we expect metadata to be represented as a dictionary with string keys. + While the values are arbitrary and up to the user, in many cases we expect metadata to be nested, string-keyed dictionaries + with scalars (strings or numbers) as the final values. + In some cases, we specify particular keys or paths in the metadata that other QCoDeS components may rely on. + +Parameter + A logically-single value input to or produced by a measurement. + A parameter need not be a scalar, but can be an array or a tuple or an array of tuples, etc. + A DataSet parameter corresponds conceptually to a QCoDeS parameter, but does not have to be defined by or associated with a QCoDeS Parameter . + Roughly, a parameter represents a column in a table of experimental data. + +Result + A result is the collection of parameter values associated to a single measurement in an experiment. + Roughly, a result corresponds to a row in a table of experimental data. + +DataSet + A DataSet is a QCoDeS object that stores the results of an experiment. + Roughly, a DataSet corresponds to a table of experimental data, along with metadata that describes the data. + Depending on the state of the experiment, a DataSet may be "in progress" or "completed". + +ExperimentContainer + An ExperimentContainer is a QCoDeS object that stores all information about an experiment. + This includes items such as the equipment on which the experiment was run, the configuration of the equipment, graphs and other analytical output, and arbitrary notes, as well as the DataSet that holds the results of the experiment. + +Requirements +============ + +The DataSet class should meet the following requirements: + +Basics +--------- + +#. A DataSet can store data of (reasonably) arbitrary types and shapes. basically, any type and shape that can fit in a NumPy array should be supported. +#. The results stored in a completed DataSet should be immutable; no new results may be added to a completed DataSet. +#. Each DataSet should have a unique identifying string that can be used to create references to DataSets. + +Creation +------------ + +#. It should be possible to create a DataSet without knowing the final item count of the various values it stores. + In particular, the number of loop iterations for a sweep should not be required to create the DataSet. +#. The list of parameters in each result to be stored in a DataSet may be specified at creation time. + This includes the name, role (set-point or output), and type of each parameter. + Parameters may be marked as optional, in which case they are not required for each result. +#. It should be possible to add a new parameter to an in-progress DataSet. +#. It should be possible to define a result parameter that is independent of any QCoDeSParameter or Instrument. +#. A QCoDeS Parameter should provide sufficient information to define a result parameter. +#. A DataSet should allow storage of relatively arbitrary metadata describing the run that + generated the results and the parameters included in the results. + Essentially, DataSet metadata should be a string-keyed dictionary at the top, + and should allow storage of any JSON-encodable data. +#. The DataSet identifier should be automatically stored in the DataSet's metadata under the "id" tag. + + +Writing +---------- + +#. It should be possible to add a single result or a sequence of results to an in-progress DataSet. +#. It should be able to add an array of values for a new parameter to an in-progress DataSet. +#. A DataSet should maintain the order in which results were added. +#. An in-progress DataSet may be marked as completed. + +Access +--------- + +#. Values in a DataSet should be easily accessible for plotting and analysis, even while the DataSet is in progress. + In particular, it should be possible to retrieve full or partial results as a NumPy array. +#. It should be possible to define a cursor that specifies a location in a specific value set in a DataSet. + It should be possible to get a cursor that specifies the current end of the DataSet when the DataSet is "in progress". + It should be possible to read "new data" in a DataSet; that is, to read everything after a cursor. +#. It should be possible to subscribe to change notifications from a DataSet. + It is acceptable if such subscriptions must be in-process until QCoDeS multiprocessing is redone. + +Storage and Persistence +----------------------- + +#. Storage and persistence should be defined outside of the DataSet class. + +The following items are no longer applicable: + +#. A DataSet object should allow writing to and reading from storage in a variety of formats. +#. Users should be able to define new persistence formats. +#. Users should be able to specify where a DataSet is written. + +Interface +========= + +Creation +-------- + +ParamSpec +~~~~~~~~~ + +A ParamSpec object specifies a single parameter in a DataSet. + +ParamSpec(name, type, metadata=) + Creates a parameter specification with the given name and type. + The type should be a NumPy dtype object. + + If metadata is provided, it is included in the overall metadata of the DataSet. + The metadata can be any JSON-able object. + +ParamSpec.name + The name of this parameter. + +ParamSpec.type + The dtype of this parameter. + +ParamSpec.metadata + The metadata of this parameter. + This should be an empty dictionary as a default. + +Either the QCoDeS Parameter class should inherit from ParamSpec, or the Parameter class should provide +a simple way to get a ParamSpec for the Parameter. + +DataSet +~~~~~~~ + +Construction +------------ + +DataSet(name) + Creates a DataSet with no parameters. + The name should be a short string that will be part of the DataSet's identifier. + +DataSet(name, specs) + Creates a DataSet for the provided list of parameter specifications. + The name should be a short string that will be part of the DataSet's identifier. + Each item in the list should be a ParamSpec object. + +DataSet(name, specs, values) + Creates a DataSet for the provided list of parameter specifications and values. + The name should be a short string that will be part of the DataSet's identifier. + Each item in the specs list should be a ParamSpec object. + Each item in the values list should be a NumPy array or a Python list of values for the corresponding ParamSpec. + There should be exactly one item in the values list for every item in the specs list. + All of the arrays/lists in the values list should have the same length. + The values list may intermix NumPy arrays and Python lists. + +DataSet.add_parameter(spec) + Adds a parameter to the DataSet. + The spec should be a ParamSpec object. + If the DataSet is not empty, then existing results will have the type-appropriate null value for the new parameter. + + It is an error to add parameters to a completed DataSet. + +DataSet.add_parameters(specs) + Adds a list of parameters to the DataSet. + Each item in the list should be a ParamSpec object. + If the DataSet is not empty, then existing results will have the type-appropriate null value for the new parameters. + + It is an error to add parameters to a completed DataSet. + +DataSet.add_metadata(tag=, metadata=) + Adds metadata to the DataSet. + The metadata is stored under the provided tag. + If there is already metadata under the provided tag, the new metadata replaces the old metadata. + The metadata can be any JSON-able object. + +Writing +------- + +DataSet.add_result(**kwargs) + Adds a result to the DataSet. + Keyword parameters should have the name of a parameter as the keyword and the value to associate as the value. + If there is only one positional parameter and it is a dictionary, then it is interpreted as a map from parameter name to parameter value. + + It is an error to provide a value for a key or keyword that is not the name of a parameter in this DataSet. + + It is an error to add a result to a completed DataSet. + +DataSet.add_results(args) + Adds a sequence of results to the DataSet. + The single argument should be a sequence of dictionaries, where each dictionary provides the values for all of the parameters in that result. + See the add_result method for a description of such a dictionary. + The order of dictionaries in the sequence will be the same as the order in which they are added to the DataSet. + + It is an error to add results to a completed DataSet. + +DataSet.add_parameter_values(spec, values) + Adds a parameter to the DataSet and associates result values with the new parameter. + The values must be a NumPy array or a Python list, with each element holding a single result value that matches the parameter's data type. + If the DataSet is not empty, then the count of provided values must equal the current count of results in the DataSet, or an error will result. + + It is an error to add parameters to a completed DataSet. + +DataSet.mark_complete() + Marks the DataSet as completed. + +Access +------ + +DataSet.id + Returns the unique identifying string for this DataSet. + This string will include the date and time that the DataSet was created and the name supplied to the constructor, + as well as additional content to ensure uniqueness. + +DataSet.length + This attribute holds the current number of results in the DataSet. + +DataSet.is_empty + This attribute will be true if the DataSet is empty (has no results), or false if at least one result has been added to the DataSet. + It is equivalent to testing if the length is zero. + +DataSet.is_marked_complete + This attribute will be true if the DataSet has been marked as complete or false if it is in progress. + +DataSet.get_data(*params, start=, end=) + Returns the values stored in the DataSet for the specified parameters. + The values are returned as a list of parallel NumPy arrays, one array per parameter. + The data type of each array is based on the data type provided when the DataSet was created. + + The parameter list may contain a mix of string parameter names, QCoDeS Parameter objects, and ParamSpec objects. + + If provided, the start and end parameters select a range of results by result count (index). + Start defaults to 0, and end defaults to the current length. + + If the range is empty -- that is, if the end is less than or equal to the start, or if start is after the current end of the DataSet – + then a list of empty arrays is returned. + +DataSet.get_parameters() + Returns a list of ParamSpec objects that describe the parameters stored in this DataSet. + +DataSet.get_metadata(tag=) + Returns metadata for this DataSet. + + If a tag string is provided, only metadata stored under that tag is returned. + Otherwise, all metadata is returned. + +Subscribing +---------------- + +DataSet.subscribe(callback, min_wait=, min_count=, state=) + Subscribes the provided callback function to result additions to the DataSet. + As results are added to the DataSet, the subscriber is notified by having the callback invoked. + + - min_wait is the minimum amount of time between notifications for this subscription, in milliseconds. The default is 100. + - min_count is the minimum number of results for which a notification should be sent. The default is 1. + + When the callback is invoked, it is passed the DataSet itself, the current length of the DataSet, and the state object provided when subscribing. + If no state object was provided, then the callback gets passed None as the fourth parameter. + + The callback is invoked when the DataSet is completed, regardless of the values of min_wait and min_count. + + This method returns an opaque subscription identifier. + +DataSet.unsubscribe(subid) + Removes the indicated subscription. + The subid must be the same object that was returned from a DataSet.subscribe call. + +Storage +------- + +DataSet persistence is handled externally to this class. + +The existing QCoDeS storage subsystem should be modified so that some object has two methods: + +- A write_dataset method that takes a DataSet object and writes it to the appropriate storage location in an appropriate format. +- A read_dataset method that reads from the appropriate location, either with a specified format or inferring the format, and returns + a DataSet object. + +Metadata +======== + +While in general the metadata associated with a DataSet is free-form, it is useful to specify a set of "well-known" tags and paths that components can rely on to contain specific information. +Other components are free to specify new well-known metadata tags and paths, as long as they don't conflict with the set defined here. + +parameters + This tag contains a dictionary from the string name of each parameter to information about that parameter. + Thus, if DataSet ds has a parameter named "foo", there will be a key "foo" in the dictionary returned from ds.get_metadata("parameters"). + The value associated with this key will be a string-keyed dictionary. + +parameters/__param__/spec + This path contains a string-keyed dictionary with (at least) the following two keys: + The "type" key is associated with the NumPy dtype for the values of this parameter. + The "metadata" key is associated with the metadata that was passed to the ParamSpec constructor that defines this parameter, or an empty dictionary if no metadata was set. + +Utilities +========= + +There are many utility routines that may be defined outside of the DataSet class that may be useful. +We collect several of them here, with the note that these functions will not be part of the DataSet class +and will not be required by the DataSet class. + +dataframe_from_dataset(dataset) + Creates a Pandas DataFrame object from a DataSet that has been marked as completed. + +Open Issues +=========== + +#. Should it be possible to "reopen" a DataSet that has been marked as completed? + +This is convenient for adding data analysis results after the experiement has added, but could potentially lead mixing data from different experimental runs accidentally. +It is already possible to modify metadata after the DataSet has beenmarked as completed, but sometimes that may not be sufficient. +