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: Detuning with examples #156

Merged
merged 3 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
60 changes: 55 additions & 5 deletions docs/examples/QDevil/QDAC2/Scan.ipynb

Large diffs are not rendered by default.

81 changes: 76 additions & 5 deletions docs/examples/QDevil/QDAC2/VirtualGates.ipynb

Large diffs are not rendered by default.

89 changes: 73 additions & 16 deletions qcodes_contrib_drivers/drivers/QDevil/QDAC2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import numpy as np
import itertools
import uuid
from time import sleep as sleep_s
from qcodes.instrument.channel import InstrumentChannel, ChannelList
Expand All @@ -8,7 +9,7 @@
from typing import Any, NewType, Sequence, List, Dict, Tuple, Optional
from packaging.version import parse

# Version 0.17.0
# Version 0.18.0
#
# Guiding principles for this driver for QDevil QDAC-II
# -----------------------------------------------------
Expand Down Expand Up @@ -1547,13 +1548,14 @@ def waveform(self, values: Sequence[float]) -> None:
class Virtual_Sweep_Context:

def __init__(self, arrangement: 'Arrangement_Context', sweep: np.ndarray,
start_sweep_trigger: Optional[str], inner_step_time_s: float,
inner_step_trigger: Optional[str]):
start_trigger: Optional[str], step_time_s: float,
step_trigger: Optional[str], repetitions: Optional[int]):
self._arrangement = arrangement
self._sweep = sweep
self._inner_step_trigger = inner_step_trigger
self._inner_step_time_s = inner_step_time_s
self._allocate_triggers(start_sweep_trigger)
self._step_trigger = step_trigger
self._step_time_s = step_time_s
self._repetitions = repetitions
self._allocate_triggers(start_trigger)
self._qdac_ready = False

def __enter__(self):
Expand Down Expand Up @@ -1598,9 +1600,9 @@ def _ensure_qdac_setup(self) -> None:
self._qdac_ready = True

def _route_inner_trigger(self) -> None:
if not self._inner_step_trigger:
if not self._step_trigger:
return
trigger = self._arrangement.get_trigger_by_name(self._inner_step_trigger)
trigger = self._arrangement.get_trigger_by_name(self._step_trigger)
# All channels change in sync, so just use the first channel to make the
# external trigger.
channel = self._get_channel(0)
Expand All @@ -1618,7 +1620,8 @@ def _send_lists_to_qdac(self) -> None:

def _send_list_to_qdac(self, gate_index, voltages):
channel = self._get_channel(gate_index)
dc_list = channel.dc_list(voltages=voltages, dwell_s=self._inner_step_time_s)
dc_list = channel.dc_list(voltages=voltages, dwell_s=self._step_time_s,
repetitions=self._repetitions)
trigger = self._arrangement.get_trigger_by_name(self._start_trigger_name)
dc_list.start_on(trigger)

Expand Down Expand Up @@ -1764,8 +1767,8 @@ def get_trigger_by_name(self, name: str) -> QDac2Trigger_Context:
def virtual_sweep(self, gate: str, voltages: Sequence[float],
start_sweep_trigger: Optional[str] = None,
step_time_s: float = 1e-5,
step_trigger: Optional[str] = None
) -> Virtual_Sweep_Context:
step_trigger: Optional[str] = None,
repetitions: int = 1) -> Virtual_Sweep_Context:
"""Sweep a gate to create a 1D sweep

Args:
Expand All @@ -1775,13 +1778,14 @@ def virtual_sweep(self, gate: str, voltages: Sequence[float],
start_sweep_trigger (None, optional): Trigger that starts sweep
step_time_s (float, optional): Delay between voltage changes
step_trigger (None, optional): Trigger that marks each step
repetitions (int, Optional): Number of back-and-forth sweeps, or -1 for infinite

Returns:
Virtual_Sweep_Context: context manager
"""
sweep = self._calculate_1d_values(gate, voltages)
return Virtual_Sweep_Context(self, sweep, start_sweep_trigger,
step_time_s, step_trigger)
step_time_s, step_trigger, repetitions)

