Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QDAC2: Give context managers close method & clean up #285

Merged
merged 2 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/examples/QDevil/QDAC2/Scan.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@
"metadata": {},
"outputs": [],
"source": [
"# Connect scope ch 1 to qdac output trigger 2\n",
"# Connect scope ch 1 to qdac output trigger 1\n",
"scope.write('chan1:disp on')\n",
"scope.write('chan1:bwl on')\n",
"scope.write('chan1:prob 1')\n",
Expand Down
1 change: 1 addition & 0 deletions docs/examples/QDevil/QDAC2/do2dScan.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"from time import sleep\n",
"import numpy as np\n",
"from qcodes.utils.dataset import doNd\n",
"from qcodes.dataset import load_or_create_experiment\n",
"from qcodes_contrib_drivers.drivers.QDevil import QDAC2\n",
"qdac_addr = '192.168.8.17'\n",
"qdac = QDAC2.QDac2('QDAC2', visalib='@py', address=f'TCPIP::{qdac_addr}::5025::SOCKET')"
Expand Down
99 changes: 90 additions & 9 deletions qcodes_contrib_drivers/drivers/QDevil/QDAC2.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from packaging.version import parse
import abc

# Version 1.10.0
# Version 2.0.0
#
# Guiding principles for this driver for QDevil QDAC-II
# -----------------------------------------------------
Expand Down Expand Up @@ -46,6 +46,28 @@
#


# Context manager hierarchy
# -------------------------
#
# _Channel_Context
# _Dc_Context
# Sweep_Context
# List_Context
# _Waveform_Context
# Square_Context
# Sine_Context
# Triangle_Context
# Awg_Context
# Measurement_Context
# Virtual_Sweep_Context
# Arrangement_Context
# QDac2Trigger_Context
#
# Calling close() on any context manager will clean up any triggers or
# markers that were set up by the context. Use with-statements to
# have this done automatically.


pseudo_trigger_voltage = 5


Expand Down Expand Up @@ -112,6 +134,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# Propagate exceptions
return False

def close(self) -> None:
self.__exit__(None, None, None)

@property
def value(self) -> int:
"""internal SCPI trigger number"""
Expand Down Expand Up @@ -188,6 +213,10 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# Propagate exceptions
return False

@abc.abstractmethod
def close(self) -> None:
pass

def allocate_trigger(self) -> QDac2Trigger_Context:
"""Allocate internal trigger

Expand Down Expand Up @@ -264,6 +293,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# Propagate exceptions
return False

def close(self) -> None:
self.__exit__(None, None, None)

def start_on(self, trigger: QDac2Trigger_Context) -> None:
"""Attach internal trigger to DC generator

Expand Down Expand Up @@ -560,7 +592,13 @@ def __init__(self, channel: 'QDac2Channel'):
def __enter__(self):
return self

def _abort(self, wave_kind: str) -> None:
"""Abort any running wave generator
"""
self._write_channel(f'sour{"{0}"}:{wave_kind}:abor')

def _cleanup(self, wave_kind: str) -> None:
self._abort(wave_kind)
if self._trigger:
self._channel._parent.free_trigger(self._trigger)
if self._marker_start:
Expand Down Expand Up @@ -674,11 +712,13 @@ def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float],
self._set_triggering()

def __exit__(self, exc_type, exc_val, exc_tb):
self.abort()
super()._cleanup('squ')
# Propagate exceptions
return False

def close(self) -> None:
super()._cleanup('squ')

def start(self) -> None:
"""Start the square wave generator
"""
Expand Down Expand Up @@ -813,11 +853,13 @@ def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float],
self._set_triggering()

def __exit__(self, exc_type, exc_val, exc_tb):
self.abort()
super()._cleanup('sine')
# Propagate exceptions
return False

def close(self) -> None:
super()._cleanup('sine')

def start(self) -> None:
"""Start the sine wave generator
"""
Expand Down Expand Up @@ -945,11 +987,13 @@ def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float],
self._set_triggering()

def __exit__(self, exc_type, exc_val, exc_tb):
self.abort()
super()._cleanup('tri')
# Propagate exceptions
return False

def close(self) -> None:
super()._cleanup('tri')

