gui: experiment docks (WIP)

This commit is contained in:
Sebastien Bourdeauducq 2015-11-27 19:30:05 +08:00
parent 6671bb33a4
commit c382fac8f2
9 changed files with 448 additions and 377 deletions

View File

@ -13,7 +13,8 @@ from pyqtgraph import dockarea
from artiq.tools import *
from artiq.protocols.pc_rpc import AsyncioClient
from artiq.gui.models import ModelSubscriber
from artiq.gui import state, explorer, moninj, datasets, schedule, log, console
from artiq.gui import (state, experiments, explorer,
moninj, datasets, schedule, log, console)
def get_argparser():
@ -85,23 +86,26 @@ def main():
# initialize main window
win = MainWindow(app, args.server)
area = dockarea.DockArea()
smgr.register(area)
dock_area = dockarea.DockArea()
smgr.register(dock_area)
smgr.register(win)
win.setCentralWidget(area)
win.setCentralWidget(dock_area)
status_bar = QtGui.QStatusBar()
status_bar.showMessage("Connected to {}".format(args.server))
win.setStatusBar(status_bar)
# create UI components
d_explorer = explorer.ExplorerDock(win, status_bar,
expmgr = experiments.ExperimentManager(status_bar, dock_area,
sub_clients["explist"],
sub_clients["schedule"],
rpc_clients["schedule"])
d_explorer = explorer.ExplorerDock(win, status_bar, expmgr,
sub_clients["explist"],
sub_clients["schedule"],
rpc_clients["schedule"],
rpc_clients["repository"])
smgr.register(d_explorer)
d_datasets = datasets.DatasetsDock(win, area, sub_clients["datasets"])
d_datasets = datasets.DatasetsDock(win, dock_area, sub_clients["datasets"])
smgr.register(d_datasets)
if os.name != "nt":
@ -112,7 +116,7 @@ def main():
d_schedule = schedule.ScheduleDock(
status_bar, rpc_clients["schedule"], sub_clients["schedule"])
logmgr = log.LogDockManager(area, sub_clients["log"])
logmgr = log.LogDockManager(dock_area, sub_clients["log"])
smgr.register(logmgr)
d_console = console.ConsoleDock(sub_clients["datasets"],
@ -120,14 +124,14 @@ def main():
# lay out docks
if os.name != "nt":
area.addDock(d_ttl_dds.dds_dock, "top")
area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock)
area.addDock(d_datasets, "above", d_ttl_dds.ttl_dock)
dock_area.addDock(d_ttl_dds.dds_dock, "top")
dock_area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock)
dock_area.addDock(d_datasets, "above", d_ttl_dds.ttl_dock)
else:
area.addDock(d_datasets, "top")
area.addDock(d_explorer, "above", d_datasets)
area.addDock(d_console, "bottom")
area.addDock(d_schedule, "above", d_console)
dock_area.addDock(d_datasets, "top")
dock_area.addDock(d_explorer, "above", d_datasets)
dock_area.addDock(d_console, "bottom")
dock_area.addDock(d_schedule, "above", d_console)
# load/initialize state
smgr.load()
@ -137,7 +141,7 @@ def main():
# create first log dock if not already in state
d_log0 = logmgr.first_log_dock()
if d_log0 is not None:
area.addDock(d_log0, "right", d_explorer)
dock_area.addDock(d_log0, "right", d_explorer)
# run
win.show()

351
artiq/gui/experiments.py Normal file
View File

