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:

1name: "My Quantum Device"
2
3gates:
4 R1: {type: RESERVOIR, control_channel: 1, measure_channel: 1, v_lower_bound: -3.0, v_upper_bound: 3.0}
5 R2: {type: RESERVOIR, control_channel: 2, measure_channel: 2, v_lower_bound: -3.0, v_upper_bound: 3.0}
6 B0: {type: BARRIER, control_channel: 3, measure_channel: 3, v_lower_bound: -3.0, v_upper_bound: 3.0}
7 B1: {type: BARRIER, control_channel: 4, measure_channel: 4, v_lower_bound: -3.0, v_upper_bound: 3.0}
8 B2: {type: BARRIER, control_channel: 5, measure_channel: 5, v_lower_bound: -3.0, v_upper_bound: 3.0}
9 P1: {type: PLUNGER, control_channel: 6, measure_channel: 6, v_lower_bound: -3.0, v_upper_bound: 3.0}
10 P2: {type: PLUNGER, control_channel: 7, measure_channel: 7, v_lower_bound: -3.0, v_upper_bound: 3.0}
11
12contacts:
13 SOURCE: {type: SOURCE, control_channel: 8, measure_channel: 8, v_lower_bound: -3.0, v_upper_bound: 3.0}
14 DRAIN: {type: DRAIN, control_channel: 9, measure_channel: 9, v_lower_bound: -3.0, v_upper_bound: 3.0}
15
16routines:
17 - name: dqd_discovery
18 routines:
19 - name: compute_peak_spacing
20 parameters:
21 gates: [P1, P2, R1, R2, B0, B1, B2]
22 measure_electrode: DRAIN
23 min_search_scale: 0.05 # Minimum voltage scale (V)
24 max_search_scale: 0.2 # Maximum voltage scale (V)
25 current_trace_points: 128
26 max_number_of_samples: 30
27 number_of_samples_for_scale_computation: 10
28 seed: 42
29
30 - name: run_dqd_search_fixed_barriers
31 parameters:
32 gates: [P1, P2, R1, R2, B0, B1, B2]
33 measure_electrode: DRAIN
34 current_trace_points: 128
35 low_res_csd_points: 16
36 high_res_csd_points: 48
37 num_dqds_for_exit: 1
38 include_diagonals: false
39 seed: 42
40
41instruments:
42 - name: qdac2
43 type: GENERAL
44 driver: qdac2
45 ip_addr: 192.168.1.100
46 nplc: 10
47 sample_time: 10e-6
48 slew_rate: 1.0

Then run the DQD discovery sequence:

1from stanza.utils import load_device_config
2from stanza.routines import RoutineRunner
3import stanza.routines.builtins
4
5# Load device configuration
6config = load_device_config("device.yaml")
7
8# Create runner
9runner = RoutineRunner(configs=[config])
10
11# Run health check first (required)
12health_results = runner.run_all(parent_routine="device_health_check")
13
14# Compute peak spacing
15peak_result = runner.run("compute_peak_spacing")
16print(f"Peak spacing: {peak_result['peak_spacing'] * 1000:.2f} mV")
17
18# Run DQD search
19dqd_result = runner.run("run_dqd_search_fixed_barriers")
20dqd_squares = dqd_result["dqd_squares"]
21print(f"Found {len(dqd_squares)} DQD region(s)!")
22
23# Access best DQD
24if dqd_squares:
25 best_dqd = dqd_squares[0]
26 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.

1from stanza.routines.builtins.dqd_search import compute_peak_spacing
2
3result = runner.run("compute_peak_spacing")
4# 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.

1from stanza.routines.builtins.dqd_search import run_dqd_search_fixed_barriers
2
3result = runner.run("run_dqd_search_fixed_barriers")
4# 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 Stage 3 (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
Barrier voltage sweep parameter space. Green points show successful DQD discoveries across different barrier 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.

1from stanza.routines.builtins.dqd_search import run_dqd_search
2
3result = runner.run("run_dqd_search")
4# 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:

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

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

Then ensure it runs before your search routines:

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

Routine Parameters

Key parameters to tune for your device:

Peak Spacing:

