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

QDevil QDAC2: Leakage sweep #158

Merged
merged 5 commits into from
Oct 17, 2022
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
136 changes: 136 additions & 0 deletions docs/examples/QDevil/QDAC2/GateLeakage.ipynb

Large diffs are not rendered by default.

99 changes: 94 additions & 5 deletions qcodes_contrib_drivers/drivers/QDevil/QDAC2.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import Any, NewType, Sequence, List, Dict, Tuple, Optional
from packaging.version import parse

# Version 0.18.0
# Version 0.21.0
#
# Guiding principles for this driver for QDevil QDAC-II
# -----------------------------------------------------
Expand Down Expand Up @@ -48,6 +48,15 @@
'specified for a wave form'


def diff_matrix(initial: Sequence[float],
measurements: Sequence[Sequence[float]]) -> np.ndarray:
"""Subtract an array of measurements by an initial measurement
"""
origin = np.asarray(initial)
matrix = np.asarray(measurements)
return matrix - np.asarray(list(itertools.repeat(initial, matrix.shape[1])))


"""External input trigger

There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4).
Expand Down Expand Up @@ -144,6 +153,10 @@ def __init__(self, parent: 'QDac2', name: str, external: int):
)


def ints_to_comma_separated_list(array: Sequence[int]):
return ','.join([str(x) for x in array])


def floats_to_comma_separated_list(array: Sequence[float]):
rounded = [format(x, 'g') for x in array]
return ','.join(rounded)
Expand Down Expand Up @@ -1663,6 +1676,10 @@ def correction_matrix(self) -> np.ndarray:
"""
return self._correction

@property
def gate_names(self) -> Sequence[str]:
return self._gate_names

def _allocate_internal_triggers(self,
internal_triggers: Optional[Sequence[str]]
) -> None:
Expand All @@ -1671,7 +1688,7 @@ def _allocate_internal_triggers(self,
for name in internal_triggers:
self._internal_triggers[name] = self._qdac.allocate_trigger()

def initiate_correction(self, gate: str, factors: Sequence[float]):
def initiate_correction(self, gate: str, factors: Sequence[float]) -> None:
"""Override how much a particular gate influences the other gates

Args:
Expand All @@ -1695,11 +1712,33 @@ def set_virtual_voltage(self, gate: str, voltage: float) -> None:
index = self._gate_index(gate)
except KeyError:
raise ValueError(f'No gate named "{gate}"')
self._effectuate_virtual_voltage(index, voltage)

def set_virtual_voltages(self, gates_to_voltages: Dict[str, float]) -> None:
"""Set virtual voltages on specific gates in one go

The actual voltage that each gate will receive depends on the
correction matrix.

Args:
gate_to_voltages (Dict[str,float]): gate to voltage map
"""
for gate, voltage in gates_to_voltages.items():
try:
index = self._gate_index(gate)
except KeyError:
raise ValueError(f'No gate named "{gate}"')
self._virtual_voltages[index] = voltage
self._effectuate_virtual_voltages()

def _effectuate_virtual_voltage(self, index: int, voltage: float) -> None:
self._virtual_voltages[index] = voltage
actual_V = self.actual_voltages()[index]
channel_number = self._channels[index]
self._qdac.channel(channel_number).dc_constant_V(actual_V)
self._effectuate_virtual_voltages()

def _effectuate_virtual_voltages(self) -> None:
for index, channel_number in enumerate(self._channels):
actual_V = self.actual_voltages()[index]
self._qdac.channel(channel_number).dc_constant_V(actual_V)

def add_correction(self, gate: str, factors: Sequence[float]) -> None:
"""Update how much a particular gate influences the other gates
Expand All @@ -1720,15 +1759,21 @@ def add_correction(self, gate: str, factors: Sequence[float]) -> None:
self._correction = np.matmul(multiplier, self._correction)

def _fix_gate_order(self, gates: Dict[str, int]) -> None:
self._gate_names = []
self._gates = {}
self._channels = []
index = 0
for gate, channel in gates.items():
self._gate_names.append(gate)
self._gates[gate] = index
index += 1
self._channels.append(channel)
self._virtual_voltages = np.zeros(self.shape)

@property
def channel_numbers(self) -> Sequence[int]:
return self._channels

def virtual_voltage(self, gate: str) -> float:
"""
Args:
Expand Down Expand Up @@ -1764,6 +1809,25 @@ def get_trigger_by_name(self, name: str) -> QDac2Trigger_Context:
print(f'Internal triggers: {list(self._internal_triggers.keys())}')
raise

def currents_A(self, nplc: int = 1) -> Sequence[float]:
"""Measure currents on all gates

Args:
nplc (int): Number of powerline cycles to average over
"""
slowest_line_freq = 50
channels_str = ints_to_comma_separated_list(self.channel_numbers)
channels_suffix = f'(@{channels_str})'
self._qdac.write(f'sens:rang low,{channels_suffix}')
self._qdac.write(f'sens:nplc {nplc},{channels_suffix}')
# Discard first reading because of possible output-capacitor effects, etc
sleep_s(1 / slowest_line_freq)
self._qdac.ask(f'read? {channels_suffix}')
# Then make the proper reading
sleep_s((nplc+1) / slowest_line_freq)
currents = self._qdac.ask(f'read? {channels_suffix}')
return comma_sequence_to_list_of_floats(currents)

def virtual_sweep(self, gate: str, voltages: Sequence[float],
start_sweep_trigger: Optional[str] = None,
step_time_s: float = 1e-5,
Expand Down Expand Up @@ -1888,6 +1952,31 @@ def _calculate_detune_values(self, gates: Sequence[str], start_V: Sequence[float
self._virtual_voltages[index] = voltage
return np.array(sweep)

def leakage(self, modulation_V: float, nplc: int = 2) -> np.ndarray:
"""Run a simple leakage test between the gates

Each gate is changed in turn and the resulting change in current from
steady-state is recorded. The resulting resistance matrix is calculated
as modulation_voltage divided by current_change.

Args:
modulation_V (float): Virtual voltage added to each gate
nplc (int, Optional): Powerline cycles to wait for each measurement

Returns:
ndarray: gate-to-gate resistance in Ohms
"""
steady_state_A = self.currents_A(nplc)
currents_matrix = []
for index, channel_nr in enumerate(self.channel_numbers):
original_V = self._virtual_voltages[index]
self._effectuate_virtual_voltage(index, original_V + modulation_V)
currents = self.currents_A(nplc)
self._effectuate_virtual_voltage(index, original_V)
currents_matrix.append(currents)
with np.errstate(divide='ignore'):
return np.abs(modulation_V / diff_matrix(steady_state_A, currents_matrix))

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

Expand Down
5 changes: 5 additions & 0 deletions qcodes_contrib_drivers/sims/QDAC2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ devices:
r: "0.01,0.02"
- q: "fetc2?"
r: "0.01,0.02"
- q: "sens:rang low,(@1,2,3)"
- q: "sens:nplc 1,(@1,2,3)"
- q: "sens:nplc 2,(@1,2,3)"
- q: "read? (@1,2,3)"
r: "0.1,0.2,0.3"

properties:
manual_trigger:
Expand Down
129 changes: 129 additions & 0 deletions qcodes_contrib_drivers/tests/QDevil/test_sim_qdac2_leakage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import pytest
from unittest.mock import MagicMock, call
import numpy as np
import itertools
import math
from .sim_qdac2_fixtures import qdac # noqa
from qcodes_contrib_drivers.drivers.QDevil.QDAC2 import diff_matrix


def test_diff_matrix():
# -----------------------------------------------------------------------
diff = diff_matrix([0.1,0.2], [[0.1,0.3],[0.3,0.2]])
# -----------------------------------------------------------------------
expected = np.array([[0.0,0.1], [0.2,0.0]])
assert np.allclose(diff, expected)


def test_arrangement_channel_numbers(qdac):
gates = {'sensor1': 1, 'plunger2': 2, 'plunger3': 3}
arrangement = qdac.arrange(gates)
# -----------------------------------------------------------------------
numbers = arrangement.channel_numbers
# -----------------------------------------------------------------------
assert numbers == [1,2,3]


def test_arrangement_steady_state(qdac, mocker):
sleep_fn = mocker.patch('qcodes_contrib_drivers.drivers.QDevil.QDAC2.sleep_s')
gates = {'sensor1': 1, 'plunger2': 2, 'plunger3': 3}
arrangement = qdac.arrange(gates)
qdac.start_recording_scpi()
# -----------------------------------------------------------------------
nplc=2
currents_A = arrangement.currents_A(nplc=nplc)
# -----------------------------------------------------------------------
assert currents_A == [0.1,0.2,0.3] # Hard-coded in simulation
commands = qdac.get_recorded_scpi_commands()
assert commands == [
'sens:rang low,(@1,2,3)',
'sens:nplc 2,(@1,2,3)',
# (Sleep 1 PLC)
'read? (@1,2,3)',
# (Sleep NPLC / line_freq)
'read? (@1,2,3)',
]
discard_s = 1/50
measure_s = (nplc+1)/50
sleep_fn.assert_has_calls([call(discard_s),call(measure_s)])


def test_arrangement_leakage(qdac, mocker): # noqa
sleep_fn = mocker.patch('qcodes_contrib_drivers.drivers.QDevil.QDAC2.sleep_s')
gates = {'sensor1': 1, 'plunger2': 2, 'plunger3': 3}
# Mock current readings
currents = {'sensor1': 0.1, 'plunger2': 0.2, 'plunger3': 0.3}
for gate, current_A in currents.items():
qdac.channel(gates[gate]).read_current_A = MagicMock(return_value=current_A)
arrangement = qdac.arrange(gates)
arrangement.set_virtual_voltages({'sensor1': 0.3, 'plunger3': 0.4})
# Mock clear_measurements to do nothing
arrangement.clear_measurements = MagicMock()
qdac.start_recording_scpi()
# -----------------------------------------------------------------------
nplc=2
leakage_matrix = arrangement.leakage(modulation_V=0.005, nplc=nplc)
# -----------------------------------------------------------------------
commands = qdac.get_recorded_scpi_commands()
assert commands == [
'sens:rang low,(@1,2,3)',
'sens:nplc 2,(@1,2,3)',
# Discard first reading
'read? (@1,2,3)',
# Steady-state reading
'read? (@1,2,3)',
# First modulation
'sour1:volt:mode fix',
'sour1:volt 0.305',
'sour2:volt:mode fix',
'sour2:volt 0.0',
'sour3:volt:mode fix',
'sour3:volt 0.4',
'sens:rang low,(@1,2,3)',
'sens:nplc 2,(@1,2,3)',
'read? (@1,2,3)',
'read? (@1,2,3)',
'sour1:volt:mode fix',
'sour1:volt 0.3',
'sour2:volt:mode fix',
'sour2:volt 0.0',
'sour3:volt:mode fix',
'sour3:volt 0.4',
# Second modulation
'sour1:volt:mode fix',
'sour1:volt 0.3',
'sour2:volt:mode fix',
'sour2:volt 0.005',
'sour3:volt:mode fix',
'sour3:volt 0.4',
'sens:rang low,(@1,2,3)',
'sens:nplc 2,(@1,2,3)',
'read? (@1,2,3)',
'read? (@1,2,3)',
'sour1:volt:mode fix',
'sour1:volt 0.3',
'sour2:volt:mode fix',
'sour2:volt 0.0',
'sour3:volt:mode fix',
'sour3:volt 0.4',
# Third modulation
'sour1:volt:mode fix',
'sour1:volt 0.3',
'sour2:volt:mode fix',
'sour2:volt 0.0',
'sour3:volt:mode fix',
'sour3:volt 0.405',
'sens:rang low,(@1,2,3)',
'sens:nplc 2,(@1,2,3)',
'read? (@1,2,3)',
'read? (@1,2,3)',
'sour1:volt:mode fix',
'sour1:volt 0.3',
'sour2:volt:mode fix',
'sour2:volt 0.0',
'sour3:volt:mode fix',
'sour3:volt 0.4',
]
astafan8 marked this conversation as resolved.
Show resolved Hide resolved
inf = math.inf
expected = [[inf, inf, inf], [inf, inf, inf], [inf, inf, inf]]
assert np.allclose(leakage_matrix, np.array(expected))
Loading