@ -0,0 +1,351 @@
import logging
import asyncio
from functools import partial
from collections import OrderedDict
from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from artiq.gui.scan import ScanController
logger = logging.getLogger(__name__)
class _StringEntry(QtGui.QLineEdit):
def __init__(self, argdesc):
QtGui.QLineEdit.__init__(self)
@staticmethod
def default(argdesc):
return ""
def get_argument_value(self):
return self.text()
def set_argument_value(self, value):
self.setText(value)
class _BooleanEntry(QtGui.QCheckBox):
def __init__(self, argdesc):
QtGui.QCheckBox.__init__(self)
@staticmethod
def default(argdesc):
return False
def get_argument_value(self):
return self.isChecked()
def set_argument_value(self, value):
self.setChecked(value)
class _EnumerationEntry(QtGui.QComboBox):
def __init__(self, argdesc):
QtGui.QComboBox.__init__(self)
self.choices = argdesc["choices"]
self.addItems(self.choices)
@staticmethod
def default(argdesc):
return argdesc["choices"][0]
def get_argument_value(self):
return self.choices[self.currentIndex()]
def set_argument_value(self, value):
idx = self.choices.index(value)
self.setCurrentIndex(idx)
class _NumberEntry(QtGui.QDoubleSpinBox):
def __init__(self, argdesc):
QtGui.QDoubleSpinBox.__init__(self)
self.scale = argdesc["scale"]
self.setDecimals(argdesc["ndecimals"])
self.setSingleStep(argdesc["step"]/self.scale)
if argdesc["min"] is not None:
self.setMinimum(argdesc["min"]/self.scale)
else:
self.setMinimum(float("-inf"))
if argdesc["max"] is not None:
self.setMaximum(argdesc["max"]/self.scale)
else:
self.setMaximum(float("inf"))
if argdesc["unit"]:
self.setSuffix(" " + argdesc["unit"])
@staticmethod
def default(argdesc):
return 0.0
def get_argument_value(self):
return self.value()*self.scale
def set_argument_value(self, value):
self.setValue(value/self.scale)
_argty_to_entry = {
"PYONValue": _StringEntry,
"BooleanValue": _BooleanEntry,
"EnumerationValue": _EnumerationEntry,
"NumberValue": _NumberEntry,
"StringValue": _StringEntry,
"Scannable": ScanController
}
class _ArgumentEditor(QtGui.QTreeWidget):
def __init__(self, arguments):
QtGui.QTreeWidget.__init__(self)
self.setColumnCount(2)
self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
self.header().setVisible(False)
self.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self._groups = dict()
self._args_to_entries = dict()
if not arguments:
self.addTopLevelItem(QtGui.QTreeWidgetItem(["No arguments", ""]))
for n, (name, (argdesc, group, value)) in enumerate(arguments.items()):
entry = _argty_to_entry[argdesc["ty"]](argdesc)
entry.set_argument_value(value)
self._args_to_entries[name] = entry
widget_item = QtGui.QTreeWidgetItem([name, ""])
if group is None:
self.addTopLevelItem(widget_item)
else:
self._get_group(group).addChild(widget_item)
self.setItemWidget(widget_item, 1, entry)
def _get_group(self, name):
if name in self._groups:
return self._groups[name]
group = QtGui.QTreeWidgetItem([name, ""])
for c in 0, 1:
group.setBackground(c, QtGui.QBrush(QtGui.QColor(100, 100, 100)))
group.setForeground(c, QtGui.QBrush(QtGui.QColor(220, 220, 255)))
font = group.font(c)
font.setBold(True)
group.setFont(c, font)
self.addTopLevelItem(group)
self._groups[name] = group
return group
def get_argument_values(self):
return {arg: entry.get_argument_value()
for arg, entry in self._args_to_entries.items()}
def save_state(self):
expanded = []
for k, v in self._groups.items():
if v.isExpanded():
expanded.append(k)
argument_values = self.get_argument_values()
return {
"expanded": expanded,
"argument_values": argument_values
}
def restore_state(self, state):
for arg, value in state["argument_values"].items():
try:
entry = self._args_to_entries[arg]
entry.set_argument_value(value)
except:
logger.warning("failed to restore value of argument %s", arg,
exc_info=True)
for e in state["expanded"]:
try:
self._groups[e].setExpanded(True)
except KeyError:
pass
class _ExperimentDock(dockarea.Dock):
def __init__(self, manager, expname):
dockarea.Dock.__init__(self, "Experiment: " + expname,
closable=True, size=(1500, 500))
self.manager = manager
self.expname = expname
self.argeditor = _ArgumentEditor(
manager.get_submission_arguments(expname))
self.addWidget(self.argeditor, 0, 0, colspan=4)
self.datetime = QtGui.QDateTimeEdit()
self.datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
self.datetime.setDate(QtCore.QDate.currentDate())
self.datetime.dateTimeChanged.connect(
lambda: self.datetime_en.setChecked(True))
self.datetime_en = QtGui.QCheckBox("Due date:")
self.addWidget(self.datetime_en, 1, 0, colspan=2)
self.addWidget(self.datetime, 1, 2, colspan=2)
self.pipeline = QtGui.QLineEdit()
self.pipeline.setText("main")
self.addWidget(QtGui.QLabel("Pipeline:"), 2, 0, colspan=2)
self.addWidget(self.pipeline, 2, 2, colspan=2)
self.priority = QtGui.QSpinBox()
self.priority.setRange(-99, 99)
self.addWidget(QtGui.QLabel("Priority:"), 3, 0)
self.addWidget(self.priority, 3, 1)
self.flush = QtGui.QCheckBox("Flush")
self.flush.setToolTip("Flush the pipeline before starting the experiment")
self.addWidget(self.flush, 3, 2)
self.log_level = QtGui.QComboBox()
self.log_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
self.log_level.setCurrentIndex(1)
self.log_level.setToolTip("Minimum level for log entry production")
self.addWidget(self.log_level, 3, 3)
submit = QtGui.QPushButton("Submit")
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
self.addWidget(submit, 4, 0, colspan=4)
submit.clicked.connect(self.submit_clicked)
def submit_clicked(self):
self.manager.submit(self.expname)
class ExperimentManager:
def __init__(self, status_bar, dock_area,
explist_sub, schedule_sub,
schedule_ctl):
self.status_bar = status_bar
self.dock_area = dock_area
self.schedule_ctl = schedule_ctl
self.submission_scheduling = dict()
self.submission_options = dict()
self.submission_arguments = dict()
self.explist = dict()
explist_sub.add_setmodel_callback(self.set_explist_model)
self.schedule = dict()
schedule_sub.add_setmodel_callback(self.set_schedule_model)
self.open_experiments = dict()
def set_explist_model(self, model):
self.explist = model.backing_store
def set_schedule_model(self, model):
self.schedule = model.backing_store
def get_submission_scheduling(self, expname):
if expname in self.submission_scheduling:
return self.submission_scheduling[expname]
else:
scheduling = {
"pipeline_name": "main",
"priority": 0,
"due_date": None,
"flush": False
}
self.submission_scheduling[expname] = scheduling
return scheduling
def get_submission_options(self, expname):
if expname in self.submission_options:
return self.submission_options[expname]
else:
options = {
"log_level": logging.WARNING,
"repo_rev": None
}
self.submission_options = options
return options
def get_submission_arguments(self, expname):
if expname in self.submission_arguments:
return self.submission_arguments[expname]
else:
arguments = OrderedDict()
arginfo = self.explist[expname]["arguments"]
for name, (procdesc, group) in arginfo:
argdesc = dict(procdesc)
if "default" in argdesc:
value = argdesc["default"]
del argdesc["default"]
else:
value = _argty_to_entry[argdesc["ty"]].default(argdesc)
arguments[name] = argdesc, group, value
self.submission_arguments[expname] = arguments
return arguments
def open_experiment(self, expname):
if expname in self.open_experiments:
return
dock = _ExperimentDock(self, expname)
self.open_experiments[expname] = dock
self.dock_area.addDock(dock)
self.dock_area.floatDock(dock)
dock.sigClosed.connect(partial(self.on_dock_closed, expname))
def on_dock_closed(self, expname):
del self.open_experiments[expname]
async def _submit_task(self, *args):
rid = await self.schedule_ctl.submit(*args)
self.status_bar.showMessage("Submitted RID {}".format(rid))
def submit(self, expname):
expinfo = self.explist[expname]
scheduling = self.get_submission_scheduling(expname)
options = self.get_submission_options(expname)
arguments = self.get_submission_arguments(expname)
argument_values = {k: v[2] for k, v in arguments.items()}
expid = {
"log_level": options["log_level"],
"repo_rev": options["repo_rev"],
"file": expinfo["file"],
"class_name": expinfo["class_name"],
"arguments": argument_values,
}
asyncio.ensure_future(self._submit_task(
scheduling["pipeline_name"],
expid,
scheduling["priority"], scheduling["due_date"],
scheduling["flush"]))
async def _request_term_multiple(self, rids):
for rid in rids:
try:
await self.schedule_ctl.request_termination(rid)
except:
# May happen if the experiment has terminated by itself
# while we were terminating others.
logger.debug("failed to request termination of RID %d",
rid, exc_info=True)
def request_inst_term(self, expname):
expinfo = self.explist[expname]
rids = []
for rid, desc in self.schedule.items():
expid = desc["expid"]
if ("repo_rev" in expid # only consider runs from repository
and expid["file"] == expinfo["file"]
and expid["class_name"] == expinfo["class_name"]):
rids.append(rid)
asyncio.ensure_future(self._request_term_multiple(rids))
def save_state(self):
return dict()
def restore_state(self):
if self.open_experiments:
raise NotImplementedError