1- name: compute_peak_spacing
2 parameters:
3 min_search_scale: 0.05 # 50 mV - lower bound for voltage scale testing
4 max_search_scale: 0.2 # 200 mV - upper bound for voltage scale testing
5 current_trace_points: 128
6 max_number_of_samples: 30 # Max attempts per scale
7 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).

1- name: run_dqd_search_fixed_barriers
2 parameters:
3 current_trace_points: 128 # Stage 1: 128 measurements
4 low_res_csd_points: 16 # Stage 2: 256 measurements
5 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:

1- name: run_dqd_search_fixed_barriers
2 parameters:
3 max_samples: 50 # Sample up to 50 grid squares (or 50% of grid, whichever smaller)
4 num_dqds_for_exit: 1 # Exit after finding 1 DQD
5 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:

1routines:
2 - name: full_device_characterization
3 routines:
4 # Health check must run first
5 - name: device_health_check
6 routines:
7 - name: noise_floor_measurement
8 parameters: {...}
9 - name: leakage_test
10 parameters: {...}
11 - name: global_accumulation
12 parameters: {...}
13 - name: reservoir_characterization
14 parameters: {...}
15 - name: finger_gate_characterization
16 parameters: {...}
17
18 # DQD discovery runs after health check
19 - name: dqd_discovery
20 routines:
21 - name: compute_peak_spacing
22 parameters: {...}
23 - name: run_dqd_search_fixed_barriers
24 parameters: {...}

Execute the entire workflow:

1results = runner.run_all(parent_routine="full_device_characterization")
2
3# Access results
4health_results = results["device_health_check"]
5peak_spacing = results["compute_peak_spacing"]["peak_spacing"]
6dqd_squares = results["run_dqd_search_fixed_barriers"]["dqd_squares"]

Advanced Usage

Visualizing DQD Results

Extract and visualize high-resolution charge stability diagrams:

1import matplotlib.pyplot as plt
2import numpy as np
3
4# Get best DQD
5dqd_result = runner.run("run_dqd_search_fixed_barriers")
6dqd_squares = dqd_result["dqd_squares"]
7
8if dqd_squares:
9 best_dqd = dqd_squares[0]
10
11 # Extract high-res CSD data
12 currents = np.array(best_dqd['high_res_csd_currents'])
13 voltages = np.array(best_dqd['high_res_csd_voltages'])
14
15 # Get voltage extent
16 v_p1 = voltages[:, :, 0] # P1 voltages
17 v_p2 = voltages[:, :, 1] # P2 voltages
18
19 # Create figure
20 fig, axes = plt.subplots(1, 2, figsize=(14, 6))
21
22 # Plot high-res CSD
23 im1 = axes[0].imshow(
24 currents,
25 extent=[v_p1.min(), v_p1.max(), v_p2.min(), v_p2.max()],
26 origin='lower',
27 aspect='auto',
28 cmap='viridis'
29 )
30 axes[0].set_xlabel('P1 Voltage (V)')
31 axes[0].set_ylabel('P2 Voltage (V)')
32 axes[0].set_title(f'High-Res CSD (Score: {best_dqd["high_res_csd_score"]:.3f})')
33 plt.colorbar(im1, ax=axes[0], label='Current (A)')
34
35 # Plot current trace
36 trace_currents = np.array(best_dqd['current_trace_currents'])
37 trace_distance = np.linspace(0, 1, len(trace_currents))
38
39 axes[1].plot(trace_distance, trace_currents, 'b-', linewidth=2)
40 axes[1].set_xlabel('Normalized Distance Along Diagonal')
41 axes[1].set_ylabel('Current (A)')
42 axes[1].set_title(f'Diagonal Trace (Score: {best_dqd["current_trace_score"]:.3f})')
43 axes[1].grid(True, alpha=0.3)
44
45 plt.tight_layout()
46 plt.show()

Custom Barrier Configurations

Run DQD search with specific barrier voltages:

1# Define custom barrier voltages
2custom_barriers = {
3 "B0": -1.2, # Outer barrier
4 "B1": -0.8, # Inner barrier
5 "B2": -1.2, # Outer barrier
6}
7
8# Run search with custom barriers
9dqd_result = runner.run(
10 "run_dqd_search_fixed_barriers",
11 barrier_voltages=custom_barriers
12)

Best Practices

1. Always Complete Health Check First

DQD search requires voltage bounds and gate characterization:

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

2. Start with Fixed Barriers

