> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.conductorquantum.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.conductorquantum.com/_mcp/server.

# Device Configuration

Think of device configuration as your lab notebook for defining a quantum device. Instead of hardcoding voltages and channel numbers in your Python scripts, you describe your device topology once in a YAML file. This makes it easy to work with different devices, share configurations with collaborators, and keep your experimental code clean.

## Your First Device Configuration

Let's configure a simple single quantum dot device. Create a file called `device.yaml`:

```yaml
name: "My Billion Qubit Device"

gates:
  PLUNGER:
    type: PLUNGER
    control_channel: 1
    v_lower_bound: -2.0
    v_upper_bound: 0.0

  BARRIER:
    type: BARRIER
    control_channel: 2
    v_lower_bound: -1.5
    v_upper_bound: 0.0

contacts:
  SOURCE:
    type: SOURCE
    measure_channel: 1
    control_channel: 3
    v_lower_bound: -0.01
    v_upper_bound: 0.01

  DRAIN:
    type: DRAIN
    measure_channel: 2
    control_channel: 4
    v_lower_bound: -0.01
    v_upper_bound: 0.01

gpios:
  VSS:
    type: INPUT
    control_channel: 5
    v_lower_bound: 0
    v_upper_bound: 3.3

  VDD:
    type: INPUT
    control_channel: 6
    v_lower_bound: -3.3
    v_upper_bound: 0

instruments:
  - name: qdac
    type: GENERAL
    driver: qdac2
    ip_addr: 192.168.1.100
    slew_rate: 0.5

routines:
  - name: pinchoff_sweep
    parameters:
      gate: BARRIER
      v_start: -1.5
      v_stop: 0.0
      n_points: 100
      contact: DRAIN
```

Now you can control your device using gate names instead of channel numbers:

```python
from stanza.utils import device_from_yaml
from stanza.models import PadType

device = device_from_yaml("device.yaml")

# Set voltages using descriptive names
device.jump({"PLUNGER": -1.2, "BARRIER": -0.8})

# Measure current
current = device.measure("DRAIN")

# Zero all gates and contacts (default)
device.zero(PadType.ALL)

# Zero only gates
device.zero(PadType.GATE)

# Zero only contacts
device.zero(PadType.CONTACT)

# Zero only GPIO pads
device.zero(PadType.GPIO)
```

## Understanding Gates

Gates control the electrostatic landscape of your device. Stanza supports four gate types:

* **PLUNGER**: Controls charge occupation in quantum dots
* **BARRIER**: Controls tunnel coupling between regions
* **RESERVOIR**: Connects to electron reservoirs
* **SCREEN**: Provides electrostatic screening

### Example: Double Quantum Dot

Here's how you might configure a double quantum dot device:

```yaml
gates:
  # Plunger gates control chemical potential
  LEFT_PLUNGER:
    type: PLUNGER
    control_channel: 1
    v_lower_bound: -2.0
    v_upper_bound: 0.0

  RIGHT_PLUNGER:
    type: PLUNGER
    control_channel: 2
    v_lower_bound: -2.0
    v_upper_bound: 0.0

  # Barrier gates control tunnel coupling
  LEFT_BARRIER:
    type: BARRIER
    control_channel: 3
    v_lower_bound: -1.5
    v_upper_bound: 0.0

  CENTER_BARRIER:
    type: BARRIER
    control_channel: 4
    v_lower_bound: -1.5
    v_upper_bound: 0.0

  RIGHT_BARRIER:
    type: BARRIER
    control_channel: 5
    v_lower_bound: -1.5
    v_upper_bound: 0.0
```

The voltage bounds act as safety limits - if your code tries to exceed them, Stanza raises an error before touching the hardware.

### Working with Gate Groups

Filter gates by type for common operations:

```python
# Get all plunger gates
plungers = device.get_gates_by_type("PLUNGER")
# ["LEFT_PLUNGER", "RIGHT_PLUNGER"]

# Sweep all barriers together
barriers = device.get_gates_by_type("BARRIER")
voltages = [-1.5, -1.0, -0.5, 0.0]
device.sweep_nd(barriers, voltages, contact="DRAIN")
```

### Example: Measurement-Only Contact

For contacts that only measure without voltage control:

```yaml
contacts:
  LEFT_RESERVOIR:
    type: SOURCE
    measure_channel: 1
    control_channel: 6
    v_lower_bound: -0.01
    v_upper_bound: 0.01

  RIGHT_RESERVOIR:
    type: DRAIN
    measure_channel: 2
    control_channel: 7
    v_lower_bound: -0.01
    v_upper_bound: 0.01

  SENSOR_DRAIN:
    type: DRAIN
    measure_channel: 3  # No voltage control needed
```