View File

@ -5,284 +5,63 @@ from quamash import QtGui, QtCore
from pyqtgraph import dockarea
from pyqtgraph import LayoutWidget
from artiq.protocols import pyon
from artiq.gui.models import DictSyncTreeSepModel
from artiq.gui.scan import ScanController
from artiq.gui.shortcuts import ShortcutManager
class Model(DictSyncTreeSepModel):
def __init__(self, init):
self.explorer = None
DictSyncTreeSepModel.__init__(self,
"/",
["Experiment"],
init)
DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init)
def __setitem__(self, k, v):
DictSyncTreeSepModel.__setitem__(self, k, v)
if self.explorer is not None:
if k == self.explorer.selected_key:
self.explorer.update_selection(k, k)
class _FreeValueEntry(QtGui.QLineEdit):
def __init__(self, procdesc):
QtGui.QLineEdit.__init__(self)
if "default" in procdesc:
self.set_argument_value(procdesc["default"])
def get_argument_value(self):
return pyon.decode(self.text())
def set_argument_value(self, value):
self.setText(pyon.encode(value))
class _BooleanEntry(QtGui.QCheckBox):
def __init__(self, procdesc):
QtGui.QCheckBox.__init__(self)
if "default" in procdesc:
self.set_argument_value(procdesc["default"])
def get_argument_value(self):
return self.isChecked()
def set_argument_value(self, value):
self.setChecked(value)
class _EnumerationEntry(QtGui.QComboBox):
def __init__(self, procdesc):
QtGui.QComboBox.__init__(self)
self.choices = procdesc["choices"]
self.addItems(self.choices)
if "default" in procdesc:
self.set_argument_value(procdesc["default"])
def get_argument_value(self):
return self.choices[self.currentIndex()]
def set_argument_value(self, value):
idx = self.choices.index(value)
self.setCurrentIndex(idx)
class _NumberEntry(QtGui.QDoubleSpinBox):
def __init__(self, procdesc):
QtGui.QDoubleSpinBox.__init__(self)
self.scale = procdesc["scale"]
self.setDecimals(procdesc["ndecimals"])
self.setSingleStep(procdesc["step"]/self.scale)
if procdesc["min"] is not None:
self.setMinimum(procdesc["min"]/self.scale)
else:
self.setMinimum(float("-inf"))
if procdesc["max"] is not None:
self.setMaximum(procdesc["max"]/self.scale)
else:
self.setMaximum(float("inf"))
if procdesc["unit"]:
self.setSuffix(" " + procdesc["unit"])
if "default" in procdesc:
self.set_argument_value(procdesc["default"])
def get_argument_value(self):
return self.value()*self.scale
def set_argument_value(self, value):
self.setValue(value/self.scale)
class _StringEntry(QtGui.QLineEdit):
def __init__(self, procdesc):
QtGui.QLineEdit.__init__(self)
if "default" in procdesc:
self.set_argument_value(procdesc["default"])
def get_argument_value(self):
return self.text()
def set_argument_value(self, value):
self.setText(value)
_procty_to_entry = {
"FreeValue": _FreeValueEntry,
"BooleanValue": _BooleanEntry,
"EnumerationValue": _EnumerationEntry,
"NumberValue": _NumberEntry,
"StringValue": _StringEntry,
"Scannable": ScanController
}
class _ArgumentEditor(QtGui.QTreeWidget):
def __init__(self, main_window):
QtGui.QTreeWidget.__init__(self)
self.setColumnCount(2)
self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
self.header().setVisible(False)
self.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.main_window = main_window
self._groups = dict()
self.set_arguments([])
def clear(self):
QtGui.QTreeWidget.clear(self)
self._groups.clear()
def _get_group(self, name):
if name in self._groups:
return self._groups[name]
group = QtGui.QTreeWidgetItem([name, ""])
for c in 0, 1:
group.setBackground(c, QtGui.QBrush(QtGui.QColor(100, 100, 100)))
group.setForeground(c, QtGui.QBrush(QtGui.QColor(220, 220, 255)))
font = group.font(c)
font.setBold(True)
group.setFont(c, font)
self.addTopLevelItem(group)
self._groups[name] = group
return group
def set_arguments(self, arguments):
self.clear()
if not arguments:
self.addTopLevelItem(QtGui.QTreeWidgetItem(["No arguments", ""]))
self._args_to_entries = dict()
for n, (name, (procdesc, group)) in enumerate(arguments):
entry = _procty_to_entry[procdesc["ty"]](procdesc)
self._args_to_entries[name] = entry
widget_item = QtGui.QTreeWidgetItem([name, ""])
if group is None:
self.addTopLevelItem(widget_item)
else:
self._get_group(group).addChild(widget_item)
self.setItemWidget(widget_item, 1, entry)
def get_argument_values(self, show_error_message):
r = dict()
for arg, entry in self._args_to_entries.items():
try:
r[arg] = entry.get_argument_value()
except Exception as e:
if show_error_message:
msgbox = QtGui.QMessageBox(self.main_window)
msgbox.setWindowTitle("Error")
msgbox.setText("Failed to obtain value for argument '{}':\n{}"
.format(arg, str(e)))
msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
msgbox.show()
return None
return r
def set_argument_values(self, arguments, ignore_errors):
for arg, value in arguments.items():
try:
entry = self._args_to_entries[arg]
entry.set_argument_value(value)
except:
if not ignore_errors:
raise
def save_state(self):
expanded = []
for k, v in self._groups.items():
if v.isExpanded():
expanded.append(k)
argument_values = self.get_argument_values(False)
return {
"expanded": expanded,
"argument_values": argument_values
}
def restore_state(self, state):
self.set_argument_values(state["argument_values"], True)
for e in state["expanded"]:
try:
self._groups[e].setExpanded(True)
except KeyError:
pass
# TODO
#if self.explorer is not None and k == self.explorer.selected_key:
# self.explorer.update_selection(k, k)
class ExplorerDock(dockarea.Dock):
def __init__(self, main_window, status_bar,
explist_sub, schedule_sub,
schedule_ctl, repository_ctl):
def __init__(self, main_window, status_bar, exp_manager,
explist_sub, schedule_ctl, repository_ctl):
dockarea.Dock.__init__(self, "Explorer", size=(1500, 500))
self.main_window = main_window
self.status_bar = status_bar
self.schedule_sub = schedule_sub
self.exp_manager = exp_manager
self.schedule_ctl = schedule_ctl
self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
self.addWidget(self.splitter)
grid = LayoutWidget()
self.splitter.addWidget(grid)
self.el = QtGui.QTreeView()
self.el.setHeaderHidden(True)
self.el.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.el.selectionChanged = self._selection_changed
self.selected_key = None
grid.addWidget(self.el, 0, 0, colspan=4)
self.addWidget(self.el, 0, 0, colspan=2)
self.el.doubleClicked.connect(self.open_clicked)
self.datetime = QtGui.QDateTimeEdit()
self.datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
self.datetime.setDate(QtCore.QDate.currentDate())
self.datetime.dateTimeChanged.connect(self.enable_duedate)
self.datetime_en = QtGui.QCheckBox("Due date:")
grid.addWidget(self.datetime_en, 1, 0, colspan=2)
grid.addWidget(self.datetime, 1, 2, colspan=2)
self.pipeline = QtGui.QLineEdit()
self.pipeline.setText("main")
grid.addWidget(QtGui.QLabel("Pipeline:"), 2, 0, colspan=2)
grid.addWidget(self.pipeline, 2, 2, colspan=2)
self.priority = QtGui.QSpinBox()
self.priority.setRange(-99, 99)
grid.addWidget(QtGui.QLabel("Priority:"), 3, 0)
grid.addWidget(self.priority, 3, 1)
self.flush = QtGui.QCheckBox("Flush")
self.flush.setToolTip("Flush the pipeline before starting the experiment")
grid.addWidget(self.flush, 3, 2)
self.log_level = QtGui.QComboBox()
self.log_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
self.log_level.setCurrentIndex(1)
self.log_level.setToolTip("Minimum level for log entry production")
grid.addWidget(self.log_level, 3, 3)
open = QtGui.QPushButton("Open")
open.setToolTip("Open the selected experiment (Return)")
self.addWidget(open, 1, 0)
open.clicked.connect(self.open_clicked)
submit = QtGui.QPushButton("Submit")
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
grid.addWidget(submit, 4, 0, colspan=4)
self.addWidget(submit, 1, 1)
submit.clicked.connect(self.submit_clicked)
self.argeditor = _ArgumentEditor(self.main_window)
self.splitter.addWidget(self.argeditor)
self.splitter.setSizes([grid.minimumSizeHint().width(), 1000])
self.argeditor_states = dict()
self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model)
self.shortcuts = ShortcutManager(self.main_window, self)
self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
open_action = QtGui.QAction("Open", self.el)
open_action.triggered.connect(self.open_clicked)
open_action.setShortcut("RETURN")
self.el.addAction(open_action)
submit_action = QtGui.QAction("Submit", self.el)
submit_action.triggered.connect(self.submit_clicked)
submit_action.setShortcut("CTRL+RETURN")
self.el.addAction(submit_action)
reqterm_action = QtGui.QAction("Request termination of instances", self.el)
reqterm_action.triggered.connect(self.request_inst_term)
reqterm_action.triggered.connect(self.reqterm_clicked)
reqterm_action.setShortcut("CTRL+BACKSPACE")
self.el.addAction(reqterm_action)
@ -306,118 +85,40 @@ class ExplorerDock(dockarea.Dock):
self.explist_model = model
self.el.setModel(model)
def update_selection(self, selected, deselected):
if deselected is not None:
self.argeditor_states[deselected] = self.argeditor.save_state()
if selected is not None:
expinfo = self.explist_model.backing_store[selected]
self.argeditor.set_arguments(expinfo["arguments"])
if selected in self.argeditor_states:
self.argeditor.restore_state(self.argeditor_states[selected])
self.splitter.insertWidget(1, self.argeditor)
self.selected_key = selected
def _sel_to_key(self, selection):
selection = selection.indexes()
def _get_selected_expname(self):
selection = self.el.selectedIndexes()
if selection:
return self.explist_model.index_to_key(selection[0])
else:
return None
def _selection_changed(self, selected, deselected):
self.update_selection(self._sel_to_key(selected),
self._sel_to_key(deselected))
def open_clicked(self):
expname = self._get_selected_expname()
if expname is not None:
self.exp_manager.open_experiment(expname)
def submit_clicked(self):
expname = self._get_selected_expname()
if expname is not None:
self.exp_manager.submit(expname)
def reqterm_clicked(self):
expname = self._get_selected_expname()
if expname is not None:
self.exp_manager.request_inst_term(expname)
def save_state(self):
idx = self.el.selectedIndexes()
if idx:
key = self.explist_model.index_to_key(idx[0])
if key is not None:
self.argeditor_states[key] = self.argeditor.save_state()
return {
"argeditor": self.argeditor_states,
"shortcuts": self.shortcuts.save_state()
}
def restore_state(self, state):
try:
argeditor_states = state["argeditor"]
shortcuts_state = state["shortcuts"]
except KeyError:
return
self.argeditor_states = argeditor_states
self.shortcuts.restore_state(shortcuts_state)
def enable_duedate(self):
self.datetime_en.setChecked(True)
async def submit_task(self, pipeline_name, file, class_name, arguments,
priority, due_date, flush):
expid = {
"log_level": getattr(logging, self.log_level.currentText()),
"repo_rev": None,
"file": file,
"class_name": class_name,
"arguments": arguments,
}
rid = await self.schedule_ctl.submit(pipeline_name, expid,
priority, due_date, flush)
self.status_bar.showMessage("Submitted RID {}".format(rid))
def submit(self, pipeline, key, priority, due_date, flush):
# TODO: refactor explorer and cleanup.
# Argument editors should immediately modify the global state.
expinfo = self.explist_model.backing_store[key]
if key == self.selected_key:
arguments = self.argeditor.get_argument_values(True)
if arguments is None:
# There has been an error. Displaying the error message box
# was done by argeditor.
return
else:
try:
arguments = self.argeditor_states[key]["argument_values"]
except KeyError:
arguments = dict()
asyncio.ensure_future(self.submit_task(pipeline, expinfo["file"],
expinfo["class_name"],
arguments, priority, due_date,
flush))
def submit_clicked(self):
if self.selected_key is not None:
if self.datetime_en.isChecked():
due_date = self.datetime.dateTime().toMSecsSinceEpoch()/1000
else:
due_date = None
self.submit(self.pipeline.text(),
self.selected_key,
self.priority.value(),
due_date,
self.flush.isChecked())
async def request_term_multiple(self, rids):
for rid in rids:
try:
await self.schedule_ctl.request_termination(rid)
except:
pass
def request_inst_term(self):
if self.selected_key is not None:
expinfo = self.explist_model.backing_store[self.selected_key]
if self.schedule_sub.model is not None:
current_schedule = self.schedule_sub.model.backing_store
rids = []
for rid, desc in current_schedule.items():
expid = desc["expid"]
if ("repo_rev" in expid # only consider runs from repository
and expid["file"] == expinfo["file"]
and expid["class_name"] == expinfo["class_name"]):
rids.append(rid)
asyncio.ensure_future(self.request_term_multiple(rids))
def edit_shortcuts(self):
experiments = sorted(self.explist_model.backing_store.keys())
self.shortcuts.edit(experiments)