Use run_dqd_search_fixed_barriers before full barrier sweep:

1# Fast: test fixed barriers first
2dqd_result = runner.run("run_dqd_search_fixed_barriers")
3
4# If no DQDs found, try barrier sweep
5if not dqd_result["dqd_squares"]:
6 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:

1peak_result = runner.run("compute_peak_spacing")
2peak_spacing = peak_result["peak_spacing"]
3
4# Check if spacing is reasonable (typically 10-200 mV)
5if peak_spacing < 0.01 or peak_spacing > 0.3:
6 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:

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

6. Validate DQD Discoveries

Visually inspect high-res CSDs:

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

7. Use Reproducible Seeds

Set seed parameter for reproducible random sweeps:

1- name: compute_peak_spacing
2 parameters:
3 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:
    1sweep_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:

1from stanza.routines import RoutineRunner
2from stanza.utils import load_device_config
3from stanza.logger import DataLogger
4from stanza.routines.builtins import (
5 compute_peak_spacing,
6 run_dqd_search_fixed_barriers,
7 run_dqd_search
8)
9import numpy as np
10import matplotlib.pyplot as plt
11
12# Load configuration
13config = load_device_config("device.yaml")
14
15# Create logger
16logger = DataLogger(
17 name="logger",
18 routine_name="full_characterization",
19 base_dir="./data"
20)
21
22# Create runner
23runner = RoutineRunner(configs=[config], logger=logger)
24
25# Step 1: Health Check
26print("Step 1: Running health check...")
27health_results = runner.run_all(parent_routine="device_health_check")
28print(f" Global turn-on voltage: {health_results['global_accumulation']['global_turn_on_voltage']:.3f} V")
29print(f" Health check complete!\n")
30
31# Step 2: Peak Spacing
32print("Step 2: Computing peak spacing...")
33peak_result = runner.run("compute_peak_spacing")
34peak_spacing = peak_result['peak_spacing']
35print(f" Peak spacing: {peak_spacing * 1000:.2f} mV")
36print(f" Grid square size: {peak_spacing * 3 / np.sqrt(2) * 1000:.2f} mV\n")
37
38# Step 3: DQD Search
39print("Step 3: Running DQD search...")
40dqd_result = runner.run("run_dqd_search_fixed_barriers")
41dqd_squares = dqd_result["dqd_squares"]
42print(f" Found {len(dqd_squares)} DQD(s)\n")
43
44# Step 4: Analyze Results
45if dqd_squares:
46 print("Step 4: Analyzing best DQD...")
47 best = dqd_squares[0]
48 voltages = np.array(best['high_res_csd_voltages'])
49
50 print(f" Total score: {best['total_score']:.3f}")
51 print(f" Current trace score: {best['current_trace_score']:.3f}")
52 print(f" Low-res CSD score: {best['low_res_csd_score']:.3f}")
53 print(f" High-res CSD score: {best['high_res_csd_score']:.3f}")
54 print(f" P1 range: [{voltages[:,:,0].min():.4f}, {voltages[:,:,0].max():.4f}] V")
55 print(f" P2 range: [{voltages[:,:,1].min():.4f}, {voltages[:,:,1].max():.4f}] V")
56
57 # Visualize
58 currents = np.array(best['high_res_csd_currents'])
59 plt.figure(figsize=(8, 6))
60 plt.imshow(
61 currents,
62 extent=[voltages[:,:,0].min(), voltages[:,:,0].max(),
63 voltages[:,:,1].min(), voltages[:,:,1].max()],
64 origin='lower',
65 aspect='auto',
66 cmap='viridis'
67 )
68 plt.colorbar(label='Current (A)')
69 plt.xlabel('P1 Voltage (V)')
70 plt.ylabel('P2 Voltage (V)')
71 plt.title(f'DQD Charge Stability Diagram (Score: {best["total_score"]:.3f})')
72 plt.tight_layout()
73 plt.savefig('best_dqd.png')
74 print("\n Saved visualization to best_dqd.png")
75 print("\nDQD discovery complete!")
76else:
77 print("Step 4: No DQDs found")
78 print("\nConsider:")
79 print(" - Adjusting barrier voltages")
80 print(" - Running full barrier sweep: runner.run('run_dqd_search')")
81 print(" - Checking device connectivity")
82 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