Skip to content
Closed
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
5 changes: 3 additions & 2 deletions docs/examples/toymodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import math

from qcodes import MockInstrument, MockModel, Parameter, Loop, DataArray
from qcodes import MockInstrument, Parameter, Loop, DataArray
from qcodes.instrument.mock import SingleMockModel
from qcodes.utils.validators import Numbers
from qcodes.instrument.mock import ArrayGetter

class AModel(MockModel):
class AModel(SingleMockModel):
def __init__(self):
self._gates = [0.0, 0.0, 0.0]
self._excitation = 0.1
Expand Down
104 changes: 104 additions & 0 deletions qcodes/instrument/mock.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Mock instruments for testing purposes."""
import time
from datetime import datetime
from uuid import uuid4

from .base import Instrument
from .parameter import MultiParameter
Expand Down Expand Up @@ -283,6 +284,109 @@ def _delattr(self, attr):
"""
self.ask('method_call', 'delattr', attr)


# Model is purely in service of mock instruments which *are* tested
# so coverage testing this (by running it locally) would be a waste.
class SingleMockModel: # pragma: no cover

"""
Base class for models to connect to various MockInstruments.

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:
- ``<instrument>_set(param, value)``: set a parameter on the model
- ``<instrument>_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.

"""

def __init__(self, name='Model-{:.7s}'):

self.uuid = uuid4().hex
self.name = name.format(self.uuid)

def handle_cmd(self, cmd):
"""
Handler for all model queries.

Args:
cmd (str): Can take several forms:

- '<instrument>:<parameter>?':
calls ``self.<instrument>_get(<parameter>)`` and forwards
the return value.
- '<instrument>:<parameter>:<value>':
calls ``self.<instrument>_set(<parameter>, <value>)``
- '<instrument>:<parameter>'.
calls ``self.<instrument>_set(<parameter>, None)``

Returns:
Union(str, None): The parameter value, if ``cmd`` has the form
'<instrument>:<parameter>?', 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)

def ask(self, func_name, *args, **kwargs):
return self.handle_cmd(args[0])

def write(self, func_name, *args, **kwargs):
self.handle_cmd(args[0])

class ArrayGetter(MultiParameter):
"""
Example parameter that just returns a single array
Expand Down
6 changes: 4 additions & 2 deletions qcodes/plots/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ def get_default_title(self):
title_parts.append(location)
return ', '.join(title_parts)

def get_label(self, data_array):
@staticmethod
def get_label(data_array):
"""
Look for a label in data_array falling back on name.

Expand All @@ -174,7 +175,8 @@ def get_label(self, data_array):
return (getattr(data_array, 'label', '') or
getattr(data_array, 'name', ''))

def expand_trace(self, args, kwargs):
@staticmethod
def expand_trace(args, kwargs):
"""
Complete the x, y (and possibly z) data definition for a trace.