def start(self) -> None:
"""Start the triangle wave generator
"""
Expand Down Expand Up @@ -1081,11 +1125,13 @@ def __init__(self, channel: 'QDac2Channel', trace_name: str,
self._set_triggering()

def __exit__(self, exc_type, exc_val, exc_tb):
self.abort()
super()._cleanup('awg')
# Propagate exceptions
return False

def close(self) -> None:
super()._cleanup('awg')

def start(self) -> None:
"""Start the AWG
"""
Expand Down Expand Up @@ -1193,6 +1239,21 @@ def __init__(self, channel: 'QDac2Channel', delay_s: float,
self._write_channel(f'sens{"{0}"}:coun {repetitions}')
self._set_triggering()

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.abort()
if self._trigger:
self._channel._parent.free_trigger(self._trigger)
# Always disable any triggering
self._write_channel(f'sens{"{0}"}:trig:sour imm')
# Propagate exceptions
return False

def close(self) -> None:
self.__exit__(None, None, None)

def start(self) -> None:
"""Start a current measurement
"""
Expand Down Expand Up @@ -1326,7 +1387,7 @@ def __init__(self, parent: 'QDac2', name: str, channum: int):
)
self.add_parameter(
name='measurement_delay_s',
label=f'delay',
label='delay',
unit='s',
set_cmd='sens{1}:del {0}'.format('{}', channum),
get_cmd=f'sens{channum}:del?',
Expand Down Expand Up @@ -1410,7 +1471,7 @@ def __init__(self, parent: 'QDac2', name: str, channum: int):
)
self.add_parameter(
name='output_filter',
label=f'low-pass cut-off',
label='low-pass cut-off',
unit='Hz',
set_cmd='sour{1}:filt {0}'.format('{}', channum),
get_cmd=f'sour{channum}:filt?',
Expand Down Expand Up @@ -1467,7 +1528,7 @@ def __init__(self, parent: 'QDac2', name: str, channum: int):
)
self.add_parameter(
name='dc_mode',
label=f'DC mode',
label='DC mode',
set_cmd='sour{1}:volt:mode {0}'.format('{}', channum),
get_cmd=f'sour{channum}:volt:mode?',
vals=validators.Enum('fixed', 'list', 'sweep')
Expand Down Expand Up @@ -1802,9 +1863,21 @@ def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
# Stop markers
channel = self._get_channel(0)
channel.write_channel(f'sour{"{0}"}:dc:mark:sst 0')
# Stop any lists
for contact_index in range(self._arrangement.shape):
channel = self._get_channel(contact_index)
channel.dc_abort()
channel.write_channel(f'sour{"{0}"}:dc:trig:sour imm')
# Let Arrangement take care of freeing triggers
return False

def close(self) -> None:
self.__exit__(None, None, None)
self._arrangement.close()

def actual_values_V(self, contact: str) -> np.ndarray:
"""The corrected values that would actually be sent to the contact

Expand Down Expand Up @@ -1879,15 +1952,24 @@ def __init__(self, qdac: 'QDac2', contacts: Dict[str, int],
self._fix_contact_order(contacts)
self._allocate_triggers(internal_triggers, output_triggers)
self._outer_trigger_channel = outer_trigger_channel
self._outer_trigger_context: Optional[Sine_Context] = None
self._correction = np.identity(self.shape)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if self._external_triggers:
for port in self._external_triggers.values():
self._qdac.write(f'outp:trig{port}:sour hold')
if self._outer_trigger_context:
self._outer_trigger_context.close()
self._free_triggers()
return False

def close(self) -> None:
self.__exit__(None, None, None)

@property
def shape(self) -> int:
"""Number of contacts in the arrangement"""
Expand Down Expand Up @@ -2141,7 +2223,6 @@ def _setup_outer_trigger(self, outer_step_trigger: str,
if not self._outer_trigger_channel:
raise ValueError("Arrangement needs an outer_trigger_channel when"
" using outer_step_trigger")
return
helper_ch = self._qdac.channel(self._outer_trigger_channel)
helper_ctx = helper_ch.sine_wave(
period_s=period_s, repetitions=outer_cycles, span_V=0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_arrangement_set_virtual_voltage_effectuated_immediately(qdac): # noqa
]


def test_arrangement_context_releases_trigger(qdac): # noqa
def test_arrangement_releases_trigger(qdac): # noqa
before = len(qdac._internal_triggers)
# -----------------------------------------------------------------------
with qdac.arrange(contacts={}, output_triggers={'dmm': 4}):
Expand All @@ -56,6 +56,62 @@ def test_arrangement_context_releases_trigger(qdac): # noqa
assert before == after


def test_arrangement_cleanup_on_exit(qdac): # noqa
qdac._set_up_internal_triggers()
# -----------------------------------------------------------------------
with qdac.arrange(contacts={'gate1': 1, 'gate2': 2},
output_triggers={'dmm': 4, 'vna': 5},
internal_triggers=('wave',), outer_trigger_channel=3
) as arrangement:
with arrangement.virtual_sweep2d(
inner_contact='gate1', inner_voltages=np.linspace(-0.2, 0.6, 5),
outer_contact='gate2', outer_voltages=np.linspace(-0.7, 0.15, 5),
inner_step_trigger='dmm', outer_step_trigger='vna'
):
qdac.start_recording_scpi()
# -----------------------------------------------------------------------
assert qdac.get_recorded_scpi_commands() == [
'sour1:dc:mark:sst 0',
'sour1:dc:abor',
'sour1:dc:trig:sour imm',
'sour2:dc:abor',
'sour2:dc:trig:sour imm',
'outp:trig4:sour hold',
'outp:trig5:sour hold',
'sour3:sine:abor',
'sour3:sine:mark:pstart 0',
'sour3:sine:trig:sour imm'
]


def test_arrangement_cleanup_on_close(qdac): # noqa
qdac._set_up_internal_triggers()
arrangement = qdac.arrange(
contacts={'gate1': 1, 'gate2': 2},
output_triggers={'dmm': 4, 'vna': 5},
internal_triggers=('wave',), outer_trigger_channel=3)
sweep = arrangement.virtual_sweep2d(
inner_contact='gate1', inner_voltages=np.linspace(-0.2, 0.6, 5),
outer_contact='gate2', outer_voltages=np.linspace(-0.7, 0.15, 5),
inner_step_trigger='dmm', outer_step_trigger='vna')
qdac.start_recording_scpi()
# -----------------------------------------------------------------------
sweep.close()
# -----------------------------------------------------------------------
assert qdac.get_recorded_scpi_commands() == [
'sour1:dc:mark:sst 0',
'sour1:dc:abor',
'sour1:dc:trig:sour imm',
'sour2:dc:abor',
'sour2:dc:trig:sour imm',
'outp:trig4:sour hold',
'outp:trig5:sour hold',
'sour3:sine:abor',
'sour3:sine:mark:pstart 0',
'sour3:sine:trig:sour imm'
]


def test_arrangement_set_virtual_voltage_affects_whole_arrangement(qdac): # noqa
arrangement = qdac.arrange(contacts={'gate1': 1, 'gate2': 2, 'gate3': 3})
arrangement.initiate_correction('gate1', [1.0, 0.5, -0.5])
Expand Down
Loading
Loading