Skip to content

Commit 7a6de01

Browse files
authored
Merge pull request #7771 from trlemon/trlemon/add_forbidden_channel_cache_keithley3706a
Add forbidden channels and _validator caches to Keithley3706a to improve performance
2 parents e020c10 + 7462dc4 commit 7a6de01

File tree

3 files changed

+75
-13
lines changed

3 files changed

+75
-13
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
On Keithly3706A, allow users to cache forbidden channels in order to bypass unnecessary `_validator()` calls when closing channels.
2+
Added a cache for `_validator()` results. Both of these changes results in significant time savings when running measurements
3+
with repeated calls to `close_channel()`.

docs/examples/driver_examples/QCoDeS example with Keithley 3706A System Switch.ipynb

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,7 +1447,18 @@
14471447
"cell_type": "markdown",
14481448
"metadata": {},
14491449
"source": [
1450-
"In certain cases, we may want to keep certain channels always open. We can achieve desired behavior by setting the specified channels (and analog backplane relays) as `forbidden` to close."
1450+
"In certain cases, we may want to keep certain channels always open. We can achieve desired behavior by setting the specified channels (and analog backplane relays) as `forbidden` to close.\n",
1451+
"\n",
1452+
"**Note on Forbidden Channels Cache:**\n",
1453+
"\n",
1454+
"When closing channels, there is a check to make sure the channel isn't forbidden to close, which can become quite \"expensive\" if many close operations are required in our measurement. To remedy this, we can set a class attribute that toggles usage of a forbidden channels cache.\n",
1455+
"```\n",
1456+
"smatrix.use_forbidden_channels_cache = True\n",
1457+
"```\n",
1458+
"\n",
1459+
"When this cache is active, setting forbidden channels will both update the forbidden channels on the instrument and update the cache.\n",
1460+
"\n",
1461+
"Please use with caution since the local cache may become out of sync with the instrument in the case of an instrument reset and/or powercycle. If intending to set forbidden channels, always do so before running a measurement to minimize risk of cache being out of sync."
14511462
]
14521463
},
14531464
{
@@ -1459,6 +1470,16 @@
14591470
"smatrix.set_forbidden_channels(\"1101:1105\")"
14601471
]
14611472
},
1473+
{
1474+
"cell_type": "markdown",
1475+
"metadata": {},
1476+
"source": [
1477+
"By default, `Keithley3706A.get_forbidden_channels()` will query the instrument, but if you'd like to query the forbidden channels cache instead, you can set the argument `fetch_from_cache` to True.\n",
1478+
"```\n",
1479+
"smatrix.get_forbidden_channels(\"allslots\", fetch_from_cache=True)\n",
1480+
"```"
1481+
]
1482+
},
14621483
{
14631484
"cell_type": "code",
14641485
"execution_count": 50,
@@ -1553,7 +1574,7 @@
15531574
"cell_type": "markdown",
15541575
"metadata": {},
15551576
"source": [
1556-
"We can clear the forbidden list when we desire to do so."
1577+
"We can clear the forbidden list when we desire to do so. If using the forbidden channels cache, calling `Keithley3706A.clear_forbidden_channels()` will update the cache with the forbidden channels on the instrument (via `Keithley3706A.get_forbidden_channels(\"allslots\")`)."
15571578
]
15581579
},
15591580
{

src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import functools
12
import itertools
23
import textwrap
34
import warnings
5+
from time import sleep
46
from typing import TYPE_CHECKING
57

68
import qcodes.validators as vals
@@ -31,16 +33,23 @@ def __init__(
3133
self,
3234
name: str,
3335
address: str,
36+
use_forbidden_channels_cache: bool = False,
3437
**kwargs: "Unpack[VisaInstrumentKWArgs]",
3538
) -> None:
3639
"""
3740
Args:
3841
name: Name to use internally in QCoDeS
3942
address: VISA resource address
43+
use_forbidden_channels_cache: If True, use local
44+
forbidden channel cache instead of querying instrument.
45+
See `Keithley3706A.get_forbidden_channels()` for
46+
usage details and warnings.
4047
**kwargs: kwargs are forwarded to base class.
4148
4249
"""
4350
super().__init__(name, address, **kwargs)
51+
self.use_forbidden_channels_cache: bool = use_forbidden_channels_cache
52+
self._forbidden_channels_cache: str = ""
4453

4554
self.channel_connect_rule: Parameter = self.add_parameter(
4655
"channel_connect_rule",
@@ -155,7 +164,9 @@ def close_channel(self, val: str) -> None:
155164
156165
"""
157166
slots = ["allslots", *self._get_slot_names()]
158-
forbidden_channels = self.get_forbidden_channels("allslots")
167+
forbidden_channels = self.get_forbidden_channels(
168+
"allslots", fetch_from_cache=self.use_forbidden_channels_cache
169+
)
159170
if val in slots:
160171
raise Keithley3706AInvalidValue("Slots cannot be closed all together.")
161172
if not self._validator(val):
@@ -331,7 +342,10 @@ def set_forbidden_channels(self, val: str) -> None:
331342
)
332343
self.write(f"channel.setforbidden('{val}')")
333344

334-
def get_forbidden_channels(self, val: str) -> str:
345+
if self.use_forbidden_channels_cache:
346+
self._forbidden_channels_cache = val
347+
348+
def get_forbidden_channels(self, val: str, fetch_from_cache: bool = False) -> str:
335349
"""
336350
Returns a string that lists the channels and backplane relays
337351
that are forbidden to close.
@@ -340,8 +354,23 @@ def get_forbidden_channels(self, val: str) -> str:
340354
val: A string representing the channels,
341355
backplane relays or channel patterns to be queried to see
342356
if they are forbidden to close.
357+
fetch_from_cache: If True, will fetch forbidden channels from cache,
358+
otherwise, it will query the instrument.
359+
May save time during measurements
360+
where closing channels happens frequently. Please use with
361+
caution since the local cache may become out of sync with
362+
the instrument in the case of an instrument reset and/or
363+
powercycle. If intending to set forbidden channels, always
364+
do so before running a measurement to minimize risk of
365+
cache being out of sync.
343366
344367
"""
368+
369+
# NOTE: The cache string should already be validated from
370+
# calling set_forbidden_channels, so we can just return it
371+
if fetch_from_cache:
372+
return self._forbidden_channels_cache
373+
345374
if not self._validator(val):
346375
raise Keithley3706AInvalidValue(
347376
f"{val} is not a valid specifier. "
@@ -367,6 +396,11 @@ def clear_forbidden_channels(self, val: str) -> None:
367396
)
368397
self.write(f"channel.clearforbidden('{val}')")
369398

399+
if self.use_forbidden_channels_cache:
400+
wait_to_clear_forbidden_channels_delay = 0.25
401+
sleep(wait_to_clear_forbidden_channels_delay)
402+
self._forbidden_channels_cache = self.get_forbidden_channels("allslots")
403+
370404
def set_delay(self, val: str, delay_time: float) -> None:
371405
"""
372406
Sets an additional delay time for the specified channels.
@@ -899,21 +933,25 @@ def load_setup(self, val: int | str) -> None:
899933
"""
900934
self.write(f"setup.recall('{val}')")
901935

902-
def _validator(self, val: str) -> bool:
936+
@functools.cached_property
937+
def _valid_specifiers(self) -> frozenset[str]:
903938
"""
904-
Instrument specific validator. As the number of validation points
905-
are around 15k, to avoid QCoDeS parameter validation to print them all,
906-
we shall raise a custom exception.
939+
Cache valid channel specifiers for fast validation.
940+
This property is computed once on first access and cached.
907941
"""
908942
ch = self.get_channels()
909943
ch_range = self._get_channel_ranges()
910944
slots = ["allslots", *self._get_slot_names()]
911945
backplanes = self.get_analog_backplane_specifiers()
912-
specifier = val.split(",")
913-
for element in specifier:
914-
if element not in (*ch, *ch_range, *slots, *backplanes):
915-
return False
916-
return True
946+
return frozenset((*ch, *ch_range, *slots, *backplanes))
947+
948+
def _validator(self, val: str) -> bool:
949+
"""
950+
Instrument specific validator. As the number of validation points
951+
are around 15k, to avoid QCoDeS parameter validation to print them all,
952+
we shall raise a custom exception.
953+
"""
954+
return all(element in self._valid_specifiers for element in val.split(","))
917955

918956
def connect_message(
919957
self, idn_param: str = "IDN", begin_time: float | None = None

0 commit comments

Comments
 (0)