Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions qcodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,14 @@
from qcodes.utils import validators

from qcodes.instrument_drivers.test import test_instruments, test_instrument

try:
get_ipython() # Check if we are in iPython
from qcodes.utils.magic import register_magic_class
_register_magic = config.core.get('register_magic', False)
if _register_magic is not False:
register_magic_class(magic_commands=_register_magic)
except NameError:
pass
except RuntimeError as e:
print(e)
3 changes: 2 additions & 1 deletion qcodes/config/qcodesrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"core":{
"loglevel": "DEBUG",
"default_fmt": "data/{date}/#{counter}_{name}_{time}"
"default_fmt": "data/{date}/#{counter}_{name}_{time}",
"register_magic": true
},
"gui" :{
"notebook": true,
Expand Down
8 changes: 8 additions & 0 deletions qcodes/config/qcodesrc_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
"INFO",
"DEBUG"
]
},
"register_magic" : {
"description": "Register QCoDeS magic when in iPython. Can be set to True, False, or a list of magic commands to be registered",
"anyOf" : [
{"type": "boolean"},
{"type": "array"}
],
"default": true
}
},
"required":["loglevel" ]
Expand Down
159 changes: 159 additions & 0 deletions qcodes/utils/magic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import sys
from IPython.core.magic import Magics, magics_class, line_cell_magic

if sys.version_info < (3, 6):
raise RuntimeError('Magic only supported for Python version 3.6 and up')

@magics_class
class QCoDeSMagic(Magics):
"""Magics related to code management (loading, saving, editing, ...)."""

def __init__(self, *args, **kwargs):
self._knowntemps = set()
super(QCoDeSMagic, self).__init__(*args, **kwargs)

@line_cell_magic
def measurement(self, line, cell=None):
"""
Create qcodes.Loop measurement mimicking Python `for` syntax via
iPython magic.
Upon execution of a notebook cell, the code is transformed from the
for loop structure to a QCoDeS Loop before being executed.
Can be run by having %%measurement in the first line of a cell,
followed by the measurement name (see below for an example)

The for loop syntax differs slightly from a Python `for` loop,
as it uses `for {iterable}` instead of `for {element} in {iterable}`.
The reason is that `{element}` cannot be accessed (yet) in QCoDeS loops.

Comments (#) are ignored in the loop.
Any code after the loop will also be run, if separated by a blank
line from the loop.
The Loop object is by default stored in a variable named `loop`,
and the dataset in `data`, and these can be overridden using options.
Must be run in a Jupyter Notebook.
Delays can be provided in a loop by adding `-d {delay}` after `for`

The following options can be passed along with the measurement name
(e.g. %%measurement -px -d data_name {measurement_name}):
-p : print transformed code
-x : Do not execute code
-d <dataset_name> : Use custom name for dataset
-l <loop_name> : Use custom name for Loop

An example for a loop cell is as follows:

%%measurement {-options} {measurement_name}
for {sweep_vals}:
{measure_parameter1}
{measure_parameter2}
for -d 1 {sweep_vals2}:
{measure_parameter3}

{Additional code}
```

which will be internally transformed to:

```
import qcodes
loop = qcodes.Loop({sweep_vals}).each(
{measure_parameter1},
{measure_parameter2},
qcodes.Loop({sweep_vals2}, delay=1).each(
{measure_parameter3}))
data = loop.get_data_set(name={measurement_name})

{Additional code}
```

An explicit example of the line `for {sweep_vals}:` could be
`for sweep_parameter.sweep(0, 42, step=1):`

"""

if cell is None:
# No loop provided, print documentation
print(self.measurement.__doc__)
return

# Parse line, get measurement name and any possible options
options, msmt_name = self.parse_options(line, 'pd:l:x')
data_name = options.get('d', 'data')
loop_name = options.get('l', 'loop')


lines = cell.splitlines()
assert lines[0][:3] == 'for', "Measurement must start with for loop"

contents = f'import qcodes\n{loop_name} = '
previous_level = 0
for k, line in enumerate(lines):
line, level = line.lstrip(), int((len(line)-len(line.lstrip())) / 4)

if not line:
# Empty line, end of loop
break
elif line[0] == '#':
# Ignore comment
continue
else:
line_representation = ' ' * level * 4
if level < previous_level :
# Exiting inner loop, close bracket
line_representation += '),' * (previous_level - level)
line_representation += '\n' + ' ' * level * 4

if line[:3] == 'for':
# New loop
for_opts, for_code = self.parse_options(line[4:-1], 'd:')
if 'd' in for_opts:
# Delay option provided
line_representation += f'qcodes.Loop({for_code}, ' \
f'delay={for_opts["d"]}).each(\n'
else:
line_representation += f'qcodes.Loop({for_code}).each(\n'
else:
# Action in current loop
line_representation += f'{line},\n'
contents += line_representation

# Remember level for next iteration (might exit inner loop)
previous_level = level

# Add closing brackets for any remaining loops
contents += ')' * previous_level + '\n'
# Add dataset
contents += f"{data_name} = {loop_name}.get_data_set(name='{msmt_name}')"

for line in lines[k+1:]:
contents += '\n' + line

if 'p' in options:
print(contents)

if 'x' not in options:
# Execute contents
self.shell.run_cell(contents, store_history=True, silent=True)


def register_magic_class(cls=QCoDeSMagic, magic_commands=True):
"""
Registers a iPython magic class
Args:
cls: magic class to register
magic_commands (List): list of magic commands within the class to
register. If not specified, all magic commands are registered

"""

ip = get_ipython()
if ip is None:
raise RuntimeError('No iPython shell found')
else:
if magic_commands is not True:
# filter out any magic commands that are not in magic_commands
cls.magics = {line_cell: {key: val for key, val in magics.items()
if key in magic_commands}
for line_cell, magics in cls.magics.items()}
ip.magics_manager.register(cls)