View File

@ -56,18 +56,18 @@ class _Range(LayoutWidget):
class ScanController(LayoutWidget):
def __init__(self, procdesc):
def __init__(self, argdesc):
LayoutWidget.__init__(self)
self.stack = QtGui.QStackedWidget()
self.addWidget(self.stack, 1, 0, colspan=4)
self.scale = procdesc["scale"]
self.scale = argdesc["scale"]
gmin, gmax = procdesc["global_min"], procdesc["global_max"]
gstep = procdesc["global_step"]
unit = procdesc["unit"]
ndecimals = procdesc["ndecimals"]
gmin, gmax = argdesc["global_min"], argdesc["global_max"]
gstep = argdesc["global_step"]
unit = argdesc["unit"]
ndecimals = argdesc["ndecimals"]
self.v_noscan = QtGui.QDoubleSpinBox()
self.v_noscan.setDecimals(ndecimals)
@ -110,10 +110,9 @@ class ScanController(LayoutWidget):
radiobuttons.addButton(b)
b.toggled.connect(self.select_page)
if "default" in procdesc:
self.set_argument_value(procdesc["default"])
else:
self.noscan.setChecked(True)
@staticmethod
def default(argdesc):
return {"ty": "NoScan", "value": 0.0}
def select_page(self):
if self.noscan.isChecked():

