Skip to navigation

DQD Search

Stanza provides automated Double Quantum Dot (DQD) search routines for efficiently discovering voltage regions where two quantum dots form with stable charge configurations. After completing health check, DQD search is the next critical step in quantum dot tuning [2], using machine learning classifiers and adaptive grid search to locate promising operating points.

Overview

DQD Search automatically explores the plunger voltage space to find regions exhibiting characteristic double quantum dot charge stability diagrams. The routine follows a systematic multi-stage workflow:

  1. Peak Spacing Computation - Determine characteristic voltage spacing of Coulomb peaks by analyzing random sweeps
  2. Grid Generation - Partition plunger voltage space into grid squares based on peak spacing
  3. Adaptive Grid Search - Use machine learning classifiers to efficiently explore the grid with a three-stage hierarchy
  4. Intelligent Square Selection - Prioritize regions near confirmed DQDs based on spatial clustering

Each stage builds on previous results, efficiently focusing measurement effort on promising voltage regions while avoiding exhaustive grid scanning.

Prerequisites

Before running DQD search, you must complete device health check. DQD search requires:

  • Leakage test results: Safe voltage bounds for sweeps
  • Global accumulation results: Global turn-on voltage for gate initialization
  • Gate characterization results: Individual gate transition and saturation voltages

See the Health Check documentation for completing these prerequisite routines.

Quick Start

Configure the DQD search routine in your device YAML file:

name: "My Quantum Device"
gates:
R1: {type: RESERVOIR, control_channel: 1, measure_channel: 1, v_lower_bound: -3.0, v_upper_bound: 3.0}
R2: {type: RESERVOIR, control_channel: 2, measure_channel: 2, v_lower_bound: -3.0, v_upper_bound: 3.0}
B0: {type: BARRIER, control_channel: 3, measure_channel: 3, v_lower_bound: -3.0, v_upper_bound: 3.0}
B1: {type: BARRIER, control_channel: 4, measure_channel: 4, v_lower_bound: -3.0, v_upper_bound: 3.0}
B2: {type: BARRIER, control_channel: 5, measure_channel: 5, v_lower_bound: -3.0, v_upper_bound: 3.0}
P1: {type: PLUNGER, control_channel: 6, measure_channel: 6, v_lower_bound: -3.0, v_upper_bound: 3.0}
P2: {type: PLUNGER, control_channel: 7, measure_channel: 7, v_lower_bound: -3.0, v_upper_bound: 3.0}
contacts:
SOURCE: {type: SOURCE, control_channel: 8, measure_channel: 8, v_lower_bound: -3.0, v_upper_bound: 3.0}
DRAIN: {type: DRAIN, control_channel: 9, measure_channel: 9, v_lower_bound: -3.0, v_upper_bound: 3.0}
routines:
- name: dqd_discovery
routines:
- name: compute_peak_spacing
parameters:
gates: [P1, P2, R1, R2, B0, B1, B2]
measure_electrode: DRAIN
min_search_scale: 0.05 # Minimum voltage scale (V)
max_search_scale: 0.2 # Maximum voltage scale (V)
current_trace_points: 128
max_number_of_samples: 30
number_of_samples_for_scale_computation: 10
seed: 42
- name: run_dqd_search_fixed_barriers
parameters:
gates: [P1, P2, R1, R2, B0, B1, B2]
measure_electrode: DRAIN
current_trace_points: 128
low_res_csd_points: 16
high_res_csd_points: 48
num_dqds_for_exit: 1
include_diagonals: false
seed: 42
instruments:
- name: qdac2
type: GENERAL
driver: qdac2
ip_addr: 192.168.1.100
nplc: 10
sample_time: 10e-6
slew_rate: 1.0

Then run the DQD discovery sequence:

from stanza.utils import load_device_config
from stanza.routines import RoutineRunner
import stanza.routines.builtins
# Load device configuration
config = load_device_config("device.yaml")
# Create runner
runner = RoutineRunner(configs=[config])
# Run health check first (required)
health_results = runner.run_all(parent_routine="device_health_check")
# Compute peak spacing
peak_result = runner.run("compute_peak_spacing")
print(f"Peak spacing: {peak_result['peak_spacing'] * 1000:.2f} mV")
# Run DQD search
dqd_result = runner.run("run_dqd_search_fixed_barriers")
dqd_squares = dqd_result["dqd_squares"]
print(f"Found {len(dqd_squares)} DQD region(s)!")
# Access best DQD
if dqd_squares:
best_dqd = dqd_squares[0]
print(f"Best DQD score: {best_dqd['total_score']:.3f}")

