# How to Run Parameter Sweeps for Qubit Designs Using the SQuADDS AnsysSimulator

> Automate qubit design parameter sweeps with SQuADDS AnsysSimulator. Easily simulate designs, extract QSweep parameters, and aggregate results for efficient analysis.

- Repository: [Levenson-Falk Lab/squadds](https://github.com/lfl-lab/squadds)
- Tags: how-to-guide
- Published: 2026-03-06

---

**SQuADDS automates parameter sweeps by expanding nested geometry dictionaries into discrete design points via `extract_QSweep_parameters`, simulating each through `simulate_whole_device` or `simulate_single_design`, and aggregating eigenmode and LOM results into timestamped JSON files.**

SQuADDS (Superconducting Qubit And Device Design and Simulation) is an open-source Python framework maintained at `lfl-lab/squadds` that streamlines the design and validation of superconducting quantum circuits. When you need to explore how geometric variations affect qubit frequencies, anharmonicities, or cavity coupling rates, you can run parameter sweeps for qubit designs using the SQuADDS AnsysSimulator to automatically drive Ansys HFSS simulations across entire design spaces.

## Core Components of the Sweep Architecture

The sweep workflow is built around three interoperating layers that handle parameter expansion, electromagnetic simulation, and result persistence.

### Parameter Extraction with `extract_QSweep_parameters`

Located in [`squadds/simulations/sweeper_helperfunctions.py`](https://github.com/lfl-lab/squadds/blob/main/squadds/simulations/sweeper_helperfunctions.py), the `extract_QSweep_parameters` function traverses a nested `geometry_dict` and returns a list of fully populated design dictionaries—one for every combination of parameters you specify.

```python
from squadds.simulations.sweeper_helperfunctions import extract_QSweep_parameters

# A sweep definition with 3 qubit lengths × 2 cavity lengths = 6 total designs

sweep_opts = {
    "geometry_dict": {
        "design_options_qubit": {"cross_length": ["150um", "200um", "250um"]},
        "design_options_cavity_claw": {"total_length": ["3500um", "4000um"]}
    }
}
design_list = extract_QSweep_parameters(sweep_opts)

```

### Single-Design Simulation Engines

Two primary functions in [`squadds/simulations/objects.py`](https://github.com/lfl-lab/squadds/blob/main/squadds/simulations/objects.py) handle the actual physics:

- **`simulate_whole_device`** – Creates a coupled qubit-cavity geometry in Qiskit-Metal, renders it to Ansys HFSS, executes eigenmode and LOM analyses, and returns a structured results dictionary.
- **`simulate_single_design`** – Performs the same workflow for standalone components (e.g., a single resonator or coupler).

Both functions instantiate an `AnsysSimulator` internally, manage the Qiskit-Metal `DesignPlanar` object, and extract quality factors, frequencies, and capacitance matrices.

### Sweep Orchestration Methods

The high-level entry points `run_qubit_cavity_sweep` and `run_sweep` (also in [`squadds/simulations/objects.py`](https://github.com/lfl-lab/squadds/blob/main/squadds/simulations/objects.py)) iterate over the design list, invoke the appropriate simulation function for each entry, and serialize results to disk.

## Step-by-Step Sweep Workflow

The canonical data flow follows this pipeline:

1. **Define** a base device dictionary containing default geometric and simulation parameters.
2. **Expand** the sweep specification through `extract_QSweep_parameters` to generate `N` discrete design dictionaries.
3. **Simulate** each design via `simulate_whole_device` (for coupled systems) or `simulate_single_design` (for individual components).
4. **Aggregate** eigenmode data (cavity frequency, Q, κ) with LOM data (capacitances, cross-to-ground) using `get_sim_results`.
5. **Persist** each result to a timestamped JSON file via `save_simulation_data_to_json`.

## Practical Code Examples

### Sweeping a Coupled Qubit-Cavity System

Use `run_qubit_cavity_sweep` when you need to co-optimize a transmon qubit and its readout cavity simultaneously. The function automatically detects coupler families (CLT for capacitive, NCap for inductive) based on the presence of `finger_count` in the options dictionary.

```python
import json
from squadds.simulations.objects import run_qubit_cavity_sweep

# Base device geometry and simulation settings

base_device = {
    "design_options_qubit": {
        "cross_width": "30um",
        "cross_length": "200um",
        "cross_gap": "5um"
    },
    "design_options_cavity_claw": {
        "total_length": "4000um",
        "claw_opts": {"claw_width": "150um", "claw_gap": "10um"},
        "cplr_opts": {"finger_count": 4, "finger_length": "150um"}
    },
    "coupler_type": "CLT",
    "setup": {"max_passes": 30, "max_delta_f": 0.02}
}

# Sweep definition: 3 cross lengths × 2 cavity lengths = 6 simulations

sweep_dict = {
    "geometry_dict": {
        "design_options_qubit": {"cross_length": ["150um", "200um", "250um"]},
        "design_options_cavity_claw": {"total_length": ["3500um", "4000um"]}
    },
    "coupler_type": "CLT"
}

# Execute sweep (synchronous execution)

results = run_qubit_cavity_sweep(
    design=None,  # Function instantiates DesignPlanar internally

    device_options={**base_device, **sweep_dict},
    emode_setup=None,
    lom_setup=None,
    filename="qubit_cavity_sweep"
)

print(f"Completed {len(results)} simulations")
print(json.dumps(results[0], indent=2))

```

Under the hood, `run_qubit_cavity_sweep` calls `extract_QSweep_parameters` to expand the nested dictionaries, then iterates through the resulting list, calling `simulate_whole_device` for each configuration. Output files follow the naming convention `filename_%d%m%Y_%H.%M.%S.json`.

### Generic Component Sweeps with `run_sweep`

For standalone resonators or isolated couplers, use the lighter-weight `run_sweep` function:

```python
from squadds.simulations.objects import run_sweep

sweep_opts = {
    "geometry_dict": {
        "total_length": ["500um", "750um", "1000um"],
        "width": ["10um", "15um"]
    },
    "coupler_type": "CLT"
}

run_sweep(
    design=None,
    sweep_opts=sweep_opts,
    emode_options=None,
    lom_options=None,
    filename="resonator_sweep"
)

```

This function invokes `simulate_single_design` for each parameter combination and stores JSON results identical to the full-device workflow.

### Parallel Execution for Large Design Spaces

While the sweep helpers run synchronously by default, you can parallelize execution by wrapping `run_qubit_cavity_sweep` in a `ThreadPoolExecutor`. This is useful when your design space contains dozens or hundreds of points.

```python
from concurrent.futures import ThreadPoolExecutor
from squadds.simulations.objects import run_qubit_cavity_sweep

executor = ThreadPoolExecutor(max_workers=4)
futures = []

for variant in device_variants:  # List of pre-generated device dictionaries

    futures.append(
        executor.submit(
            run_qubit_cavity_sweep,
            design=None,
            device_options=variant,
            emode_setup=None,
            lom_setup=None,
            filename=f"sweep_variant_{variant['id']}"
        )
    )

# Wait for completion

results = [f.result() for f in futures]

```

**Note:** Parallel execution requires sufficient Ansys HFSS licenses and CPU cores. Each thread launches an independent HFSS process.

## Key Implementation Details

### Geometry and Coupler Handling

All geometric parameters—lengths, widths, and junction properties—reside inside `device_options` (specifically within `sweep_opts["geometry_dict"]`). The sweep helper automatically inspects `finger_count` to select between **CLT** (capacitive-loaded transmission line) and **NCap** (inductive NFC coupler) models, allowing a single sweep to explore both coupling families without manual branching logic.

### Result Aggregation and Storage

The `get_sim_results` utility (called internally by the simulation functions) merges:
- **Eigenmode data:** Cavity frequency, quality factor (Q), and external coupling rate (κ).
- **LOM data:** Capacitance matrices, cross-to-ground capacitances, and junction participation ratios.

From these, it computes derived quantities including qubit frequency, anharmonicity (α), and coupling strength (g). The `save_simulation_data_to_json` utility in [`squadds/simulations/utils.py`](https://github.com/lfl-lab/squadds/blob/main/squadds/simulations/utils.py) writes each point to a uniquely timestamped file, preventing overwrites and preserving simulation provenance.

## Summary

- **Parameter expansion** is handled by `extract_QSweep_parameters` in [`squadds/simulations/sweeper_helperfunctions.py`](https://github.com/lfl-lab/squadds/blob/main/squadds/simulations/sweeper_helperfunctions.py), which converts nested sweep dictionaries into flat lists of design configurations.
- **Simulation execution** uses `simulate_whole_device` (coupled systems) or `simulate_single_design` (single components) from [`squadds/simulations/objects.py`](https://github.com/lfl-lab/squadds/blob/main/squadds/simulations/objects.py), both leveraging the `AnsysSimulator` class to drive HFSS.
- **Orchestration** is provided by `run_qubit_cavity_sweep` and `run_sweep`, which automate the iteration and result collection workflow.
- **Output** consists of timestamped JSON files containing eigenmode and LOM data suitable for machine learning pipelines or database ingestion.

## Frequently Asked Questions

### What is the difference between `run_qubit_cavity_sweep` and `run_sweep`?

`run_qubit_cavity_sweep` is specialized for coupled qubit-cavity systems and expects a `device_options` dictionary containing both qubit and cavity geometry keys. It internally calls `simulate_whole_device` to ensure both components are co-simulated. `run_sweep` is a generic wrapper that accepts any component definition and calls `simulate_single_design`, making it suitable for standalone resonators, couplers, or test structures.

### How does SQuADDS handle different coupler types during a sweep?

The framework inspects the `finger_count` key inside the coupler options dictionary. If present, it routes the design to the CLT (capacitive) model; otherwise, it defaults to NCap (inductive). This detection happens inside `extract_QSweep_parameters`, allowing a single sweep definition to mix or compare coupler topologies automatically.

### Can I run parameter sweeps in parallel on multiple CPU cores?

Yes. While the built-in `run_qubit_cavity_sweep` and `run_sweep` functions execute serially for simplicity, you can parallelize them by submitting each sweep call to a `concurrent.futures.ThreadPoolExecutor`. Each thread requires an independent Ansys HFSS license and sufficient RAM to run eigenmode simulations concurrently.

### Where are the simulation results stored and in what format?

Results are stored as individual JSON files in the working directory. The filename format is `filename_%d%m%Y_%H.%M.%S.json` (day-month-year_hour.minute.second). Each file contains a dictionary with eigenmode frequencies, quality factors, LOM capacitances, and derived quantum metrics (g, α, f_qubit), making them immediately compatible with pandas DataFrames or ML training loops.