From f5e13d447c4e1eb4ec6c8ed0a8a05b896e743c3b Mon Sep 17 00:00:00 2001 From: Thomas Lemon Date: Fri, 9 Jan 2026 14:56:09 -0800 Subject: [PATCH 1/3] Add forbidden channels cache to increase performance --- .../Keithley/Keithley_3706A.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py b/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py index 8d9f3af816db..b37132da0871 100644 --- a/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py +++ b/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py @@ -1,6 +1,7 @@ import itertools import textwrap import warnings +from time import sleep from typing import TYPE_CHECKING import qcodes.validators as vals @@ -31,16 +32,23 @@ def __init__( self, name: str, address: str, + use_forbidden_channels_cache: bool = False, **kwargs: "Unpack[VisaInstrumentKWArgs]", ) -> None: """ Args: name: Name to use internally in QCoDeS address: VISA resource address + use_forbidden_channels_cache: If True, use local + forbidden channel cache instead of querying instrument. + See `Keithley3706A.get_forbidden_channels()` for + usage details and warnings. **kwargs: kwargs are forwarded to base class. """ super().__init__(name, address, **kwargs) + self.use_forbidden_channels_cache: bool = use_forbidden_channels_cache + self.forbidden_channels_cache: str = "" self.channel_connect_rule: Parameter = self.add_parameter( "channel_connect_rule", @@ -155,7 +163,9 @@ def close_channel(self, val: str) -> None: """ slots = ["allslots", *self._get_slot_names()] - forbidden_channels = self.get_forbidden_channels("allslots") + forbidden_channels = self.get_forbidden_channels( + "allslots", fetch_from_cache=self.use_forbidden_channels_cache + ) if val in slots: raise Keithley3706AInvalidValue("Slots cannot be closed all together.") if not self._validator(val): @@ -330,8 +340,9 @@ def set_forbidden_channels(self, val: str) -> None: 'ranges, slots, backplane relays or "allslots".' ) self.write(f"channel.setforbidden('{val}')") + self.forbidden_channels_cache = val - def get_forbidden_channels(self, val: str) -> str: + def get_forbidden_channels(self, val: str, fetch_from_cache: bool = False) -> str: """ Returns a string that lists the channels and backplane relays that are forbidden to close. @@ -340,8 +351,23 @@ def get_forbidden_channels(self, val: str) -> str: val: A string representing the channels, backplane relays or channel patterns to be queried to see if they are forbidden to close. + fetch_from_cache: If True, will fetch forbidden channels from cache, + otherwise, it will query the instrument. + May save time during measurements + where closing channels happens frequently. 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. """ + + # NOTE: The cache string should already be validated from + # calling set_forbidden_channels, so we can just return it + if fetch_from_cache: + return self.forbidden_channels_cache + if not self._validator(val): raise Keithley3706AInvalidValue( f"{val} is not a valid specifier. " @@ -367,6 +393,11 @@ def clear_forbidden_channels(self, val: str) -> None: ) self.write(f"channel.clearforbidden('{val}')") + wait_for_instrument_to_update_settings_delay = 0.25 + sleep(wait_for_instrument_to_update_settings_delay) + + self.forbidden_channels_cache = self.get_forbidden_channels("allslots") + def set_delay(self, val: str, delay_time: float) -> None: """ Sets an additional delay time for the specified channels. From 7f7138521d7b8798e15ac3d4b1929f58314b3b5c Mon Sep 17 00:00:00 2001 From: Thomas Lemon Date: Fri, 9 Jan 2026 16:09:41 -0800 Subject: [PATCH 2/3] Check if forbidden channels cache is enabled before setting/clearing forbidden channels --- .../instrument_drivers/Keithley/Keithley_3706A.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py b/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py index b37132da0871..4db5553c2fca 100644 --- a/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py +++ b/src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py @@ -340,7 +340,9 @@ def set_forbidden_channels(self, val: str) -> None: 'ranges, slots, backplane relays or "allslots".' ) self.write(f"channel.setforbidden('{val}')") - self.forbidden_channels_cache = val + + if self.use_forbidden_channels_cache: + self.forbidden_channels_cache = val def get_forbidden_channels(self, val: str, fetch_from_cache: bool = False) -> str: """ @@ -393,10 +395,11 @@ def clear_forbidden_channels(self, val: str) -> None: ) self.write(f"channel.clearforbidden('{val}')") - wait_for_instrument_to_update_settings_delay = 0.25 - sleep(wait_for_instrument_to_update_settings_delay) + if self.use_forbidden_channels_cache: + wait_for_instrument_to_update_settings_delay = 0.25 + sleep(wait_for_instrument_to_update_settings_delay) - self.forbidden_channels_cache = self.get_forbidden_channels("allslots") + self.forbidden_channels_cache = self.get_forbidden_channels("allslots") def set_delay(self, val: str, delay_time: float) -> None: """ From 368365632d465a023e99193533a69c6527cd0eda Mon Sep 17 00:00:00 2001 From: Thomas Lemon Date: Fri, 9 Jan 2026 16:22:39 -0800 Subject: [PATCH 3/3] Add newsfragment for 7771 --- docs/changes/newsfragments/7771.improved_driver | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 docs/changes/newsfragments/7771.improved_driver diff --git a/docs/changes/newsfragments/7771.improved_driver b/docs/changes/newsfragments/7771.improved_driver new file mode 100644 index 000000000000..6309544c3203 --- /dev/null +++ b/docs/changes/newsfragments/7771.improved_driver @@ -0,0 +1,2 @@ +On Keithly3706A, allow users to cache forbidden channels in order to bypass unnecessary `_validator()` calls when closing channels. +Results in significant time savings when running measurements with repeated calls to `close_channel()`.