Use measurement-only contacts for fast current measurements during sweeps:

```python
import numpy as np

# Sweep and measure at sensor
voltages = np.linspace(-2.0, 0.0, 100)
v_data, i_data = device.sweep_1d(
    "LEFT_PLUNGER",
    voltages.tolist(),
    "SENSOR_DRAIN"
)
```

## Contacts: Where You Measure

Contacts are where current flows in and out of your device. Stanza supports SOURCE and DRAIN contact types for DC transport measurements.

### Example: DC Transport with Bias

Configure contacts that can both measure current and apply bias voltage:

```yaml
contacts:
  LEFT_RESERVOIR:
    type: SOURCE
    measure_channel: 1
    control_channel: 6
    v_lower_bound: -0.01  # 10 mV bias range
    v_upper_bound: 0.01

  RIGHT_RESERVOIR:
    type: DRAIN
    measure_channel: 2
    control_channel: 7
    v_lower_bound: -0.01
    v_upper_bound: 0.01
```

Sweep bias voltage to measure Coulomb diamonds:

```python
# Apply 10 mV bias
device.jump({"LEFT_RESERVOIR": 0.005, "RIGHT_RESERVOIR": -0.005})

# Measure conductance
current = device.measure("RIGHT_RESERVOIR")
conductance = current / 0.010
```

## GPIOs: Pins for digital inputs and outputs

GPIOS (General Purpose Input/Output) are digital pins for sending and receiving signals from external components.

### Example: Setting VSS and VDD

If your device has an external component, such as an LED or a multiplexer, you can configure the positive supply voltage (`VDD`) and the drain (`VSS`) with the following configuration:

```yaml
gpios:
  VSS:
    type: INPUT
    control_channel: 5
    v_lower_bound: 0
    v_upper_bound: 3.3

  VDD:
    type: INPUT
    control_channel: 6
    v_lower_bound: -3.3
    v_upper_bound: 0
```

## Connecting to Hardware

Instruments bridge your device configuration and actual hardware. The most common setup uses a QDAC-II for voltage control and current measurement.

### Example: Simple Setup

Use a single QDAC-II for everything:

```yaml
instruments:
  - name: qdac
    type: GENERAL  # Both control and measurement
    driver: qdac2
    ip_addr: 192.168.1.100
    slew_rate: 0.5  # V/s
    measurement_duration: 10e-3  # 10 ms
    sample_time: 100e-6  # 100 μs
```

### Example: Separate Control and Measurement

For more flexibility, split control and measurement:

```yaml
instruments:
  # QDAC for voltage control
  - name: qdac-control
    type: CONTROL
    driver: qdac2
    ip_addr: 192.168.1.100
    slew_rate: 0.5

  # QDAC for DC current measurement
  - name: qdac-measure
    type: MEASUREMENT
    driver: qdac2
    ip_addr: 192.168.1.100
    measurement_duration: 10e-3
    sample_time: 100e-6
    current_range: "LOW"  # For pA-nA currents
```

The device automatically routes operations to the appropriate instrument based on control/measure channels.

### Testing Without Hardware

Enable simulation mode for developing routines before hardware access:

```yaml
instruments:
  - name: qdac-sim
    type: GENERAL
    driver: qdac2
    ip_addr: 127.0.0.1
    is_simulation: true
    sim_file: "./sim_config.yaml"
```

## Pre-configuring Routine Parameters

The `routines` section lets you pre-configure parameters for common measurements:

```yaml
routines:
  - name: barrier_sweep
    parameters:
      gate: LEFT_BARRIER
      v_start: -1.5
      v_stop: 0.0
      n_points: 100
      contact: DRAIN

  - name: charge_stability
    parameters:
      gate_x: LEFT_PLUNGER
      gate_y: RIGHT_PLUNGER
      v_x_start: -2.0
      v_x_stop: -0.5
      v_y_start: -2.0
      v_y_stop: -0.5
      n_points_x: 100
      n_points_y: 100
      contact: DRAIN
```

Run routines without repeating parameters:

```python
from stanza.routines import RoutineRunner
from stanza.models import DeviceConfig

config = DeviceConfig.from_yaml("device.yaml")
runner = RoutineRunner(configs=[config])

# Parameters come from YAML
runner.run("barrier_sweep")

# Override parameters at runtime
runner.run("barrier_sweep", v_start=-2.0, n_points=200)
```

