Skip to content

Commit

Permalink
Merge pull request #285 from QDevil/qdac2
Browse files Browse the repository at this point in the history
QDAC2: Give context managers close method & clean up
  • Loading branch information
astafan8 authored Jan 15, 2024
2 parents d80e337 + b3b461d commit 0b2d108
Show file tree
Hide file tree
Showing 17 changed files with 269 additions and 44 deletions.
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 @@ -8,7 +8,7 @@ def test_various_operations_have_common_functions(qdac): # noqa
qdac.ch02.sine_wave(),
qdac.ch03.triangle_wave(),
qdac.ch04.dc_sweep(start_V=-1, stop_V=1, points=11),
qdac.ch05.dc_list(voltages=(-1,0,1)),
qdac.ch05.dc_list(voltages=(-1, 0, 1)),
qdac.ch05.measurement()
]
# -----------------------------------------------------------------------
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
10 changes: 5 additions & 5 deletions qcodes_contrib_drivers/tests/QDevil/test_sim_qdac2_array.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from unittest.mock import MagicMock, call
from unittest.mock import call
from qcodes_contrib_drivers.drivers.QDevil.QDAC2 import QDac2
from qcodes_contrib_drivers.drivers.QDevil.QDAC2_Array import QDac2_Array
from .sim_qdac2_fixtures import qdac, qdac2 # noqa
Expand Down Expand Up @@ -96,7 +96,7 @@ def test_fails_on_non_unique_contact_names(qdac, qdac2): # noqa
listener: {'sensorA': 2, 'plungerB': 3}
})
# -----------------------------------------------------------------------
assert f'Contact name sensorA used multiple times' in repr(error)
assert 'Contact name sensorA used multiple times' in repr(error)


def test_internal_connect_to_trigger_out(qdac, qdac2): # noqa
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_set_virtual_voltages_goes_to_correct_qdac(qdac, qdac2): # noqa


def test_sync_steady_state(qdac, qdac2, mocker): # noqa
sleep_s = mocker.patch('qcodes_contrib_drivers.drivers.QDevil.QDAC2_Array.sleep_s') # Don't sleep
sleep_s = mocker.patch('qcodes_contrib_drivers.drivers.QDevil.QDAC2_Array.sleep_s') # Don't sleep
qdacs, controller, listener = two_qdacs(qdac, qdac2)
contacts = {controller: {'A': 2, 'B': 1}, listener: {'C': 3}}
arrangement = qdacs.arrange(contacts)
Expand Down Expand Up @@ -208,7 +208,7 @@ def test_sync_steady_state(qdac, qdac2, mocker): # noqa


def test_sync_leakage(qdac, qdac2, mocker): # noqa
sleep_s = mocker.patch('qcodes_contrib_drivers.drivers.QDevil.QDAC2_Array.sleep_s') # Don't sleep
mocker.patch('qcodes_contrib_drivers.drivers.QDevil.QDAC2_Array.sleep_s') # Don't sleep
qdacs, controller, listener = two_qdacs(qdac, qdac2)
contacts = {controller: {'A': 3}, listener: {'B': 1, 'C': 2}}
arrangement = qdacs.arrange(contacts)
Expand Down Expand Up @@ -298,7 +298,7 @@ def test_frees_internal_triggers_on_exit(qdac, qdac2): # noqa
qdacs, controller, listener = two_qdacs(qdac, qdac2)
contacts = {controller: {'A': 2, 'B': 1}, listener: {'C': 3}}
# -----------------------------------------------------------------------
with qdacs.arrange(contacts, internal_triggers=['starter']) as arrangement:
with qdacs.arrange(contacts, internal_triggers=['starter']):
pass
# -----------------------------------------------------------------------
assert qdac.n_triggers() == len(qdac._internal_triggers)
Loading

0 comments on commit 0b2d108

Please sign in to comment.