environment, artiq_run: introduce interactive arguments

This commit is contained in:
Sébastien Bourdeauducq 2024-02-26 19:30:31 +08:00
parent a0450555e2
commit 7688f380b1
3 changed files with 53 additions and 2 deletions

View File

@ -41,6 +41,7 @@ Highlights:
+ CTRL+SHIFT+C cascades experiment windows + CTRL+SHIFT+C cascades experiment windows
* Datasets can now be associated with units and scale factors, and displayed accordingly in the dashboard * Datasets can now be associated with units and scale factors, and displayed accordingly in the dashboard
including applets, like widgets such as ``NumberValue`` already did in earlier ARTIQ versions. including applets, like widgets such as ``NumberValue`` already did in earlier ARTIQ versions.
* Experiments can now request arguments interactively from the user at any time.
* Persistent datasets are now stored in a LMDB database for improved performance. * Persistent datasets are now stored in a LMDB database for improved performance.
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on * Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
kernel functions. kernel functions.

View File

@ -13,7 +13,7 @@ import h5py
from llvmlite import binding as llvm from llvmlite import binding as llvm
from sipyco import common_args from sipyco import common_args, pyon
from artiq import __version__ as artiq_version from artiq import __version__ as artiq_version
from artiq.language.environment import EnvExperiment, ProcessArgumentManager from artiq.language.environment import EnvExperiment, ProcessArgumentManager
@ -166,9 +166,28 @@ def get_argparser(with_file=True):
return parser return parser
class ArgumentManager(ProcessArgumentManager):
def get_interactive(self, interactive_arglist):
result = dict()
for key, processor, group, tooltip in interactive_arglist:
success = False
while not success:
user_input = input("{}:{} (group={}, tooltip={}): ".format(
key, type(processor).__name__, group, tooltip))
try:
user_input_deser = pyon.decode(user_input)
value = processor.process(user_input_deser)
except:
logger.error("failed to process user input, retrying", exc_info=True)
else:
success = True
result[key] = value
return result
def _build_experiment(device_mgr, dataset_mgr, args): def _build_experiment(device_mgr, dataset_mgr, args):
arguments = parse_arguments(args.arguments) arguments = parse_arguments(args.arguments)
argument_mgr = ProcessArgumentManager(arguments) argument_mgr = ArgumentManager(arguments)
managers = (device_mgr, dataset_mgr, argument_mgr, {}) managers = (device_mgr, dataset_mgr, argument_mgr, {})
if hasattr(args, "file"): if hasattr(args, "file"):
is_tar = tarfile.is_tarfile(args.file) is_tar = tarfile.is_tarfile(args.file)

View File

@ -1,5 +1,7 @@
from collections import OrderedDict from collections import OrderedDict
from inspect import isclass from inspect import isclass
from contextlib import contextmanager
from types import SimpleNamespace
from sipyco import pyon from sipyco import pyon
@ -212,6 +214,9 @@ class TraceArgumentManager:
self.requested_args[key] = processor, group, tooltip self.requested_args[key] = processor, group, tooltip
return None return None
def get_interactive(self, interactive_arglist):
raise NotImplementedError
class ProcessArgumentManager: class ProcessArgumentManager:
def __init__(self, unprocessed_arguments): def __init__(self, unprocessed_arguments):
@ -233,6 +238,10 @@ class ProcessArgumentManager:
raise AttributeError("Supplied argument(s) not queried in experiment: " + raise AttributeError("Supplied argument(s) not queried in experiment: " +
", ".join(unprocessed)) ", ".join(unprocessed))
def get_interactive(self, interactive_arglist):
raise NotImplementedError
class HasEnvironment: class HasEnvironment:
"""Provides methods to manage the environment of an experiment (arguments, """Provides methods to manage the environment of an experiment (arguments,
devices, datasets).""" devices, datasets)."""
@ -322,6 +331,28 @@ class HasEnvironment:
kernel_invariants = getattr(self, "kernel_invariants", set()) kernel_invariants = getattr(self, "kernel_invariants", set())
self.kernel_invariants = kernel_invariants | {key} self.kernel_invariants = kernel_invariants | {key}
@contextmanager
def interactive(self):
"""Request arguments from the user interactively.
This context manager returns a namespace object on which the method
`setattr_argument` should be called, with the usual semantics.
When the context manager terminates, the experiment is blocked
and the user is presented with the requested argument widgets.
After the user enters values, the experiment is resumed and
the namespace contains the values of the arguments."""
interactive_arglist = []
namespace = SimpleNamespace()
def setattr_argument(key, processor=None, group=None, tooltip=None):
interactive_arglist.append((key, processor, group, tooltip))
namespace.setattr_argument = setattr_argument
yield namespace
del namespace.setattr_argument
argdict = self.__argument_mgr.get_interactive(interactive_arglist)
for key, value in argdict.items():
setattr(namespace, key, value)
def get_device_db(self): def get_device_db(self):
"""Returns the full contents of the device database.""" """Returns the full contents of the device database."""
return self.__device_mgr.get_device_db() return self.__device_mgr.get_device_db()