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.tools import *
from artiq.protocols.pc_rpc import AsyncioClient from artiq.protocols.pc_rpc import AsyncioClient
from artiq.gui.models import ModelSubscriber 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(): def get_argparser():
@ -85,23 +86,26 @@ def main():
# initialize main window # initialize main window
win = MainWindow(app, args.server) win = MainWindow(app, args.server)
area = dockarea.DockArea() dock_area = dockarea.DockArea()
smgr.register(area) smgr.register(dock_area)
smgr.register(win) smgr.register(win)
win.setCentralWidget(area) win.setCentralWidget(dock_area)
status_bar = QtGui.QStatusBar() status_bar = QtGui.QStatusBar()
status_bar.showMessage("Connected to {}".format(args.server)) status_bar.showMessage("Connected to {}".format(args.server))
win.setStatusBar(status_bar) win.setStatusBar(status_bar)
# create UI components # 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["explist"],
sub_clients["schedule"],
rpc_clients["schedule"], rpc_clients["schedule"],
rpc_clients["repository"]) rpc_clients["repository"])
smgr.register(d_explorer) 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) smgr.register(d_datasets)
if os.name != "nt": if os.name != "nt":
@ -112,7 +116,7 @@ def main():
d_schedule = schedule.ScheduleDock( d_schedule = schedule.ScheduleDock(
status_bar, rpc_clients["schedule"], sub_clients["schedule"]) 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) smgr.register(logmgr)
d_console = console.ConsoleDock(sub_clients["datasets"], d_console = console.ConsoleDock(sub_clients["datasets"],
@ -120,14 +124,14 @@ def main():
# lay out docks # lay out docks
if os.name != "nt": if os.name != "nt":
area.addDock(d_ttl_dds.dds_dock, "top") dock_area.addDock(d_ttl_dds.dds_dock, "top")
area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock) dock_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_datasets, "above", d_ttl_dds.ttl_dock)
else: else:
area.addDock(d_datasets, "top") dock_area.addDock(d_datasets, "top")
area.addDock(d_explorer, "above", d_datasets) dock_area.addDock(d_explorer, "above", d_datasets)
area.addDock(d_console, "bottom") dock_area.addDock(d_console, "bottom")
area.addDock(d_schedule, "above", d_console) dock_area.addDock(d_schedule, "above", d_console)
# load/initialize state # load/initialize state
smgr.load() smgr.load()
@ -137,7 +141,7 @@ def main():
# create first log dock if not already in state # create first log dock if not already in state
d_log0 = logmgr.first_log_dock() d_log0 = logmgr.first_log_dock()
if d_log0 is not None: if d_log0 is not None:
area.addDock(d_log0, "right", d_explorer) dock_area.addDock(d_log0, "right", d_explorer)
# run # run
win.show() 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 dockarea
from pyqtgraph import LayoutWidget from pyqtgraph import LayoutWidget
from artiq.protocols import pyon
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
from artiq.gui.scan import ScanController
from artiq.gui.shortcuts import ShortcutManager from artiq.gui.shortcuts import ShortcutManager
class Model(DictSyncTreeSepModel): class Model(DictSyncTreeSepModel):
def __init__(self, init): def __init__(self, init):
self.explorer = None self.explorer = None
DictSyncTreeSepModel.__init__(self, DictSyncTreeSepModel.__init__(self, "/", ["Experiment"], init)
"/",
["Experiment"],
init)
def __setitem__(self, k, v): def __setitem__(self, k, v):
DictSyncTreeSepModel.__setitem__(self, k, v) DictSyncTreeSepModel.__setitem__(self, k, v)
if self.explorer is not None: # TODO
if k == self.explorer.selected_key: #if self.explorer is not None and k == self.explorer.selected_key:
self.explorer.update_selection(k, k) # 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
class ExplorerDock(dockarea.Dock): class ExplorerDock(dockarea.Dock):
def __init__(self, main_window, status_bar, def __init__(self, main_window, status_bar, exp_manager,
explist_sub, schedule_sub, explist_sub, schedule_ctl, repository_ctl):
schedule_ctl, repository_ctl):
dockarea.Dock.__init__(self, "Explorer", size=(1500, 500)) dockarea.Dock.__init__(self, "Explorer", size=(1500, 500))
self.main_window = main_window self.main_window = main_window
self.status_bar = status_bar self.status_bar = status_bar
self.schedule_sub = schedule_sub self.exp_manager = exp_manager
self.schedule_ctl = schedule_ctl 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 = QtGui.QTreeView()
self.el.setHeaderHidden(True) self.el.setHeaderHidden(True)
self.el.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems) self.el.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.el.selectionChanged = self._selection_changed self.addWidget(self.el, 0, 0, colspan=2)
self.selected_key = None self.el.doubleClicked.connect(self.open_clicked)
grid.addWidget(self.el, 0, 0, colspan=4)
self.datetime = QtGui.QDateTimeEdit() open = QtGui.QPushButton("Open")
self.datetime.setDisplayFormat("MMM d yyyy hh:mm:ss") open.setToolTip("Open the selected experiment (Return)")
self.datetime.setDate(QtCore.QDate.currentDate()) self.addWidget(open, 1, 0)
self.datetime.dateTimeChanged.connect(self.enable_duedate) open.clicked.connect(self.open_clicked)
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)
submit = QtGui.QPushButton("Submit") submit = QtGui.QPushButton("Submit")
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)") 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) 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()) self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model) explist_sub.add_setmodel_callback(self.set_model)
self.shortcuts = ShortcutManager(self.main_window, self) self.shortcuts = ShortcutManager(self.main_window, self)
self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) 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 = QtGui.QAction("Submit", self.el)
submit_action.triggered.connect(self.submit_clicked) submit_action.triggered.connect(self.submit_clicked)
submit_action.setShortcut("CTRL+RETURN") submit_action.setShortcut("CTRL+RETURN")
self.el.addAction(submit_action) self.el.addAction(submit_action)
reqterm_action = QtGui.QAction("Request termination of instances", self.el) 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") reqterm_action.setShortcut("CTRL+BACKSPACE")
self.el.addAction(reqterm_action) self.el.addAction(reqterm_action)
@ -306,118 +85,40 @@ class ExplorerDock(dockarea.Dock):
self.explist_model = model self.explist_model = model
self.el.setModel(model) self.el.setModel(model)
def update_selection(self, selected, deselected): def _get_selected_expname(self):
if deselected is not None: selection = self.el.selectedIndexes()
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()
if selection: if selection:
return self.explist_model.index_to_key(selection[0]) return self.explist_model.index_to_key(selection[0])
else: else:
return None return None
def _selection_changed(self, selected, deselected): def open_clicked(self):
self.update_selection(self._sel_to_key(selected), expname = self._get_selected_expname()
self._sel_to_key(deselected)) 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): 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 { return {
"argeditor": self.argeditor_states,
"shortcuts": self.shortcuts.save_state() "shortcuts": self.shortcuts.save_state()
} }
def restore_state(self, state): def restore_state(self, state):
try: try:
argeditor_states = state["argeditor"]
shortcuts_state = state["shortcuts"] shortcuts_state = state["shortcuts"]
except KeyError: except KeyError:
return return
self.argeditor_states = argeditor_states
self.shortcuts.restore_state(shortcuts_state) 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): def edit_shortcuts(self):
experiments = sorted(self.explist_model.backing_store.keys()) experiments = sorted(self.explist_model.backing_store.keys())
self.shortcuts.edit(experiments) self.shortcuts.edit(experiments)