View File

@ -1,9 +1,11 @@
from collections import OrderedDict
from inspect import isclass
from artiq.protocols import pyon
__all__ = ["NoDefault",
"FreeValue", "BooleanValue", "EnumerationValue",
"PYONValue", "BooleanValue", "EnumerationValue",
"NumberValue", "StringValue",
"HasEnvironment",
"Experiment", "EnvExperiment", "is_experiment"]
@ -40,9 +42,16 @@ class _SimpleArgProcessor:
return d
class FreeValue(_SimpleArgProcessor):
"""An argument that can be an arbitrary Python value."""
pass
class PYONValue(_SimpleArgProcessor):
"""An argument that can be any PYON-serializable value."""
def process(self, x):
return pyon.decode(x)
def describe(self):
d = {"ty": self.__class__.__name__}
if hasattr(self, "default_value"):
d["default"] = pyon.encode(self.default_value)
return d
class BooleanValue(_SimpleArgProcessor):
@ -166,7 +175,7 @@ class HasEnvironment:
if self.__parent is not None and key not in self.__kwargs:
return self.__parent.get_argument(key, processor, group)
if processor is None:
processor = FreeValue()
processor = PYONValue()
self.requested_args[key] = processor, group
try:
argval = self.__kwargs[key]

View File

@ -37,7 +37,7 @@ class SubComponent2(HasEnvironment):
class ArgumentsDemo(EnvExperiment):
def build(self):
self.setattr_argument("free_value", FreeValue(None))
self.setattr_argument("pyon_value", PYONValue(None))
self.setattr_argument("number", NumberValue(42e-6,
unit="us", scale=1e-6,
ndecimals=4))
@ -59,7 +59,7 @@ class ArgumentsDemo(EnvExperiment):
logging.info("logging test: info")
logging.debug("logging test: debug")
print(self.free_value)
print(self.pyon_value)
print(self.boolean)
print(self.enum)
print(self.number)

