From 33da27a749d521c65bd009883c6c9cfd11a10373 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Thu, 17 Mar 2016 18:41:49 +0100 Subject: [PATCH] experiment/gui: support reverse scan (closes #246) --- RELEASE_NOTES.rst | 10 +++++-- artiq/gui/entries.py | 68 +++++++++++++++++++++--------------------- artiq/language/scan.py | 44 ++++++++++++++------------- 3 files changed, 64 insertions(+), 58 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index d675623d8..9bbf19ccb 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -10,9 +10,13 @@ Release notes * Core device flash storage has moved due to increased runtime size. This requires reflashing the runtime and the flash storage filesystem image or erase and rewrite its entries. -* RTIOCollisionError has been renamed to RTIOCollision -* the new API for DDS batches is: +* ``RTIOCollisionError`` has been renamed to ``RTIOCollision`` +* the new API for DDS batches is:: with self.core_dds.batch: ... - with core_dds a device of type artiq.coredevice.dds.CoreDDS. + + with ``core_dds`` a device of type ``artiq.coredevice.dds.CoreDDS``. The dds_bus device should not be used anymore. +* LinearScan now supports scanning from high to low. Accordingly, + it's arguments ``min/max`` have been renamed to ``start/stop`` respectively. + Same for RandomScan (even though there direction matters little). diff --git a/artiq/gui/entries.py b/artiq/gui/entries.py index 383d0d701..f0d48bd13 100644 --- a/artiq/gui/entries.py +++ b/artiq/gui/entries.py @@ -154,52 +154,52 @@ class _RangeScan(LayoutWidget): if procdesc["unit"]: spinbox.setSuffix(" " + procdesc["unit"]) - self.scanner = scanner = ScanWidget() + scanner = ScanWidget() disable_scroll_wheel(scanner) self.addWidget(scanner, 0, 0, -1, 1) - self.min = ScientificSpinBox() - self.min.setStyleSheet("QDoubleSpinBox {color:blue}") - self.min.setMinimumSize(110, 0) - self.min.setSizePolicy(QtWidgets.QSizePolicy( + start = ScientificSpinBox() + start.setStyleSheet("QDoubleSpinBox {color:blue}") + start.setMinimumSize(110, 0) + start.setSizePolicy(QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)) - disable_scroll_wheel(self.min) - self.addWidget(self.min, 0, 1) + disable_scroll_wheel(start) + self.addWidget(start, 0, 1) - self.npoints = QtWidgets.QSpinBox() - self.npoints.setMinimum(1) - disable_scroll_wheel(self.npoints) - self.addWidget(self.npoints, 1, 1) + npoints = QtWidgets.QSpinBox() + npoints.setMinimum(1) + disable_scroll_wheel(npoints) + self.addWidget(npoints, 1, 1) - self.max = ScientificSpinBox() - self.max.setStyleSheet("QDoubleSpinBox {color:red}") - self.max.setMinimumSize(110, 0) - disable_scroll_wheel(self.max) - self.addWidget(self.max, 2, 1) + stop = ScientificSpinBox() + stop.setStyleSheet("QDoubleSpinBox {color:red}") + stop.setMinimumSize(110, 0) + disable_scroll_wheel(stop) + self.addWidget(stop, 2, 1) - def update_min(value): - state["min"] = value*scale + def update_start(value): + state["start"] = value*scale scanner.setStart(value) - def update_max(value): - state["max"] = value*scale + def update_stop(value): + state["stop"] = value*scale scanner.setStop(value) def update_npoints(value): state["npoints"] = value scanner.setNum(value) - scanner.startChanged.connect(self.min.setValue) - scanner.numChanged.connect(self.npoints.setValue) - scanner.stopChanged.connect(self.max.setValue) - self.min.valueChanged.connect(update_min) - self.npoints.valueChanged.connect(update_npoints) - self.max.valueChanged.connect(update_max) - scanner.setStart(state["min"]/scale) + scanner.startChanged.connect(start.setValue) + scanner.numChanged.connect(npoints.setValue) + scanner.stopChanged.connect(stop.setValue) + start.valueChanged.connect(update_start) + npoints.valueChanged.connect(update_npoints) + stop.valueChanged.connect(update_stop) + scanner.setStart(state["start"]/scale) scanner.setNum(state["npoints"]) - scanner.setStop(state["max"]/scale) - apply_properties(self.min) - apply_properties(self.max) + scanner.setStop(state["stop"]/scale) + apply_properties(start) + apply_properties(stop) class _ExplicitScan(LayoutWidget): @@ -266,8 +266,8 @@ class _ScanEntry(LayoutWidget): state = { "selected": "NoScan", "NoScan": {"value": 0.0}, - "LinearScan": {"min": 0.0, "max": 100.0*scale, "npoints": 10}, - "RandomScan": {"min": 0.0, "max": 100.0*scale, "npoints": 10}, + "LinearScan": {"start": 0.0, "stop": 100.0*scale, "npoints": 10}, + "RandomScan": {"start": 0.0, "stop": 100.0*scale, "npoints": 10}, "ExplicitScan": {"sequence": []} } if "default" in procdesc: @@ -278,8 +278,8 @@ class _ScanEntry(LayoutWidget): state["NoScan"]["value"] = default["value"] elif ty == "LinearScan" or ty == "RandomScan": for d in state["LinearScan"], state["RandomScan"]: - d["min"] = default["min"] - d["max"] = default["max"] + d["start"] = default["start"] + d["stop"] = default["stop"] d["npoints"] = default["npoints"] elif ty == "ExplicitScan": state["ExplicitScan"]["sequence"] = default["sequence"] diff --git a/artiq/language/scan.py b/artiq/language/scan.py index dabe76df1..8b88a9f5f 100644 --- a/artiq/language/scan.py +++ b/artiq/language/scan.py @@ -11,7 +11,7 @@ Iterate on a scan object to scan it, e.g. :: do_something(variable) Iterating multiple times on the same scan object is possible, with the scan -restarting at the minimum value each time. Iterating concurrently on the +yielding the same values each time. Iterating concurrently on the same scan object (e.g. via nested loops) is also supported, and the iterators are independent from each other. @@ -55,21 +55,23 @@ class NoScan(ScanObject): class LinearScan(ScanObject): - """A scan object that yields a fixed number of increasing evenly + """A scan object that yields a fixed number of evenly spaced values in a range.""" - def __init__(self, min, max, npoints): - if min > max: - raise ValueError("Scan minimum must be less than maximum") - self.min = min - self.max = max + def __init__(self, start, stop, npoints): + self.start = start + self.stop = stop self.npoints = npoints @portable def _gen(self): - r = self.max - self.min - d = self.npoints - 1 - for i in range(self.npoints): - yield r*i/d + self.min + if self.npoints == 0: + return + if self.npoints == 1: + yield self.start + else: + dx = (self.stop - self.start)/(self.npoints - 1) + for i in range(self.npoints): + yield i*dx + self.start @portable def __iter__(self): @@ -80,19 +82,18 @@ class LinearScan(ScanObject): def describe(self): return {"ty": "LinearScan", - "min": self.min, "max": self.max, "npoints": self.npoints} + "start": self.start, "stop": self.stop, + "npoints": self.npoints} class RandomScan(ScanObject): """A scan object that yields a fixed number of randomly ordered evenly spaced values in a range.""" - def __init__(self, min, max, npoints, seed=0): - if min > max: - raise ValueError("Scan minimum must be less than maximum") - self.min = min - self.max = max + def __init__(self, start, stop, npoints, seed=0): + self.start = start + self.stop = stop self.npoints = npoints - self.sequence = list(LinearScan(min, max, npoints)) + self.sequence = list(LinearScan(start, stop, npoints)) shuffle(self.sequence, Random(seed).random) @portable @@ -104,7 +105,8 @@ class RandomScan(ScanObject): def describe(self): return {"ty": "RandomScan", - "min": self.min, "max": self.max, "npoints": self.npoints} + "start": self.start, "stop": self.stop, + "npoints": self.npoints} class ExplicitScan(ScanObject): @@ -142,8 +144,8 @@ class Scannable: :param global_step: The step with which the value should be modified by up/down buttons in a user interface. The default is the scale divided by 10. - :param unit: A string representing the unit of the scanned variable, for user - interface (UI) purposes. + :param unit: A string representing the unit of the scanned variable, for + user interface (UI) purposes. :param scale: The scale of value for UI purposes. The displayed value is divided by the scale. :param ndecimals: The number of decimals a UI should use.