def _calculate_1d_values(self, gate: str, voltages: Sequence[float]
) -> np.ndarray:
Expand All @@ -1799,8 +1803,8 @@ def virtual_sweep2d(self, inner_gate: str, inner_voltages: Sequence[float],
outer_gate: str, outer_voltages: Sequence[float],
start_sweep_trigger: Optional[str] = None,
inner_step_time_s: float = 1e-5,
inner_step_trigger: Optional[str] = None
) -> Virtual_Sweep_Context:
inner_step_trigger: Optional[str] = None,
repetitions: int = 1) -> Virtual_Sweep_Context:
"""Sweep two gates to create a 2D sweep

Args:
Expand All @@ -1811,14 +1815,15 @@ def virtual_sweep2d(self, inner_gate: str, inner_voltages: Sequence[float],
start_sweep_trigger (None, optional): Trigger that starts sweep
inner_step_time_s (float, optional): Delay between voltage changes
inner_step_trigger (None, optional): Trigger that marks each step
repetitions (int, Optional): Number of back-and-forth sweeps, or -1 for infinite

Returns:
Virtual_Sweep_Context: context manager
"""
sweep = self._calculate_2d_values(inner_gate, inner_voltages,
outer_gate, outer_voltages)
outer_gate, outer_voltages)
return Virtual_Sweep_Context(self, sweep, start_sweep_trigger,
inner_step_time_s, inner_step_trigger)
inner_step_time_s, inner_step_trigger, repetitions)