DQD Search Routines

1. Peak Spacing Computation

Peak spacing computation
Random diagonal sweeps (left) and Coulomb peak detection (right). Inter-peak spacings determine the grid square size.

Purpose: Determine the characteristic voltage spacing of Coulomb peaks [3] by analyzing random sweeps through plunger voltage space.

Peak spacing computation provides the fundamental length scale for DQD search by measuring typical distances between charge transitions. This spacing is used to size the grid squares for efficient exploration.

from stanza.routines.builtins.dqd_search import compute_peak_spacing
result = runner.run("compute_peak_spacing")
# Returns: {"peak_spacing": <float>}

Parameters:

  • gates (list): All gate electrode names (plungers, barriers, reservoirs)
  • measure_electrode (str): Electrode to measure current from (e.g., “DRAIN”)
  • min_search_scale (float): Minimum voltage scale to test in volts. Typical: 0.05 V (50 mV)
  • max_search_scale (float): Maximum voltage scale to test in volts. Typical: 0.2 V (200 mV)
  • current_trace_points (int, default=128): Number of voltage points per sweep trace
  • max_number_of_samples (int, default=30): Maximum sweep attempts per voltage scale
  • number_of_samples_for_scale_computation (int, default=10): Target number of successful peak detections
  • seed (int, default=42): Random seed for reproducibility
  • session (LoggerSession, optional): Session for logging measurements
  • barrier_voltages (dict, optional): Custom barrier voltages (overrides health check values)

Returns:

  • peak_spacing (float): Median peak spacing in volts

How It Works:

  1. Tests multiple voltage scales linearly spaced from min_search_scale to max_search_scale
  2. For each scale, generates random diagonal sweeps through plunger voltage space
  3. Measures current along each sweep and classifies for Coulomb blockade using ML model coulomb-blockade-classifier-v3
  4. If blockade detected, uses peak detector (coulomb-blockade-peak-detector-v2) to identify individual Coulomb peaks
  5. Computes inter-peak voltage spacings and collects statistics across all successful measurements
  6. Returns median spacing, which determines grid square size: grid_square_size = peak_spacing × 3/√2 ≈ 2.12 × peak_spacing. This factor ensures the grid square spans approximately three Coulomb peaks along each plunger axis, assuming equal contributions from both gates to the diagonal spacing.

Usage Notes:

  • Requires voltage bounds from leakage test (max_safe_voltage_bound, min_safe_voltage_bound)
  • Uses gate characterization voltages (transition and saturation) from health check
  • Automatically sets plunger voltage bounds based on gate characterization
  • Early exits when enough samples collected across all scales
  • Raises ValueError if no valid peak spacings detected

2. DQD Search with Fixed Barriers

Adaptive grid search
Three-stage hierarchy: diagonal traces filter regions (left), low-res CSDs confirm features (center), high-res CSDs characterize DQDs (right).

Purpose: Efficiently explore plunger voltage space to find DQD regions using adaptive grid search with a three-stage classification hierarchy.

This routine implements the core DQD search algorithm, using intelligent square selection and multi-resolution measurements to minimize total measurement time while maximizing DQD discovery probability.

from stanza.routines.builtins.dqd_search import run_dqd_search_fixed_barriers
result = runner.run("run_dqd_search_fixed_barriers")
# Returns: {"dqd_squares": [dict, ...]}

Parameters:

  • gates (list): All gate electrode names
  • measure_electrode (str): Electrode to measure current from
  • current_trace_points (int, default=128): Points in diagonal current trace for Stage 1
  • low_res_csd_points (int, default=16): Points per axis in low-resolution CSD for Stage 2
  • high_res_csd_points (int, default=48): Points per axis in high-resolution CSD for Stage 3
  • max_samples (int, optional): Maximum grid squares to sample. Default: 50% of total grid squares
  • num_dqds_for_exit (int, default=1): Exit after finding this many DQDs
  • include_diagonals (bool, default=False): Use 8-connected neighborhoods (True) vs 4-connected (False)
  • seed (int, default=42): Random seed for reproducibility
  • session (LoggerSession, optional): Session for logging measurements
  • barrier_voltages (dict, optional): Custom barrier voltages