### Organizing Complex Workflows

Use nested routines to organize multi-step tune-ups:

```yaml
routines:
  - name: full_tuneup
    parameters:
      threshold: 1e-9
    routines:
      - name: leakage_test
        parameters:
          gates: [LEFT_BARRIER, CENTER_BARRIER, RIGHT_BARRIER]
          contact: DRAIN

      - name: pinchoff_all
        parameters:
          gates: [LEFT_BARRIER, CENTER_BARRIER, RIGHT_BARRIER]
          v_start: -1.5
          v_stop: 0.0
          n_points: 50

      - name: charge_stability
        parameters:
          gate_x: LEFT_PLUNGER
          gate_y: RIGHT_PLUNGER
```

Run the entire workflow:

```python
# Execute all nested routines
results = runner.run_all(parent_routine="full_tuneup")
```

## Complete Example

Here's a full configuration for a double quantum dot device:

```yaml
name: "Double Quantum Dot - Device 42"

gates:
  LP:
    type: PLUNGER
    control_channel: 1
    v_lower_bound: -3.0
    v_upper_bound: 0.0

  RP:
    type: PLUNGER
    control_channel: 2
    v_lower_bound: -3.0
    v_upper_bound: 0.0

  LB:
    type: BARRIER
    control_channel: 3
    v_lower_bound: -2.0
    v_upper_bound: 0.0

  CB:
    type: BARRIER
    control_channel: 4
    v_lower_bound: -2.0
    v_upper_bound: 0.0

  RB:
    type: BARRIER
    control_channel: 5
    v_lower_bound: -2.0
    v_upper_bound: 0.0

contacts:
  LEFT:
    type: SOURCE
    control_channel: 6
    measure_channel: 1
    v_lower_bound: -0.01
    v_upper_bound: 0.01

  RIGHT:
    type: DRAIN
    control_channel: 7
    measure_channel: 2
    v_lower_bound: -0.01
    v_upper_bound: 0.01

routines:
  - name: quick_pinchoff
    parameters:
      gate: LB
      v_start: -2.0
      v_stop: 0.0
      n_points: 100
      contact: RIGHT

  - name: charge_diagram
    parameters:
      gate_x: LP
      gate_y: RP
      v_x_start: -2.5
      v_x_stop: -0.5
      v_y_start: -2.5
      v_y_stop: -0.5
      n_points_x: 150
      n_points_y: 150
      contact: RIGHT

instruments:
  - name: qdac-control
    type: CONTROL
    driver: qdac2
    ip_addr: 192.168.1.100
    slew_rate: 0.5

  - name: qdac-measure
    type: MEASUREMENT
    driver: qdac2
    ip_addr: 192.168.1.100
    measurement_duration: 10e-3
    sample_time: 100e-6
    current_range: "LOW"
```

Use it in your code:

```python
from stanza.utils import device_from_yaml
from stanza.routines import RoutineRunner
from stanza.models import DeviceConfig

# Direct device usage
device = device_from_yaml("device.yaml")
device.jump({"LP": -1.5, "RP": -1.2})
current = device.measure("RIGHT")

# Or use with routines
config = DeviceConfig.from_yaml("device.yaml")
runner = RoutineRunner(configs=[config])
runner.run("quick_pinchoff")
runner.run("charge_diagram")
```

## Tips and Best Practices

**Use descriptive names**: Names like `LEFT_PLUNGER` and `RIGHT_BARRIER` are clearer than `G1` and `G2`.

**Start with conservative voltage bounds**: You can always widen them later. Better safe than sorry.

**Test with simulation first**: Use `is_simulation: true` to verify your configuration before connecting to hardware.

**One config per device**: Don't try to reuse configurations across different physical devices. Channel mappings will differ.

**Version control your configs**: Keep them in git alongside your routines. Document what changed and why.

**Comment your YAML**: Future you will thank you for explaining why certain parameters were chosen.

## Next Steps

* [Device Groups](/control/stanza/core-concepts/device-groups) - Learn how to organize pads for isolated testing
* [Routines](/control/stanza/core-concepts/routines/overview) - Learn how to write measurement routines using your configured device
* [Drivers](/control/stanza/core-concepts/drivers) - Understand how instruments communicate with hardware
* [Data Logging](/control/stanza/core-concepts/data-logging) - Automatically log data from your measurements