mirror of https://github.com/m-labs/artiq.git
gui: experiment docks (WIP)
This commit is contained in:
parent
6671bb33a4
commit
c382fac8f2
|
@ -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"],
|
||||
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()
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue