From 07c71bf0208f2ffdf1cb7e839893c619ebd9d2ec Mon Sep 17 00:00:00 2001 From: Chris Ballance Date: Thu, 30 Mar 2017 23:04:17 +0100 Subject: [PATCH] language,gui: combine LinearScan and RandomScan into RangeScan. Closes #679 --- RELEASE_NOTES.rst | 2 +- artiq/gui/entries.py | 23 +++++++++----- artiq/language/scan.py | 68 ++++++++++++++---------------------------- 3 files changed, 39 insertions(+), 54 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 045e7a9f4..010fca70c 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -29,7 +29,7 @@ Release notes * The "collision" and "busy" RTIO errors are reported through the log instead of raising exceptions. * Results are still saved when ``analyze`` raises an exception. - +* LinearScan and RandomScan have been consolidated into RangeScan 2.2 --- diff --git a/artiq/gui/entries.py b/artiq/gui/entries.py index c85d14b90..ced7de645 100644 --- a/artiq/gui/entries.py +++ b/artiq/gui/entries.py @@ -229,6 +229,9 @@ class _RangeScan(LayoutWidget): disable_scroll_wheel(stop) self.addWidget(stop, 2, 1) + randomize = QtWidgets.QCheckBox("Randomize") + self.addWidget(randomize, 3, 1) + apply_properties(start) start.setPrecision() start.setRelativeStep() @@ -255,15 +258,22 @@ class _RangeScan(LayoutWidget): if npoints.value() != value: npoints.setValue(value) + def update_randomize(value): + state["randomize"] = value + if randomize.isChecked() != value: + randomize.setChecked(value) + scanner.startChanged.connect(update_start) scanner.numChanged.connect(update_npoints) scanner.stopChanged.connect(update_stop) start.valueChanged.connect(update_start) npoints.valueChanged.connect(update_npoints) stop.valueChanged.connect(update_stop) + randomize.stateChanged.connect(update_randomize) scanner.setStart(state["start"]/scale) scanner.setNum(state["npoints"]) scanner.setStop(state["stop"]/scale) + randomize.setChecked(state["randomize"]) class _ExplicitScan(LayoutWidget): @@ -297,16 +307,14 @@ class ScanEntry(LayoutWidget): state = argument["state"] self.widgets = OrderedDict() self.widgets["NoScan"] = _NoScan(procdesc, state["NoScan"]) - self.widgets["LinearScan"] = _RangeScan(procdesc, state["LinearScan"]) - self.widgets["RandomScan"] = _RangeScan(procdesc, state["RandomScan"]) + self.widgets["RangeScan"] = _RangeScan(procdesc, state["RangeScan"]) self.widgets["ExplicitScan"] = _ExplicitScan(state["ExplicitScan"]) for widget in self.widgets.values(): self.stack.addWidget(widget) self.radiobuttons = OrderedDict() self.radiobuttons["NoScan"] = QtWidgets.QRadioButton("No scan") - self.radiobuttons["LinearScan"] = QtWidgets.QRadioButton("Linear") - self.radiobuttons["RandomScan"] = QtWidgets.QRadioButton("Random") + self.radiobuttons["RangeScan"] = QtWidgets.QRadioButton("Range") self.radiobuttons["ExplicitScan"] = QtWidgets.QRadioButton("Explicit") scan_type = QtWidgets.QButtonGroup() for n, b in enumerate(self.radiobuttons.values()): @@ -334,8 +342,8 @@ class ScanEntry(LayoutWidget): state = { "selected": "NoScan", "NoScan": {"value": 0.0, "repetitions": 1}, - "LinearScan": {"start": 0.0, "stop": 100.0*scale, "npoints": 10}, - "RandomScan": {"start": 0.0, "stop": 100.0*scale, "npoints": 10}, + "RangeScan": {"start": 0.0, "stop": 100.0*scale, "npoints": 10, + "randomize": False}, "ExplicitScan": {"sequence": []} } if "default" in procdesc: @@ -348,10 +356,11 @@ class ScanEntry(LayoutWidget): if ty == "NoScan": state[ty]["value"] = default["value"] state[ty]["repetitions"] = default["repetitions"] - elif ty == "LinearScan" or ty == "RandomScan": + elif ty == "RangeScan": state[ty]["start"] = default["start"] state[ty]["stop"] = default["stop"] state[ty]["npoints"] = default["npoints"] + state[ty]["randomize"] = default["randomize"] elif ty == "ExplicitScan": state[ty]["sequence"] = default["sequence"] else: diff --git a/artiq/language/scan.py b/artiq/language/scan.py index 2ba8ef2d4..37e3c0be1 100644 --- a/artiq/language/scan.py +++ b/artiq/language/scan.py @@ -1,7 +1,7 @@ """ Implementation and management of scan objects. -A scan object (e.g. :class:`artiq.language.scan.LinearScan`) represents a +A scan object (e.g. :class:`artiq.language.scan.RangeScan`) represents a one-dimensional sweep of a numerical range. Multi-dimensional scans are constructed by combining several scan objects, for example using :class:`artiq.language.scan.MultiScanManager`. @@ -27,7 +27,7 @@ from artiq.language import units __all__ = ["ScanObject", - "NoScan", "LinearScan", "RandomScan", "ExplicitScan", + "NoScan", "RangeScan", "ExplicitScan", "Scannable", "MultiScanManager"] @@ -59,52 +59,28 @@ class NoScan(ScanObject): "repetitions": self.repetitions} -class LinearScan(ScanObject): - """A scan object that yields a fixed number of evenly - spaced values in a range.""" - def __init__(self, start, stop, npoints): - self.start = start - self.stop = stop - self.npoints = npoints - - @portable - def _gen(self): - 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): - return self._gen() - - def __len__(self): - return self.npoints - - def describe(self): - return {"ty": "LinearScan", - "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, start, stop, npoints, seed=None): +class RangeScan(ScanObject): + """A scan object that yields a fixed number of evenly spaced values in a + range. If ``randomize`` is True the points are randomly ordered.""" + def __init__(self, start, stop, npoints, randomize=False, seed=None): self.start = start self.stop = stop self.npoints = npoints + self.randomize = randomize self.seed = seed - self.sequence = list(LinearScan(start, stop, npoints)) - if seed is None: - rf = random.random + + if npoints == 0: + self.sequence = [] + if npoints == 1: + self.sequence = [self.start] else: - rf = Random(seed).random - random.shuffle(self.sequence, rf) + dx = (stop - start)/(npoints - 1) + self.sequence = [i*dx + start for i in range(npoints)] + + if randomize: + if seed is not None: + random.seed(seed) + random.shuffle(self.sequence) @portable def __iter__(self): @@ -114,9 +90,10 @@ class RandomScan(ScanObject): return self.npoints def describe(self): - return {"ty": "RandomScan", + return {"ty": "RangeScan", "start": self.start, "stop": self.stop, "npoints": self.npoints, + "randomize": self.randomize, "seed": self.seed} @@ -138,8 +115,7 @@ class ExplicitScan(ScanObject): _ty_to_scan = { "NoScan": NoScan, - "LinearScan": LinearScan, - "RandomScan": RandomScan, + "RangeScan": RangeScan, "ExplicitScan": ExplicitScan }