Expand Down
141 changes: 141 additions & 0 deletions qcodes/plots/qcmatplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
"""
from collections import Mapping

from qtpy import QtWidgets

import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
from matplotlib.widgets import Cursor
import mplcursors


import numpy as np
from numpy.ma import masked_invalid, getmask

Expand Down Expand Up @@ -217,3 +223,138 @@ def save(self, filename=None):
default = "{}.png".format(self.get_default_title())
filename = filename or default
self.fig.savefig(filename)


class ClickWidget:
def __init__(self, dataset):
self._data = {}
BasePlot.expand_trace(args=[dataset], kwargs=self._data)
self._data['xlabel'] = BasePlot.get_label(self._data['x'])
self._data['ylabel'] = BasePlot.get_label(self._data['y'])
self._data['zlabel'] = BasePlot.get_label(self._data['z'])
self._data['xaxis'] = self._data['x'].ndarray[0, :]
self._data['yaxis'] = self._data['y'].ndarray

self.fig = plt.figure()

self._lines = []
self._datacursor = []
self._cid = 0

hbox = QtWidgets.QHBoxLayout()
self.fig.canvas.setLayout(hbox)
hspace = QtWidgets.QSpacerItem(0,
0,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
vspace = QtWidgets.QSpacerItem(0,
0,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
hbox.addItem(hspace)

vbox = QtWidgets.QVBoxLayout()
self.crossbtn = QtWidgets.QCheckBox('Cross section')
self.crossbtn.setToolTip("Display extra subplots with selectable cross sections "
"or sums along axis.")
self.sumbtn = QtWidgets.QCheckBox('Sum')
self.sumbtn.setToolTip("Display sums or cross sections.")

self.crossbtn.toggled.connect(self.toggle_cross)
self.sumbtn.toggled.connect(self.toggle_sum)
self.toggle_cross()
self.toggle_sum()

vbox.addItem(vspace)
vbox.addWidget(self.crossbtn)
vbox.addWidget(self.sumbtn)

hbox.addLayout(vbox)

def toggle_cross(self):
self.remove_plots()
self.fig.clear()
if self._cid:
self.fig.canvas.mpl_disconnect(self._cid)
if self.crossbtn.isChecked():
self.sumbtn.setEnabled(True)
self.ax = np.empty((2, 2), dtype='O')
self.ax[0, 0] = self.fig.add_subplot(2, 2, 1)
self.ax[0, 1] = self.fig.add_subplot(2, 2, 2)
self.ax[1, 0] = self.fig.add_subplot(2, 2, 3)
self._cid = self.fig.canvas.mpl_connect('button_press_event', self._click)
self._cursor = Cursor(self.ax[0, 0], useblit=True, color='black')
self.toggle_sum()
else:
self.sumbtn.setEnabled(False)
self.ax = np.empty((1, 1), dtype='O')
self.ax[0, 0] = self.fig.add_subplot(1, 1, 1)
self.ax[0, 0].pcolormesh(self._data['x'],
self._data['y'],
self._data['z'])
self.ax[0, 0].set_xlabel(self._data['xlabel'])
self.ax[0, 0].set_ylabel(self._data['ylabel'])
self.fig.tight_layout(rect=(0, 0.07, 0.9, 1))
self.fig.canvas.draw_idle()

def toggle_sum(self):
self.remove_plots()
if not self.crossbtn.isChecked():
return
if self.sumbtn.isChecked():
self._cursor.set_active(False)
self.ax[1, 0].set_ylim(0, self._data['z'].sum(axis=0).max() * 1.05)
self.ax[0, 1].set_xlim(0, self._data['z'].sum(axis=1).max() * 1.05)
self.ax[1, 0].set_xlabel(self._data['xlabel'])
self.ax[1, 0].set_ylabel("sum of " + self._data['zlabel'])
self.ax[0, 1].set_xlabel("sum of " + self._data['zlabel'])
self.ax[0, 1].set_ylabel(self._data['ylabel'])
self._lines.append(self.ax[0, 1].plot(self._data['z'].sum(axis=1),
self._data['yaxis'],
color='C0',
marker='.')[0])
self.ax[0, 1].set_title("")
self._lines.append(self.ax[1, 0].plot(self._data['xaxis'],
self._data['z'].sum(axis=0),
color='C0',
marker='.')[0])
self.ax[1, 0].set_title("")
self._datacursor = mplcursors.cursor(self._lines, multiple=False)
else:
self._cursor.set_active(True)
self.ax[1, 0].set_xlabel(self._data['xlabel'])
self.ax[1, 0].set_ylabel(self._data['zlabel'])
self.ax[0, 1].set_xlabel(self._data['zlabel'])
self.ax[0, 1].set_ylabel(self._data['ylabel'])
self.ax[1, 0].set_ylim(0, self._data['z'].max() * 1.05)
self.ax[0, 1].set_xlim(0, self._data['z'].max() * 1.05)
self.fig.canvas.draw_idle()

def remove_plots(self):
for line in self._lines:
line.remove()
self._lines = []
if self._datacursor:
self._datacursor.remove()

def _click(self, event):

if event.inaxes == self.ax[0, 0] and not self.sumbtn.isChecked():
xpos = (abs(self._data['xaxis'] - event.xdata)).argmin()
ypos = (abs(self._data['yaxis'] - event.ydata)).argmin()
self.remove_plots()

self._lines.append(self.ax[0, 1].plot(self._data['z'][:, xpos],
self._data['yaxis'],
color='C0',
marker='.')[0])
self.ax[0,1].set_title("{} = {} ".format(self._data['xlabel'], self._data['xaxis'][xpos]),
fontsize='small')
self._lines.append(self.ax[1, 0].plot(self._data['xaxis'],
self._data['z'][ypos, :],
color='C0',
marker='.')[0])
self.ax[1, 0].set_title("{} = {} ".format(self._data['ylabel'], self._data['yaxis'][ypos]),
fontsize='small')
self._datacursor = mplcursors.cursor(self._lines, multiple=False)
self.fig.canvas.draw()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
jupyter==1.0.0
numpy==1.11.2
matplotlib==1.5.3
mplcursors==0.1
pyqtgraph==0.10.0
PyVISA==1.8
PyQt5==5.7.1
Expand Down