View File

@ -13,8 +13,8 @@ class PhotonHistogram(EnvExperiment):
self.setattr_device("bdd_sw")
self.setattr_device("pmt")
self.setattr_argument("nbins", FreeValue(100))
self.setattr_argument("repeats", FreeValue(100))
self.setattr_argument("nbins", NumberValue(100))
self.setattr_argument("repeats", NumberValue(100))
self.setattr_dataset("cool_f", 230*MHz)
self.setattr_dataset("detect_f", 220*MHz)

View File

@ -4,10 +4,17 @@ from artiq import *
class BlinkForever(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("led")
self.setattr_device("ttl0")
self.setattr_device("ttl1")
def hello(self, i):
print("Hello world", i)
@kernel
def run(self):
while True:
self.led.pulse(100*ms)
delay(100*ms)
for i in range(80000):
self.ttl0.pulse(40*us)
self.ttl1.pulse(40*us)
delay(40*us)
for i in range(7):
self.hello(i)

View File

@ -22,10 +22,10 @@ class Transport(EnvExperiment):
self.setattr_device("pmt")
self.setattr_device("electrodes")
self.setattr_argument("wait_at_stop", FreeValue(100*us))
self.setattr_argument("speed", FreeValue(1.5))
self.setattr_argument("repeats", FreeValue(100))
self.setattr_argument("nbins", FreeValue(100))
self.setattr_argument("wait_at_stop", NumberValue(100*us))
self.setattr_argument("speed", NumberValue(1.5))
self.setattr_argument("repeats", NumberValue(100))
self.setattr_argument("nbins", NumberValue(100))
def calc_waveforms(self, stop):
t = transport_data["t"][:stop]*self.speed