Returns:

  • dqd_squares (list): List of confirmed DQD squares, sorted by descending total score. Each square contains:
    • grid_idx (int): Linear index in grid
    • current_trace_currents (array): Stage 1 current measurements
    • current_trace_voltages (array): Stage 1 voltage coordinates
    • current_trace_score (float): Stage 1 ML classification score
    • current_trace_classification (bool): Stage 1 classification result
    • low_res_csd_currents (array or None): Stage 2 current grid
    • low_res_csd_voltages (array or None): Stage 2 voltage coordinates
    • low_res_csd_score (float): Stage 2 ML classification score
    • low_res_csd_classification (bool): Stage 2 classification result
    • high_res_csd_currents (array or None): Stage 3 current grid
    • high_res_csd_voltages (array or None): Stage 3 voltage coordinates
    • high_res_csd_score (float): Stage 3 ML classification score
    • high_res_csd_classification (bool): Stage 3 classification result
    • total_score (float): Sum of all stage scores
    • is_dqd (bool): True if high-res CSD classified as DQD

How It Works:

The routine uses a hierarchical three-stage classification to efficiently filter grid squares:

Stage 1: Current Trace (Fast Screening)

  • Measures diagonal sweep through grid square (typically 128 points)
  • ML classifier: coulomb-blockade-classifier-v3
  • If classification fails, skip to next square (saves 256-2,304 measurements)

Stage 2: Low-Resolution CSD (Intermediate Check)

  • 2D grid sweep (typically 16x16 = 256 points)
  • ML classifier: charge-stability-diagram-binary-classifier-v2-16x16
  • Confirms DQD-like features with reasonable measurement cost
  • If classification fails, skip to next square (saves 2,048 measurements for 48x48)

Stage 3: High-Resolution CSD (Confirmation)

  • 2D grid sweep (typically 48x48 = 2,304 points)
  • ML classifier: charge-stability-diagram-binary-classifier-v1-48x48
  • High-quality CSD for detailed characterization
  • If classification succeeds, square confirmed as DQD

Intelligent Square Selection: After the first random square, prioritizes exploration using spatial clustering:

  1. DQD Neighbors: Explore unvisited neighbors of confirmed DQD squares (weighted by proximity and scores)
  2. High-Score Neighbors: Explore neighbors of squares with total_score > 1.5 (passed Stage 1 and possibly Stage 2)
  3. Random Exploration: When no promising neighbors, randomly sample unvisited squares

The search terminates when:

  • num_dqds_for_exit DQDs are found, OR
  • max_samples squares are visited, OR
  • All grid squares are visited

Usage Notes:

  • Requires peak_spacing from prior compute_peak_spacing routine
  • Uses voltage bounds and gate characterization from health check
  • Measurements automatically logged if session provided
  • Grid squares that exceed voltage bounds are automatically skipped
  • Results sorted by descending total_score for easy access to best candidates

3. DQD Search with Barrier Sweep

DQD barrier sweep
Final DQD potential energy landscape showing two quantum dot wells. The inner barrier potential is raised while the outer barrier potentials are lowered to find optimal configurations.

Purpose: Comprehensive DQD search that sweeps barrier voltages to explore different tunnel coupling regimes.

This routine extends the fixed-barrier search by systematically varying barrier voltages to find optimal configurations. It’s useful when fixed barriers don’t yield DQDs or when exploring the full parameter space.

from stanza.routines.builtins.dqd_search import run_dqd_search
result = runner.run("run_dqd_search")
# Returns: {"run_dqd_search": [dict with outer_barrier_voltage, inner_barrier_voltage, peak_spacing, dqd_squares]}

Parameters:

  • gates (list): All gate electrode names
  • measure_electrode (str): Electrode to measure current from
  • min_search_scale (float): Minimum voltage scale for peak spacing computation (V)
  • max_search_scale (float): Maximum voltage scale for peak spacing computation (V)
  • current_trace_points (int): Points per current trace
  • outer_barrier_points (int, default=5): Number of sweep points for outer barriers (B0, B2)
  • inner_barrier_points (int, default=5): Number of sweep points for inner barrier (B1)
  • num_dqds_for_exit (int, default=1): Number of DQDs required to exit barrier sweep
  • session (LoggerSession, optional): Session for logging

Returns:

  • run_dqd_search (list): List of results for each barrier configuration tested, each containing:
    • outer_barrier_voltage (float): Voltage applied to outer barriers (B0, B2)
    • inner_barrier_voltage (float): Voltage applied to inner barrier (B1)
    • peak_spacing (dict): Result from compute_peak_spacing at this barrier configuration
    • dqd_squares (list): DQD squares found at this barrier configuration

How It Works:

  1. Determines barrier sweep ranges:
    • Outer barriers (B0, B2): From global turn-on voltage to mean transition voltage (decreasing tunnel coupling)
    • Inner barrier (B1): From transition voltage to global turn-on voltage (increasing tunnel coupling)
  2. For each barrier configuration:
    • Sets barrier voltages
    • Runs compute_peak_spacing to determine grid size
    • Runs run_dqd_search_fixed_barriers to search for DQDs
  3. Early exits if num_dqds_for_exit DQDs found at any configuration

Usage Notes:

  • WARNING: Can be time-consuming! Default 5x5 grid = 25 barrier configurations
  • Each configuration runs full peak spacing and grid search
  • Middle barrier (B1) swept from transition to saturation ensures sufficient tunnel coupling for observable transition lines
  • Higher tunnel coupling (B1 closer to transition voltage) typically yields more prominent transition lines
  • Lower tunnel coupling (B1 closer to saturation voltage) may form bias triangles, harder for ML models to detect
  • Results ordered by barrier sweep, find successful configurations with [r for r in result['run_dqd_search'] if len(r['dqd_squares']) > 0]

Machine Learning Classifiers

DQD search uses several specialized ML models trained on quantum dot measurement data [1]. For detailed specifications and performance metrics, refer to the Models documentation:

All classifiers return a score indicating confidence. Higher scores indicate stronger feature detection. The hierarchical use of classifiers minimizes total measurement time by filtering out unpromising regions early.

Configuration Guide

Device Configuration

DQD search requires proper gate type annotations and sufficient plunger gates:

gates:
# Reservoir gates - connect to electron/hole reservoirs
R1: {type: RESERVOIR, control_channel: 1, measure_channel: 1, v_lower_bound: -3.0, v_upper_bound: 3.0}
R2: {type: RESERVOIR, control_channel: 2, measure_channel: 2, v_lower_bound: -3.0, v_upper_bound: 3.0}
# Barrier gates - control tunneling between dots and reservoirs
B0: {type: BARRIER, control_channel: 3, measure_channel: 3, v_lower_bound: -3.0, v_upper_bound: 3.0}
B1: {type: BARRIER, control_channel: 4, measure_channel: 4, v_lower_bound: -3.0, v_upper_bound: 3.0}
B2: {type: BARRIER, control_channel: 5, measure_channel: 5, v_lower_bound: -3.0, v_upper_bound: 3.0}
# Plunger gates - control dot chemical potential (need at least 2 for DQD)
P1: {type: PLUNGER, control_channel: 6, measure_channel: 6, v_lower_bound: -3.0, v_upper_bound: 3.0}
P2: {type: PLUNGER, control_channel: 7, measure_channel: 7, v_lower_bound: -3.0, v_upper_bound: 3.0}

ML Model Setup

To use the machine learning classifiers, you must initialize the Conductor Quantum SDK using the setup_models_sdk routine. This routine authenticates with the platform and makes the models client available to the DQD search routines.

Add it to your configuration sequence before running DQD search:

routines:
- name: setup_models_sdk
parameters:
token: "${CQ_API_TOKEN}" # Token can be substituted at runtime

Then ensure it runs before your search routines:

# Run setup first
runner.run("setup_models_sdk", token="your-actual-token-here")
# Then run DQD search
runner.run("dqd_discovery")

Routine Parameters

Key parameters to tune for your device:

Peak Spacing:

- name: compute_peak_spacing
parameters:
min_search_scale: 0.05 # 50 mV - lower bound for voltage scale testing
max_search_scale: 0.2 # 200 mV - upper bound for voltage scale testing
current_trace_points: 128
max_number_of_samples: 30 # Max attempts per scale
number_of_samples_for_scale_computation: 10 # Target successful samples

Trade-offs:

  • Wider scale range: More likely to find peaks, but longer computation time
  • More samples: Better statistics, but slower
  • More trace points: Better resolution for peak detection, but slower per trace

Grid Search Resolution:

It is recommended to use the default resolutions as the machine learning models provided with Stanza are specifically designed and trained for these input sizes (128/16/48).

- name: run_dqd_search_fixed_barriers
parameters:
current_trace_points: 128 # Stage 1: 128 measurements
low_res_csd_points: 16 # Stage 2: 256 measurements
high_res_csd_points: 48 # Stage 3: 2,304 measurements

Changing these resolutions is discouraged as it may significantly degrade model performance. The default models expect these specific input dimensions. Only modify these parameters if you are supplying your own custom ML models trained on different resolutions.

Search Control:

- name: run_dqd_search_fixed_barriers
parameters:
max_samples: 50 # Sample up to 50 grid squares (or 50% of grid, whichever smaller)
num_dqds_for_exit: 1 # Exit after finding 1 DQD
include_diagonals: false # Use 4-connected neighborhoods

Trade-offs:

  • Larger max_samples: More thorough exploration, longer measurement time
  • Higher num_dqds_for_exit: Find multiple DQD regions, takes longer
  • include_diagonals=true: Denser exploration (8 neighbors instead of 4), may find nearby DQDs faster

Nested Routine Execution

Organize DQD discovery as a nested routine workflow:

routines:
- name: full_device_characterization
routines:
# Health check must run first
- name: device_health_check
routines:
- name: noise_floor_measurement
parameters: {...}
- name: leakage_test
parameters: {...}
- name: global_accumulation
parameters: {...}
- name: reservoir_characterization
parameters: {...}
- name: finger_gate_characterization
parameters: {...}
# DQD discovery runs after health check
- name: dqd_discovery
routines:
- name: compute_peak_spacing
parameters: {...}
- name: run_dqd_search_fixed_barriers
parameters: {...}

Execute the entire workflow:

results = runner.run_all(parent_routine="full_device_characterization")
# Access results
health_results = results["device_health_check"]
peak_spacing = results["compute_peak_spacing"]["peak_spacing"]
dqd_squares = results["run_dqd_search_fixed_barriers"]["dqd_squares"]

Advanced Usage

Visualizing DQD Results

Extract and visualize high-resolution charge stability diagrams:

import matplotlib.pyplot as plt
import numpy as np
# Get best DQD
dqd_result = runner.run("run_dqd_search_fixed_barriers")
dqd_squares = dqd_result["dqd_squares"]
if dqd_squares:
best_dqd = dqd_squares[0]
# Extract high-res CSD data
currents = np.array(best_dqd['high_res_csd_currents'])
voltages = np.array(best_dqd['high_res_csd_voltages'])
# Get voltage extent
v_p1 = voltages[:, :, 0] # P1 voltages
v_p2 = voltages[:, :, 1] # P2 voltages
# Create figure
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Plot high-res CSD
im1 = axes[0].imshow(
currents,
extent=[v_p1.min(), v_p1.max(), v_p2.min(), v_p2.max()],
origin='lower',
aspect='auto',
cmap='viridis'
)
axes[0].set_xlabel('P1 Voltage (V)')
axes[0].set_ylabel('P2 Voltage (V)')
axes[0].set_title(f'High-Res CSD (Score: {best_dqd["high_res_csd_score"]:.3f})')
plt.colorbar(im1, ax=axes[0], label='Current (A)')
# Plot current trace
trace_currents = np.array(best_dqd['current_trace_currents'])
trace_distance = np.linspace(0, 1, len(trace_currents))
axes[1].plot(trace_distance, trace_currents, 'b-', linewidth=2)
axes[1].set_xlabel('Normalized Distance Along Diagonal')
axes[1].set_ylabel('Current (A)')
axes[1].set_title(f'Diagonal Trace (Score: {best_dqd["current_trace_score"]:.3f})')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Custom Barrier Configurations

Run DQD search with specific barrier voltages:

# Define custom barrier voltages
custom_barriers = {
"B0": -1.2, # Outer barrier
"B1": -0.8, # Inner barrier
"B2": -1.2, # Outer barrier
}
# Run search with custom barriers
dqd_result = runner.run(
"run_dqd_search_fixed_barriers",
barrier_voltages=custom_barriers
)

Best Practices

1. Always Complete Health Check First

DQD search requires voltage bounds and gate characterization:

# Required order
health_results = runner.run_all(parent_routine="device_health_check")
# Now safe to run DQD search
peak_result = runner.run("compute_peak_spacing")

2. Start with Fixed Barriers

Use run_dqd_search_fixed_barriers before full barrier sweep:

# Fast: test fixed barriers first
dqd_result = runner.run("run_dqd_search_fixed_barriers")
# If no DQDs found, try barrier sweep
if not dqd_result["dqd_squares"]:
sweep_result = runner.run("run_dqd_search")

3. Use Appropriate Resolution

Balance measurement quality vs time:

  • Quick exploration: 128/16/32 (current_trace/low_res/high_res)
  • Standard quality: 128/16/48 (recommended starting point)
  • Publication quality: 128/32/64 (slower but better CSDs)

4. Monitor Peak Spacing Results

Verify peak spacing is reasonable:

peak_result = runner.run("compute_peak_spacing")
peak_spacing = peak_result["peak_spacing"]
# Check if spacing is reasonable (typically 10-200 mV)
if peak_spacing < 0.01 or peak_spacing > 0.3:
print(f"Warning: peak spacing {peak_spacing*1000:.1f} mV may be outside typical range")

5. Set Reasonable Exit Conditions

num_dqds_for_exit=1 is often sufficient:

# Fast: exit after first DQD
runner.run("run_dqd_search_fixed_barriers", num_dqds_for_exit=1)
# Comprehensive: find multiple DQDs
runner.run("run_dqd_search_fixed_barriers", num_dqds_for_exit=3, max_samples=100)

6. Validate DQD Discoveries

Visually inspect high-res CSDs:

# Check that top candidates have proper DQD features
for i, dqd in enumerate(dqd_squares[:3]): # Top 3
print(f"DQD {i+1}: score={dqd['total_score']:.3f}")
# Plot to verify honeycomb pattern

7. Use Reproducible Seeds

Set seed parameter for reproducible random sweeps:

- name: compute_peak_spacing
parameters:
seed: 42 # Reproducible random sweeps

Troubleshooting

No Peak Spacings Found

Symptom: ValueError: No peak spacings found.

Causes:

  • Health check not completed
  • Voltage scale range too narrow
  • Device not showing Coulomb blockade
  • Barrier voltages not optimal

Solutions:

  1. Verify health check completed: check ctx.results for gate characterization
  2. Increase max_search_scale to test larger voltage ranges (e.g., 0.3 V)
  3. Increase max_number_of_samples for more attempts per scale (e.g., 50)
  4. Check device connectivity and current measurements
  5. Try manual barrier voltage adjustments for better tunnel coupling

Symptom: dqd_squares is empty after search completes

Causes:

  • Barrier voltages don’t allow sufficient tunnel coupling
  • Peak spacing incorrect
  • Plunger voltage bounds too narrow
  • Grid resolution too coarse

Solutions:

  1. Try run_dqd_search to sweep barrier voltages:
    sweep_result = runner.run("run_dqd_search")
  2. Increase max_samples to explore more grid squares
  3. Verify plunger voltage bounds are adequate (check health check results)
  4. Increase high_res_csd_points for better resolution (e.g., 64 instead of 48)
  5. Review logged low-score squares - may indicate near-misses

Grid Search Too Slow

Symptom: Search takes too long to complete

Causes:

  • Resolution too high
  • max_samples too large
  • Grid too fine (peak spacing too small)

Solutions:

  1. Reduce resolution: use 128/16/32 instead of 128/16/48
  2. Reduce max_samples for faster termination (e.g., 20% of grid)
  3. Set num_dqds_for_exit: 1 to exit after first DQD
  4. Use include_diagonals: false for sparser exploration
  5. If peak spacing is very small (<30 mV), consider adjusting search scales

False Positive DQD Classifications

Symptom: High-res CSDs don’t show clear DQD features

Causes:

  • ML classifier over-predicting
  • Measurement artifacts or noise
  • Insufficient resolution

Solutions:

  1. Review top candidates visually - plot high-res CSDs
  2. Check total_score values - higher scores (>2.0) more reliable
  3. Increase high_res_csd_points for clearer features (e.g., 64)
  4. Review Stage 1 and Stage 2 scores - all stages should agree
  5. Consider manual validation of top candidates

Voltages Out of Bounds

Symptom: Grid squares skipped or voltage errors during search

Causes:

  • Grid extends beyond safe voltage bounds
  • Gate characterization voltages incorrect

Solutions:

  1. Check leakage test results for safe voltage bounds
  2. Verify gate characterization completed successfully
  3. Algorithm automatically skips out-of-bounds squares - this is expected
  4. If too many squares skipped, adjust plunger voltage bounds in device config

Complete Example: DQD Discovery Workflow

Here’s a complete example from health check to DQD discovery:

from stanza.routines import RoutineRunner
from stanza.utils import load_device_config
from stanza.logger import DataLogger
from stanza.routines.builtins import (
compute_peak_spacing,
run_dqd_search_fixed_barriers,
run_dqd_search
)
import numpy as np
import matplotlib.pyplot as plt
# Load configuration
config = load_device_config("device.yaml")
# Create logger
logger = DataLogger(
name="logger",
routine_name="full_characterization",
base_dir="./data"
)
# Create runner
runner = RoutineRunner(configs=[config], logger=logger)
# Step 1: Health Check
print("Step 1: Running health check...")
health_results = runner.run_all(parent_routine="device_health_check")
print(f" Global turn-on voltage: {health_results['global_accumulation']['global_turn_on_voltage']:.3f} V")
print(f" Health check complete!\n")
# Step 2: Peak Spacing
print("Step 2: Computing peak spacing...")
peak_result = runner.run("compute_peak_spacing")
peak_spacing = peak_result['peak_spacing']
print(f" Peak spacing: {peak_spacing * 1000:.2f} mV")
print(f" Grid square size: {peak_spacing * 3 / np.sqrt(2) * 1000:.2f} mV\n")
# Step 3: DQD Search
print("Step 3: Running DQD search...")
dqd_result = runner.run("run_dqd_search_fixed_barriers")
dqd_squares = dqd_result["dqd_squares"]
print(f" Found {len(dqd_squares)} DQD(s)\n")
# Step 4: Analyze Results
if dqd_squares:
print("Step 4: Analyzing best DQD...")
best = dqd_squares[0]
voltages = np.array(best['high_res_csd_voltages'])
print(f" Total score: {best['total_score']:.3f}")
print(f" Current trace score: {best['current_trace_score']:.3f}")
print(f" Low-res CSD score: {best['low_res_csd_score']:.3f}")
print(f" High-res CSD score: {best['high_res_csd_score']:.3f}")
print(f" P1 range: [{voltages[:,:,0].min():.4f}, {voltages[:,:,0].max():.4f}] V")
print(f" P2 range: [{voltages[:,:,1].min():.4f}, {voltages[:,:,1].max():.4f}] V")
# Visualize
currents = np.array(best['high_res_csd_currents'])
plt.figure(figsize=(8, 6))
plt.imshow(
currents,
extent=[voltages[:,:,0].min(), voltages[:,:,0].max(),
voltages[:,:,1].min(), voltages[:,:,1].max()],
origin='lower',
aspect='auto',
cmap='viridis'
)
plt.colorbar(label='Current (A)')
plt.xlabel('P1 Voltage (V)')
plt.ylabel('P2 Voltage (V)')
plt.title(f'DQD Charge Stability Diagram (Score: {best["total_score"]:.3f})')
plt.tight_layout()
plt.savefig('best_dqd.png')
print("\n Saved visualization to best_dqd.png")
print("\nDQD discovery complete!")
else:
print("Step 4: No DQDs found")
print("\nConsider:")
print(" - Adjusting barrier voltages")
print(" - Running full barrier sweep: runner.run('run_dqd_search')")
print(" - Checking device connectivity")
print(" - Reviewing logged data for near-misses")

References

[1] Moon, H., et al. Machine learning enables completely automatic tuning of a quantum device faster than human experts. Nat Commun 11, 4161 (2020). https://doi.org/10.1038/s41467-020-17835-9

[2] Zwolak, J. P., et al. Autotuning of double-dot devices in situ with machine learning. Phys. Rev. Applied 13, 034075 (2020). https://doi.org/10.1103/PhysRevApplied.13.034075

[3] van der Wiel, W. G., et al. Electron transport through double quantum dots. Rev. Mod. Phys. 75, 1 (2002). https://doi.org/10.1103/RevModPhys.75.1