def _calculate_2d_values(self, inner_gate: str,
inner_voltages: Sequence[float],
Expand All @@ -1838,6 +1843,51 @@ def _calculate_2d_values(self, inner_gate: str,
self._virtual_voltages[outer_index] = original_slow_voltage
return np.array(sweep)

def virtual_detune(self, gates: Sequence[str], start_V: Sequence[float],
end_V: Sequence[float], steps: int,
start_trigger: Optional[str] = None,
step_time_s: float = 1e-5,
step_trigger: Optional[str] = None,
repetitions: int = 1) -> Virtual_Sweep_Context:
"""Sweep any number of gates from one set of values to another set of values

Args:
gates (Sequence[str]): Gates involved in sweep
start_V (Sequence[float]): First-extreme values
end_V (Sequence[float]): Second-extreme values
steps (int): Number of steps between extremes
start_trigger (None, optional): Trigger that starts sweep
step_time_s (float, Optional): Seconds between each step
step_trigger (None, optional): Trigger that marks each step
repetitions (int, Optional): Number of back-and-forth sweeps, or -1 for infinite
"""
self._check_same_lengths(gates, start_V, end_V)
sweep = self._calculate_detune_values(gates, start_V, end_V, steps)
return Virtual_Sweep_Context(self, sweep, start_trigger, step_time_s,
step_trigger, repetitions)

@staticmethod
def _check_same_lengths(gates, start_V, end_V) -> None:
n_gates = len(gates)
if n_gates != len(start_V):
raise ValueError(f'There must be exactly one voltage per gate: {start_V}')
if n_gates != len(end_V):
raise ValueError(f'There must be exactly one voltage per gate: {end_V}')

def _calculate_detune_values(self, gates: Sequence[str], start_V: Sequence[float],
end_V: Sequence[float], steps: int):
original_voltages = [self.virtual_voltage(gate) for gate in gates]
indices = [self._gate_index(gate) for gate in gates]
sweep = []
forward_V = [forward_and_back(start_V[i], end_V[i], steps) for i in range(len(gates))]
for voltages in zip(*forward_V):
for index, voltage in zip(indices, voltages):
self._virtual_voltages[index] = voltage
sweep.append(self.actual_voltages())
for index, voltage in zip(indices, original_voltages):
self._virtual_voltages[index] = voltage
return np.array(sweep)

def _gate_index(self, gate: str) -> int:
return self._gates[gate]

Expand Down Expand Up @@ -1865,6 +1915,13 @@ def _free_triggers(self) -> None:
self._qdac.free_trigger(trigger)


def forward_and_back(start: float, end: float, steps: int):
forward = np.linspace(start, end, steps)
backward = np.flip(forward)[1:][:-1]
back_and_forth = itertools.chain(forward, backward)
return back_and_forth


class QDac2(VisaInstrument):

def __init__(self, name: str, address: str, **kwargs) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
from .sim_qdac2_fixtures import qdac # noqa
from qcodes_contrib_drivers.drivers.QDevil.QDAC2 import forward_and_back
import numpy as np


Expand Down Expand Up @@ -182,7 +183,7 @@ def test_stability_diagram_external(qdac): # noqa
inner_step_trigger='dmm')
# -----------------------------------------------------------------------
A = np.array([
# S1, P2, P3, P4
# S1, P2, P3, P4
[0.089, -0.046, -0.65, -0.49],
[0.085, -0.034, -0.62, -0.28],
[0.081, -0.022, -0.59, -0.06],
Expand Down Expand Up @@ -288,3 +289,71 @@ def test_stability_diagram_external(qdac): # noqa
# Start sweep
'tint 2'
]



def test_arrangement_detune_wrong_number_of_voltages(qdac): # noqa
arrangement = qdac.arrange(gates={'plunger1': 1, 'plunger2': 2})
# -----------------------------------------------------------------------
with pytest.raises(ValueError) as error:
arrangement.virtual_detune(
gates=('plunger1', 'plunger2'),
start_V=(-0.3, 0.6),
end_V=(0.3,),
steps=2)
# -----------------------------------------------------------------------
assert 'There must be exactly one voltage per gate' in repr(error)


def test_forward_and_back():
assert list(forward_and_back(-1, 1, 3)) == [-1, 0, 1, 0]
assert list(forward_and_back(-2, 2, 5)) == [-2, -1, 0, 1, 2, 1, 0, -1]


def test_arrangement_detune(qdac): # noqa
qdac.free_all_triggers()
arrangement = qdac.arrange(gates={'plunger1': 1, 'plunger2': 2})
detune = arrangement.virtual_detune(
gates=('plunger1', 'plunger2'),
start_V=(-0.3, 0.6),
end_V=(0.3, -0.1),
steps=5,
step_time_s=5e-6,
repetitions=2)
qdac.start_recording_scpi()
# -----------------------------------------------------------------------
detune.start()
# -----------------------------------------------------------------------
commands = qdac.get_recorded_scpi_commands()
assert commands == [
# Plunger 1
'sour1:dc:trig:sour hold',
'sour1:volt:mode list',
'sour1:list:volt -0.3,-0.15,0,0.15,0.3,0.15,0,-0.15',
'sour1:list:tmod auto',
'sour1:list:dwel 5e-06',
'sour1:list:dir up',
'sour1:list:coun 2',
'sour1:dc:trig:sour bus',
'sour1:dc:init:cont on',
'sour1:dc:init',
'sour1:dc:trig:sour int1',
'sour1:dc:init:cont on',
'sour1:dc:init',
# Plunger 2
'sour2:dc:trig:sour hold',
'sour2:volt:mode list',
'sour2:list:volt 0.6,0.425,0.25,0.075,-0.1,0.075,0.25,0.425',
'sour2:list:tmod auto',
'sour2:list:dwel 5e-06',
'sour2:list:dir up',
'sour2:list:coun 2',
'sour2:dc:trig:sour bus',
'sour2:dc:init:cont on',
'sour2:dc:init',
'sour2:dc:trig:sour int1',
'sour2:dc:init:cont on',
'sour2:dc:init',
# Start sweep
'tint 1'
]