View File

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

View File

@ -1,9 +1,11 @@
from collections import OrderedDict from collections import OrderedDict
from inspect import isclass from inspect import isclass
from artiq.protocols import pyon
__all__ = ["NoDefault", __all__ = ["NoDefault",
"FreeValue", "BooleanValue", "EnumerationValue", "PYONValue", "BooleanValue", "EnumerationValue",
"NumberValue", "StringValue", "NumberValue", "StringValue",
"HasEnvironment", "HasEnvironment",
"Experiment", "EnvExperiment", "is_experiment"] "Experiment", "EnvExperiment", "is_experiment"]
@ -40,9 +42,16 @@ class _SimpleArgProcessor:
return d return d
class FreeValue(_SimpleArgProcessor): class PYONValue(_SimpleArgProcessor):
"""An argument that can be an arbitrary Python value.""" """An argument that can be any PYON-serializable value."""
pass 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): class BooleanValue(_SimpleArgProcessor):
@ -166,7 +175,7 @@ class HasEnvironment:
if self.__parent is not None and key not in self.__kwargs: if self.__parent is not None and key not in self.__kwargs:
return self.__parent.get_argument(key, processor, group) return self.__parent.get_argument(key, processor, group)
if processor is None: if processor is None:
processor = FreeValue() processor = PYONValue()
self.requested_args[key] = processor, group self.requested_args[key] = processor, group
try: try:
argval = self.__kwargs[key] argval = self.__kwargs[key]

View File

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

View File

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

View File

@ -4,10 +4,17 @@ from artiq import *
class BlinkForever(EnvExperiment): class BlinkForever(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") 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 @kernel
def run(self): def run(self):
while True: for i in range(80000):
self.led.pulse(100*ms) self.ttl0.pulse(40*us)
delay(100*ms) 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("pmt")
self.setattr_device("electrodes") self.setattr_device("electrodes")
self.setattr_argument("wait_at_stop", FreeValue(100*us)) self.setattr_argument("wait_at_stop", NumberValue(100*us))
self.setattr_argument("speed", FreeValue(1.5)) self.setattr_argument("speed", NumberValue(1.5))
self.setattr_argument("repeats", FreeValue(100)) self.setattr_argument("repeats", NumberValue(100))
self.setattr_argument("nbins", FreeValue(100)) self.setattr_argument("nbins", NumberValue(100))
def calc_waveforms(self, stop): def calc_waveforms(self, stop):
t = transport_data["t"][:stop]*self.speed t = transport_data["t"][:stop]*self.speed