diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md new file mode 100644 index 000000000..a59ed2bf6 --- /dev/null +++ b/docs/ONBOARDING.md @@ -0,0 +1,520 @@ +# Zero-to-Hero Onboarding Guide + +Welcome to the qubit calibration codebase! This guide will take you from zero knowledge to becoming a productive contributor. Whether you're a physicist new to quantum control systems or an experienced developer new to this codebase, this guide is designed for you. + +## Table of Contents + +1. [Welcome and Overview](#welcome-and-overview) +2. [Prerequisites](#prerequisites) +3. [Quick Start](#quick-start) +4. [Understanding the Codebase](#understanding-the-codebase) +5. [Your First Contribution](#your-first-contribution) +6. [Deep Dive: Calibration Procedures](#deep-dive-calibration-procedures) +7. [Advanced Topics](#advanced-topics) +8. [Troubleshooting and FAQ](#troubleshooting-and-faq) +9. [Next Steps](#next-steps) + +## Welcome and Overview + +### What is This Codebase? + +This repository contains calibration workflows and utilities for superconducting quantum computing systems using the QM (Quantum Machines) platform. It provides: + +- **Calibration nodes**: Individual calibration procedures (spectroscopy, gates, characterization) +- **Calibration graphs**: Orchestrated sequences of calibrations +- **Utilities**: Reusable analysis and plotting functions +- **Configuration management**: QUA-M state management + +### Who is This Guide For? + +This guide is designed for: + +- **Physicists** with quantum mechanics background who want to contribute to calibration code +- **Developers** familiar with Python who want to understand quantum control systems +- **Researchers** who need to run, modify, or create calibration procedures + +### Learning Path Overview + +This guide follows a progressive learning path: + +1. **Setup** (30 min): Get your environment running +2. **Understanding** (1-2 hours): Learn the codebase structure +3. **First Contribution** (2-3 hours): Make your first code change +4. **Deep Dive** (3-4 hours): Master calibration procedures +5. **Advanced Topics** (2-3 hours): Create new calibrations + +Each section builds on the previous one, so we recommend following the order. + +## Prerequisites + +### Required Knowledge + +**Quantum Mechanics**: +- Basic understanding of qubits, quantum states, and gates +- Familiarity with superconducting qubits (helpful but not required) +- Understanding of quantum measurements and readout + +**Programming**: +- Python 3.10+ proficiency (intermediate level) +- Familiarity with NumPy, SciPy, and data analysis +- Basic understanding of object-oriented programming + +**Tools**: +- Git for version control +- Command line/terminal usage +- Basic Docker knowledge (for DevContainer option) + +### Required Tools + +- **Python 3.10, 3.11, or 3.12** +- **Git** +- **Docker Desktop** (optional, for DevContainer) +- **VS Code** (recommended) +- **Access to QM hardware or simulator** (for running calibrations) + +### Optional but Helpful + +- Experience with quantum control systems +- Familiarity with xarray for labeled arrays +- Knowledge of QUA language (we'll cover this) +- Understanding of calibration theory + +## Quick Start + +**Time: 30 minutes** + +Let's get you up and running with your first calibration! + +### Step 1: Set Up Environment + +Follow the [Development Environment Setup Guide](onboarding/dev-environment.md) to: + +1. Install Python and dependencies +2. Clone the repository +3. Set up your development environment (Docker or local) + +**Quick command summary**: +```bash +cd qualibration_graphs/superconducting +uv sync --group dev --prerelease=allow +source .venv/bin/activate # or .venv\Scripts\activate on Windows +``` + +### Step 2: Run Your First Calibration + +Let's start with the simplest calibration - Hello QUA: + +```bash +python calibrations/1Q_calibrations/00_hello_qua.py +``` + +This will: +- Connect to the QOP (Quantum Operations Processor) +- Run a simple QUA program +- Test your connection + +**Expected output**: Connection successful, program executed. + +### Step 3: Understand the Output + +After running, you should see: +- Execution report +- Progress indicators +- Results saved to disk + +Check the generated files in the results directory to see what was created. + +### Step 4: Explore the Code + +Open `calibrations/1Q_calibrations/00_hello_qua.py` and: +1. Read through the code +2. Notice the structure: imports, node initialization, QUA program, execution +3. Try modifying parameters (in the `custom_param` function) + +**Congratulations!** You've run your first calibration. Now let's understand how it works. + +## Understanding the Codebase + +**Time: 1-2 hours** + +### Repository Structure + +Start by reading the [Repository Map](onboarding/repository-map.md) to understand: +- Directory organization +- Key entry points +- Module dependencies +- File purposes + +**Key directories**: +- `calibrations/`: Individual calibration nodes +- `calibration_utils/`: Reusable utilities +- `quam_config/`: Configuration management + +### Key Concepts + +#### QualibrationNode + +The core abstraction is the `QualibrationNode`. Each calibration is a node that: +- Takes parameters as input +- Executes a QUA program +- Analyzes data +- Updates system state +- Saves results + +#### QUA Language + +QUA (Quantum Machines Assembly) is a domain-specific language for quantum control. It's embedded in Python and allows you to: +- Define pulse sequences +- Control timing +- Perform measurements +- Process data streams + +See the [DSL and API Reference](onboarding/dsl-api-reference.md) for details. + +#### Calibration Workflow + +Calibrations follow a specific order: +1. Hardware setup (time of flight, mixer calibration) +2. Resonator characterization +3. Qubit characterization +4. Gate calibration +5. Characterization measurements + +See the [Calibration Workflow](onboarding/calibration-workflow-detailed.md) for the complete 34-step process. + +### Code Patterns + +All calibration nodes follow consistent patterns. Read [Code Patterns](onboarding/code-patterns.md) to learn: +- Standard node structure +- QUA program patterns +- Data processing patterns +- State management patterns + +### Data Flow + +Understanding how data flows is crucial: + +``` +Quam State → Node Parameters → QUA Program → Raw Data → +Analysis → Fit Results → State Update → Saved Results +``` + +Each calibration: +1. Reads from the Quam state +2. Executes the experiment +3. Analyzes results +4. Updates the state +5. Saves everything + +## Your First Contribution + +**Time: 2-3 hours** + +### Setting Up Development Environment + +If you haven't already, complete the [Development Environment Setup](onboarding/dev-environment.md), including: +- Pre-commit hooks +- IDE configuration +- Testing setup + +### Understanding the Code Structure + +Before making changes, understand: +1. **Node structure**: Review [Code Patterns](onboarding/code-patterns.md) +2. **Parameter management**: How parameters are defined and used +3. **State updates**: How calibrations modify system state + +### Making a Small Change + +Let's make a simple modification to the Hello QUA calibration: + +1. **Open the file**: + ```bash + code calibrations/1Q_calibrations/00_hello_qua.py + ``` + +2. **Modify the amplitude sweep**: + Find the line: + ```python + amps = np.linspace(-1, 1, 11) + ``` + Change it to: + ```python + amps = np.linspace(-0.5, 0.5, 21) # Smaller range, more points + ``` + +3. **Test your change**: + ```bash + python calibrations/1Q_calibrations/00_hello_qua.py + ``` + +4. **Verify results**: Check that the output reflects your changes + +### Running Tests + +```bash +# Run all tests +pytest + +# Run specific test +pytest tests/test_imports.py + +# Run with coverage +pytest --cov +``` + +### Code Quality Checks + +```bash +# Format code +black . + +# Check linting +pylint calibrations/1Q_calibrations/00_hello_qua.py + +# Run pre-commit hooks +pre-commit run --all-files +``` + +### Submitting a PR + +1. **Create a branch**: + ```bash + git checkout -b feature/my-first-change + ``` + +2. **Commit your changes**: + ```bash + git add . + git commit -m "feat: modify hello qua amplitude range" + ``` + (Pre-commit hooks will run automatically) + +3. **Push and create PR**: + ```bash + git push origin feature/my-first-change + ``` + Then create a pull request on GitHub. + +**Congratulations!** You've made your first contribution. Now let's dive deeper. + +## Deep Dive: Calibration Procedures + +**Time: 3-4 hours** + +### Understanding Calibration Physics + +Each calibration has a physical purpose. Read the [Calibration Procedures Catalog](onboarding/calibration-procedures.md) to understand: +- Physics behind each calibration +- What parameters are being measured +- How results are used + +### Single-Qubit Calibrations + +Start with single-qubit calibrations: + +1. **Time of Flight** (`01a_time_of_flight.py`): + - Measures signal propagation delay + - Calibrates readout timing + - First calibration in the workflow + +2. **Resonator Spectroscopy** (`02a_resonator_spectroscopy.py`): + - Finds resonator frequency + - Required for readout + +3. **Qubit Spectroscopy** (`03a_qubit_spectroscopy.py`): + - Finds qubit transition frequency + - Required for gates + +4. **Power Rabi** (`04b_power_rabi.py`): + - Calibrates π-pulse amplitude + - Enables X180 gate + +5. **Ramsey** (`06a_ramsey.py`): + - Fine-tunes qubit frequency + - Measures T2* + +### Two-Qubit Calibrations + +For two-qubit gates: + +1. **CZ Gate - Chevron** (`19_chevron_11_02.py`): + - Finds initial CZ parameters + - Uses |11⟩ ↔ |02⟩ avoided crossing + +2. **CZ Gate - Conditional Phase** (`20_cz_conditional_phase.py`): + - Fine-tunes CZ amplitude + - Achieves π conditional phase + +3. **CZ Gate - Phase Compensation** (`21_cz_phase_compensation.py`): + - Compensates single-qubit phases + - Virtual Z rotations + +### Calibration Graphs + +Calibration graphs orchestrate multiple nodes: + +- **Fixed-Frequency Transmon Bringup** (`90_calibration_graph_bringup_fixed_frequency_transmon.py`): + - Complete single-qubit calibration workflow + - Runs all necessary calibrations in order + +- **CZ Gate Calibration Graph** (`99_CZ_calibration_graph.py`): + - Complete two-qubit gate calibration + - Includes all CZ calibration steps + +### Hands-On Practice + +1. **Run a complete calibration graph**: + ```bash + python calibrations/1Q_calibrations/90_calibration_graph_bringup_fixed_frequency_transmon.py + ``` + +2. **Modify parameters**: Try changing sweep ranges or number of shots + +3. **Analyze results**: Examine the generated plots and data files + +4. **Debug issues**: Practice troubleshooting common problems + +## Advanced Topics + +**Time: 2-3 hours** + +### Creating New Calibration Procedures + +To create a new calibration: + +1. **Copy a similar calibration** as a template +2. **Modify the QUA program** for your experiment +3. **Create analysis functions** in `calibration_utils/` +4. **Update parameters** class +5. **Add plotting functions** +6. **Test thoroughly** + +See [Code Patterns](onboarding/code-patterns.md) for the standard structure. + +### Extending Calibration Utilities + +When creating reusable utilities: + +1. **Follow the pattern**: `parameters.py`, `analysis.py`, `plotting.py` +2. **Use type hints**: For better IDE support +3. **Document functions**: Include docstrings +4. **Add tests**: Ensure reliability + +### Telemetry and Observability + +The codebase includes a telemetry module (not currently used in calibrations). To add observability: + +```python +from telemetry import trace_function, trace_span + +@trace_function("my_calibration", attributes={"qubit": "q1"}) +def my_calibration_function(): + with trace_span("data_analysis"): + # Analysis code + pass +``` + +See [Telemetry README](../../telemetry/README.md) for details. + +### Performance Optimization + +Tips for optimizing calibrations: + +1. **Batch operations**: Use `qubits.batch()` for multiplexed readout +2. **Minimize data transfer**: Buffer and average in QUA +3. **Parallel execution**: Run independent calibrations in parallel +4. **Vectorized analysis**: Use NumPy/xarray operations + +## Troubleshooting and FAQ + +### Common Issues + +**Q: Calibration fails with "Connection refused"** + +A: Check that: +- QOP is running and accessible +- Network connectivity is working +- Credentials in Quam state are correct + +**Q: Fit results show "success: False"** + +A: Common causes: +- Poor signal-to-noise ratio (increase shots) +- Wrong parameter ranges (adjust sweep ranges) +- Hardware issues (check connections) + +**Q: Pre-commit hooks fail** + +A: Run: +```bash +pre-commit run --all-files +``` +This will auto-fix most issues (formatting, etc.). + +**Q: Import errors** + +A: Ensure: +- Virtual environment is activated +- Dependencies are installed: `uv sync --group dev` +- You're in the correct directory + +### Debugging Tips + +1. **Use simulation mode**: Test with `simulate=True` first +2. **Load previous data**: Use `load_data_id` to re-analyze without re-running +3. **Check execution report**: Look for warnings or errors +4. **Examine raw data**: Plot raw data before analysis +5. **Reduce complexity**: Use fewer qubits or shots for testing + +### Getting Help + +1. **Check documentation**: Review relevant guides +2. **Search issues**: Check GitHub issues for similar problems +3. **Ask in channel**: Development channel or tag maintainers +4. **Review code**: Look at similar calibrations for examples + +## Next Steps + +### Recommended Reading + +1. **QUA Documentation**: [QM Documentation](https://docs.quantum-machines.co/) +2. **Quantum Control Theory**: Nielsen & Chuang, "Quantum Computation and Quantum Information" +3. **Superconducting Qubits**: Review papers on transmon qubits + +### Advanced Resources + +- **QM Academy**: Training courses on QUA programming +- **Research Papers**: Latest calibration techniques +- **Community Forums**: QM user community + +### Practice Exercises + +1. **Modify existing calibration**: Change parameters, add features +2. **Create simple calibration**: Start with a basic spectroscopy +3. **Optimize a calibration**: Improve speed or accuracy +4. **Debug a failing calibration**: Practice troubleshooting + +### Contributing + +Ready to contribute? See the [Contributing Guide](../../qualibration_graphs/superconducting/CONTRIBUTING.md) for: +- Code style guidelines +- Testing requirements +- PR process +- Review expectations + +## Summary + +You've completed the Zero-to-Hero onboarding! You now know: + +- ✅ How to set up your development environment +- ✅ The structure and organization of the codebase +- ✅ How calibration nodes work +- ✅ The QUA language basics +- ✅ How to make contributions +- ✅ The physics behind calibrations +- ✅ How to troubleshoot issues + +**Keep learning**: The field of quantum control is rapidly evolving. Stay updated with the latest techniques and best practices. + +**Happy calibrating!** 🚀 diff --git a/docs/onboarding/calibration-procedures.md b/docs/onboarding/calibration-procedures.md new file mode 100644 index 000000000..12e7afe87 --- /dev/null +++ b/docs/onboarding/calibration-procedures.md @@ -0,0 +1,373 @@ +# Calibration Procedures Catalog + +This document provides detailed information about each calibration procedure, including physics background, implementation details, and troubleshooting guides. + +## Calibration Categories + +Calibrations are organized into three main categories: + +1. **Initial Setup**: Hardware characterization and basic calibrations +2. **Single-Qubit Calibrations**: Qubit-specific parameters and gates +3. **Two-Qubit Calibrations**: Entangling gate calibration + +## Initial Setup Calibrations + +### Time of Flight + +**File**: `calibrations/1Q_calibrations/01a_time_of_flight.py` + +**Physics**: The time of flight is the delay between sending a readout pulse and when the signal arrives at the ADC. This includes: +- Internal processing time in the OPX +- Propagation delay through cables and components +- Signal processing delays + +**Purpose**: Calibrate the acquisition window offset to properly time the readout measurement. + +**Inputs**: +- `time_of_flight_in_ns`: Initial guess (typically 28 ns) +- `readout_amplitude_in_v`: Readout pulse amplitude +- `readout_length_in_ns`: Readout pulse duration + +**Outputs**: +- `qubit.resonator.time_of_flight`: Calibrated time of flight +- `qubit.resonator.opx_input_I.offset`: I channel DC offset +- `qubit.resonator.opx_input_Q.offset`: Q channel DC offset + +**Troubleshooting**: +- **No signal**: Check readout line connection, increase amplitude +- **Signal too weak**: Increase readout amplitude or input gain +- **Signal saturates**: Decrease readout amplitude or adjust input gain +- **Inconsistent results**: Check for loose connections, increase averaging + +--- + +### Mixer Calibration + +**File**: `calibrations/1Q_calibrations/01a_mixer_calibration.py` + +**Physics**: IQ mixers can have imperfections (amplitude imbalance, phase error, DC offsets) that cause leakage and distortion. Calibration compensates for these. + +**Purpose**: Calibrate IQ mixer to minimize LO leakage and image rejection. + +**Inputs**: +- `calibrate_drive`: Whether to calibrate drive mixer +- `calibrate_resonator`: Whether to calibrate readout mixer + +**Outputs**: Mixer calibration parameters stored in qubit/resonator configuration + +**Troubleshooting**: +- **High leakage**: Check mixer connections, verify LO power +- **Poor image rejection**: Re-run calibration with more iterations + +--- + +### Resonator Spectroscopy + +**File**: `calibrations/1Q_calibrations/02a_resonator_spectroscopy.py` + +**Physics**: The readout resonator has a characteristic resonance frequency determined by its geometry and coupling. Spectroscopy measures the transmission/reflection to find this frequency. + +**Purpose**: Find the resonator frequency for optimal readout. + +**Inputs**: +- `intermediate_frequency_range`: Frequency sweep range + +**Outputs**: +- `qubit.resonator.frequency`: Resonator frequency +- `qubit.resonator.intermediate_frequency`: Intermediate frequency + +**Troubleshooting**: +- **No resonance found**: Widen frequency range, check resonator connection +- **Multiple peaks**: Identify correct peak based on expected frequency from design +- **Broad resonance**: May indicate poor quality factor or coupling issues + +--- + +## Single-Qubit Calibrations + +### Qubit Spectroscopy + +**File**: `calibrations/1Q_calibrations/03a_qubit_spectroscopy.py` + +**Physics**: The qubit transition frequency |0⟩ ↔ |1⟩ is determined by the Josephson energy and charging energy. Spectroscopy measures the qubit response to find this frequency. + +**Purpose**: Find the qubit transition frequency for gate operations. + +**Inputs**: +- `intermediate_frequency_range`: Frequency sweep range +- `drive_scaling_amp`: Drive amplitude + +**Outputs**: +- `qubit.xy.frequency`: Qubit frequency +- `qubit.xy.intermediate_frequency`: Intermediate frequency + +**Troubleshooting**: +- **No resonance**: Widen frequency range, check drive line connection +- **Multiple peaks**: Identify |0⟩↔|1⟩ transition (usually strongest, at expected frequency) +- **Broad linewidth**: May indicate short T2* or strong noise + +--- + +### Power Rabi + +**File**: `calibrations/1Q_calibrations/04b_power_rabi.py` + +**Physics**: Rabi oscillations occur when driving a qubit at its resonance frequency. The population oscillates between |0⟩ and |1⟩ with frequency proportional to drive amplitude. A π-pulse corresponds to a full oscillation (180° rotation). + +**Purpose**: Calibrate the π-pulse amplitude for X180 gate. + +**Inputs**: +- `amp_scaling_range`: Amplitude sweep range +- `operation`: Which gate to calibrate ("x180" or "x90") + +**Outputs**: +- `qubit.xy.operations["x180"].amplitude`: π-pulse amplitude +- `qubit.xy.operations["x90"].amplitude`: π/2-pulse amplitude (if calibrated) + +**Troubleshooting**: +- **No oscillations**: Increase amplitude range, check drive line, verify qubit frequency +- **Multiple π-pulses**: Use first π-pulse amplitude +- **Decaying oscillations**: May indicate T1/T2 issues, reduce measurement delay + +--- + +### Ramsey + +**File**: `calibrations/1Q_calibrations/06a_ramsey.py` + +**Physics**: Ramsey interferometry measures qubit frequency by preparing a superposition state and letting it evolve. The phase evolution causes oscillations at the detuning frequency. The decay envelope gives T2*. + +**Purpose**: Fine-tune qubit frequency and measure T2* dephasing time. + +**Inputs**: +- `evolution_times`: Free evolution time array +- `detuning_freqs`: Optional detuning frequencies + +**Outputs**: +- `qubit.xy.frequency`: Fine-tuned qubit frequency +- `qubit.T2*`: Dephasing time (if measured) + +**Troubleshooting**: +- **No oscillations**: Check detuning, increase evolution time range +- **Fast decay**: Qubit may have short T2*, check for noise sources +- **Frequency drift**: May need to re-run if qubit frequency has drifted + +--- + +### T1 Measurement + +**File**: `calibrations/1Q_calibrations/05_T1.py` + +**Physics**: T1 is the energy relaxation time, measuring how long the qubit takes to decay from |1⟩ to |0⟩. This is an exponential decay process. + +**Purpose**: Measure qubit energy relaxation time. + +**Inputs**: +- `wait_times`: Wait time array after exciting qubit + +**Outputs**: +- `qubit.T1`: T1 relaxation time + +**Troubleshooting**: +- **T1 too short**: Check qubit environment, connections, may indicate heating or noise +- **Poor fit**: Increase wait time range, use more shots +- **Non-exponential decay**: May indicate measurement issues or state preparation problems + +--- + +### DRAG Calibration + +**File**: `calibrations/1Q_calibrations/10b_drag_calibration_180_minus_180.py` + +**Physics**: DRAG (Derivative Reduction by Adiabatic Gate) reduces phase errors and leakage to higher levels by adding a derivative component to the pulse envelope. + +**Purpose**: Calibrate DRAG coefficient to minimize phase errors. + +**Inputs**: +- `drag_amp_values`: DRAG coefficient sweep range +- `num_of_repetitions`: Number of repetitions + +**Outputs**: +- `qubit.xy.operations["x180"].drag_coefficient`: Optimal DRAG coefficient + +**Troubleshooting**: +- **No clear optimum**: Use default DRAG coefficient, may need to adjust pulse length +- **Phase error persists**: Check pulse length, increase DRAG range, verify qubit frequency + +--- + +### IQ Blobs + +**File**: `calibrations/1Q_calibrations/07_iq_blobs.py` + +**Physics**: When measuring a qubit in |0⟩ or |1⟩, the readout signal appears as a distribution (blob) in IQ space. Optimal state discrimination requires separating these blobs. + +**Purpose**: Measure IQ distributions and calibrate state discrimination. + +**Inputs**: +- `num_shots`: Number of measurements + +**Outputs**: +- `qubit.resonator.IQ_rotation_angle`: Rotation angle for optimal discrimination +- `qubit.resonator.ge_discrimination_threshold`: State discrimination threshold + +**Troubleshooting**: +- **Overlapping blobs**: Improve readout optimization, increase measurement time +- **Blobs not separated**: Check readout frequency and power, may need better readout calibration + +--- + +### Randomized Benchmarking + +**File**: `calibrations/1Q_calibrations/11a_single_qubit_randomized_benchmarking.py` + +**Physics**: Randomized benchmarking measures average gate fidelity by applying random sequences of Clifford gates and measuring the final state. The fidelity decays exponentially with sequence length. + +**Purpose**: Measure single-qubit gate fidelity. + +**Inputs**: +- `circuit_depth`: Maximum sequence length +- `num_random_sequence`: Number of random sequences + +**Outputs**: +- `qubit.gate_fidelity["averaged"]`: Average gate fidelity +- `SQ_error_per_gate`: Error per gate + +**Troubleshooting**: +- **Fidelity too low**: Check previous calibrations, especially DRAG and frequency +- **No decay visible**: Increase circuit depth, check readout calibration +- **Long execution time**: Reduce circuit depth or number of sequences + +--- + +## Two-Qubit Calibrations + +### CZ Gate - Chevron + +**File**: `calibrations/CZ_calibration_fixed_couplers/19_chevron_11_02.py` + +**Physics**: The CZ gate uses the |11⟩ ↔ |02⟩ avoided crossing. By pulsing the qubit frequency to this crossing, a conditional phase accumulates. The Chevron pattern shows the oscillation between these states. + +**Purpose**: Find initial CZ gate parameters (amplitude and duration). + +**Inputs**: +- `flux_amplitude`: Flux amplitude range +- `gate_time`: Gate duration range + +**Outputs**: +- `qubit.flux_amp`: Initial qubit flux amplitude +- `coupler.flux_amp`: Initial coupler flux amplitude + +**Troubleshooting**: +- **No Chevron pattern**: Check flux line connections, widen parameter ranges +- **Unclear pattern**: Increase resolution, check qubit frequencies + +--- + +### CZ Gate - Conditional Phase + +**File**: `calibrations/CZ_calibration_fixed_couplers/20_cz_conditional_phase.py` + +**Physics**: The conditional phase is the phase acquired by the target qubit when the control qubit is in |1⟩. For a perfect CZ, this should be π. + +**Purpose**: Fine-tune CZ gate amplitude to achieve π conditional phase. + +**Inputs**: +- `qubit_flux_amplitude`: Flux amplitude range + +**Outputs**: +- `qubit.flux_amp`: Refined qubit flux amplitude + +**Troubleshooting**: +- **Phase not π**: Adjust amplitude range, check previous calibrations +- **Poor fit**: Increase measurement statistics, check state preparation + +--- + +### CZ Gate - Phase Compensation + +**File**: `calibrations/CZ_calibration_fixed_couplers/21_cz_phase_compensation.py` + +**Physics**: During the CZ pulse, both qubits acquire additional phases due to frequency shifts. These must be compensated with virtual Z rotations. + +**Purpose**: Measure and compensate for single-qubit phases during CZ. + +**Inputs**: +- `frame_rotation`: Frame rotation range + +**Outputs**: +- `qubit.phase_cor_qij`: Phase correction for qubit i when interacting with qubit j + +**Troubleshooting**: +- **Large phase corrections**: May indicate issues with CZ calibration +- **Unstable phases**: Check qubit frequencies and flux stability + +--- + +### Two-Qubit Randomized Benchmarking + +**File**: `calibrations/CZ_calibration_fixed_couplers/22_two_qubit_standard_rb.py` + +**Physics**: Similar to single-qubit RB, but uses two-qubit Clifford gates including CZ. Measures the fidelity of the two-qubit gate set. + +**Purpose**: Measure two-qubit gate fidelity. + +**Inputs**: +- `circuit_depth`: Maximum sequence length + +**Outputs**: +- `2q_fid`: Two-qubit gate fidelity +- `CZ_error_per_gate`: CZ gate error rate + +**Troubleshooting**: +- **Fidelity too low**: Re-calibrate CZ gate parameters, check single-qubit gates +- **Long execution time**: Reduce circuit depth or number of sequences + +--- + +## Parameter Ranges + +### Typical Values + +| Parameter | Typical Range | Units | +|-----------|---------------|-------| +| Qubit frequency | 3-7 | GHz | +| Resonator frequency | 5-8 | GHz | +| Readout amplitude | 0.01-0.1 | V | +| Drive amplitude (π-pulse) | 0.1-0.5 | (normalized) | +| Time of flight | 20-50 | ns | +| T1 | 10-100 | μs | +| T2* | 1-50 | μs | +| Gate fidelity | >99% | - | + +### Sweep Ranges + +- **Frequency sweeps**: ±50 MHz around expected value +- **Amplitude sweeps**: 0.5× to 1.5× expected value +- **Time sweeps**: 0 to 5×T1 or T2* + +## Common Failure Modes + +### Hardware Issues + +- **No signal**: Check connections, verify hardware power +- **Inconsistent results**: Check for loose connections, temperature stability +- **Saturation**: Reduce signal amplitude or adjust gain + +### Calibration Issues + +- **Poor fits**: Increase statistics, check parameter ranges +- **Unstable parameters**: Check for drift, may need to re-run calibrations +- **Dependencies not met**: Ensure prerequisite calibrations completed successfully + +### Physics Issues + +- **Short coherence times**: Check environment, may indicate noise sources +- **Frequency drift**: May need frequent re-calibration +- **Leakage**: Check pulse shapes, may need better DRAG calibration + +## Next Steps + +- Review [Calibration Workflow](calibration-workflow-detailed.md) for execution order +- Study [Code Patterns](code-patterns.md) for implementation details +- Explore [Example Walkthrough](example-walkthrough.md) for hands-on learning diff --git a/docs/onboarding/calibration-workflow-detailed.md b/docs/onboarding/calibration-workflow-detailed.md new file mode 100644 index 000000000..e59a863e9 --- /dev/null +++ b/docs/onboarding/calibration-workflow-detailed.md @@ -0,0 +1,824 @@ +# Calibration Workflow - Detailed Reference + +This document provides a detailed breakdown of the 34-step calibration workflow, mapping each step to its corresponding code file, documenting inputs/outputs, dependencies, and common issues. + +## Overview + +The calibration workflow is a sequential process that takes a quantum processor from initial hardware setup to fully calibrated gates. The workflow is divided into: + +1. **Initial Setup** (Steps 1-7): Hardware characterization and basic calibrations +2. **Single-Qubit Calibrations** (Steps 8-25): Qubit-specific parameters +3. **Two-Qubit Calibrations** (Steps 26-34): Entangling gate calibration + +## Workflow Steps with Code References + +### Step 1: Time of Flight + +**Code File**: `calibrations/1Q_calibrations/01a_time_of_flight.py` + +**Purpose**: Calibrate the time delay between sending a readout pulse and when the signal arrives at the ADC. Also calibrates analog input offsets and gains. + +**Inputs**: +- `time_of_flight_in_ns`: Initial guess (default: 28 ns) +- `readout_amplitude_in_v`: Readout pulse amplitude (default: 0.03 V) +- `readout_length_in_ns`: Readout pulse length (default: 1000 ns) +- `num_shots`: Number of averages (default: 100) + +**Outputs**: +- `qubit.resonator.time_of_flight`: Calibrated time of flight (ns) +- `qubit.resonator.opx_input_I.offset`: I channel offset (V) +- `qubit.resonator.opx_input_Q.offset`: Q channel offset (V) +- `input_gain_db`: Optimal input gain (if applicable) + +**Dependencies**: None (first calibration) + +**Typical Execution Time**: 1-2 minutes per readout line + +**Common Issues**: +- Signal too weak: Increase readout amplitude +- Signal saturates: Decrease readout amplitude or adjust input gain +- No signal detected: Check wiring and resonator connection + +--- + +### Step 2: Resonator Spectroscopy (Wide Scan) + +**Code File**: `calibrations/1Q_calibrations/02a_resonator_spectroscopy.py` + +**Purpose**: Find the resonator frequency over a wide frequency range. + +**Inputs**: +- `intermediate_frequency_range`: Frequency sweep range (MHz) +- `num_shots`: Number of averages + +**Outputs**: +- `qubit.resonator.frequency`: Resonator frequency (Hz) +- `qubit.resonator.intermediate_frequency`: Intermediate frequency (Hz) + +**Dependencies**: Step 1 (Time of Flight) + +**Typical Execution Time**: 2-5 minutes per readout line + +**Common Issues**: +- No resonance found: Widen frequency range or check resonator connection +- Multiple peaks: Identify correct peak based on expected frequency + +--- + +### Step 3: Resonator Spectroscopy (Individual Scan) + +**Code File**: `calibrations/1Q_calibrations/02a_resonator_spectroscopy.py` (same as Step 2, with refined parameters) + +**Purpose**: Refine resonator frequency measurement with higher resolution. + +**Inputs**: +- `intermediate_frequency_delta`: Fine frequency step (MHz) +- `readout_amplitude`: Readout power + +**Outputs**: +- `qubit.resonator.frequency`: Refined resonator frequency (Hz) + +**Dependencies**: Step 2 + +**Typical Execution Time**: 1-2 minutes per resonator + +--- + +### Step 4: TWPA Calibration + +**Code File**: Not yet implemented in current codebase + +**Purpose**: Calibrate Traveling Wave Parametric Amplifier (TWPA) pump frequency and amplitude for optimal readout amplification. + +**Inputs**: +- `intermediate_frequency_range`: Frequency range to optimize +- `amp_scaling_range`: Amplitude scaling range + +**Outputs**: +- `optimal_pump_frequency`: Optimal TWPA pump frequency +- `optimal_pump_amplitude`: Optimal TWPA pump amplitude + +**Dependencies**: Step 3 + +**Typical Execution Time**: 5-10 minutes + +--- + +### Step 5: Resonator Spectroscopy vs Power + +**Code File**: `calibrations/1Q_calibrations/02b_resonator_spectroscopy_vs_power.py` + +**Purpose**: Measure resonator frequency as a function of readout power to find optimal readout amplitude. + +**Inputs**: +- `intermediate_frequency_delta`: Frequency step (MHz) +- `amp_scaling_range`: Power sweep range + +**Outputs**: +- `qubit.resonator.frequency`: Power-dependent frequency +- `qubit.resonator.operations["readout"].amplitude`: Optimal readout amplitude + +**Dependencies**: Step 3 + +**Typical Execution Time**: 3-5 minutes per resonator + +**Common Issues**: +- Frequency shift too large: Reduce readout power range +- No clear optimum: Use default amplitude or manual selection + +--- + +### Step 6: Resonator Spectroscopy vs Qubit Flux + +**Code File**: `calibrations/1Q_calibrations/02c_resonator_spectroscopy_vs_flux.py` + +**Purpose**: For flux-tunable qubits, find the optimal flux bias point by measuring resonator frequency vs qubit flux. + +**Inputs**: +- `intermediate_frequency_delta`: Frequency step (MHz) +- `z_DC_offset_range`: Flux bias range (V) + +**Outputs**: +- `joint_UPSS_voltages_set`: UPSS voltage settings +- `joint_LSS_voltages_set`: LSS voltage settings +- `qubit.z.flux_point`: Optimal flux point + +**Dependencies**: Step 5 + +**Typical Execution Time**: 5-10 minutes per qubit + +**Common Issues**: +- No clear optimum: Widen flux range or check flux line connection +- Hysteresis: Use consistent flux sweep direction + +--- + +### Step 7: Resonator Spectroscopy vs Coupler Flux + +**Code File**: Not directly mapped (part of coupler calibration) + +**Purpose**: For systems with tunable couplers, optimize coupler flux bias. + +**Inputs**: +- `intermediate_frequency_amplitude`: Readout amplitude +- `coupler_flux_DC_offset_range`: Coupler flux range + +**Outputs**: +- `coupler_bias_phi_0_over_4`: Optimal coupler bias +- `UPSS_voltages_set`: UPSS voltage settings +- `LSS_voltages_set`: LSS voltage settings + +**Dependencies**: Step 6 + +**Typical Execution Time**: 5-10 minutes per coupler + +--- + +### Step 8: Qubit Spectroscopy + +**Code File**: `calibrations/1Q_calibrations/03a_qubit_spectroscopy.py` + +**Purpose**: Find the qubit transition frequency (|0⟩ ↔ |1⟩). + +**Inputs**: +- `intermediate_frequency_range`: Frequency sweep range (MHz) +- `drive_scaling_amp`: Drive amplitude scaling + +**Outputs**: +- `qubit.xy.frequency`: Qubit frequency (Hz) +- `qubit.xy.intermediate_frequency`: Intermediate frequency (Hz) + +**Dependencies**: Step 2 (Resonator Spectroscopy) + +**Typical Execution Time**: 3-5 minutes per qubit + +**Common Issues**: +- No resonance found: Widen frequency range or check drive line connection +- Multiple peaks: Identify |0⟩↔|1⟩ transition (usually strongest) + +--- + +### Step 9: Qubit Spectroscopy vs Qubit Flux + +**Code File**: `calibrations/1Q_calibrations/03b_qubit_spectroscopy_vs_flux.py` + +**Purpose**: For flux-tunable qubits, measure qubit frequency vs flux to characterize frequency dispersion. + +**Inputs**: +- `intermediate_frequency_range`: Frequency range +- `qubit_z_DC_offset`: Flux bias point + +**Outputs**: +- `frequency_dispersion`: Frequency vs flux relationship +- `UPSS_voltage_fine_tune`: Fine-tuned UPSS voltage +- `LSS_voltage_fine_tune`: Fine-tuned LSS voltage + +**Dependencies**: Step 8 + +**Typical Execution Time**: 5-10 minutes per qubit + +--- + +### Step 10: Power Rabi + +**Code File**: `calibrations/1Q_calibrations/04b_power_rabi.py` + +**Purpose**: Calibrate the π-pulse amplitude by measuring qubit population vs drive amplitude. + +**Inputs**: +- `amp_scaling_range`: Amplitude sweep range +- `num_shots`: Number of averages + +**Outputs**: +- `qubit.xy.operations["x180"].amplitude`: π-pulse amplitude +- `qubit.xy.operations["x90"].amplitude`: π/2-pulse amplitude (if calibrated) + +**Dependencies**: Step 8 (Qubit Spectroscopy) + +**Typical Execution Time**: 2-3 minutes per qubit + +**Common Issues**: +- Rabi oscillation not visible: Increase amplitude range or check drive line +- Multiple π-pulses: Use first π-pulse amplitude + +--- + +### Step 11: Ramsey + +**Code File**: `calibrations/1Q_calibrations/06a_ramsey.py` + +**Purpose**: Fine-tune qubit frequency by measuring detuning-induced oscillations. + +**Inputs**: +- `evolution_times`: Free evolution time array (ns) +- `detuning_freqs`: Detuning frequencies (MHz, optional) + +**Outputs**: +- `qubit.xy.frequency`: Fine-tuned qubit frequency (Hz) +- `qubit.T2*`: Dephasing time (if measured) + +**Dependencies**: Step 10 (Power Rabi) + +**Typical Execution Time**: 2-3 minutes per qubit + +**Common Issues**: +- No oscillations: Check detuning or increase evolution time range +- Fast decay: Qubit may have short T2* + +--- + +### Step 12: Phase Error Amp (DRAG) + +**Code File**: `calibrations/1Q_calibrations/10b_drag_calibration_180_minus_180.py` + +**Purpose**: Calibrate DRAG (Derivative Reduction by Adiabatic Gate) coefficient to reduce phase errors. + +**Inputs**: +- `num_of_repetitions`: Number of repetitions +- `drag_amp_values`: DRAG coefficient sweep range + +**Outputs**: +- `qubit.xy.operations["x180"].drag_coefficient`: Optimal DRAG coefficient +- `qubit.xy.operations["x90"].drag_coefficient`: DRAG coefficient for π/2 pulse + +**Dependencies**: Step 11 (Ramsey) + +**Typical Execution Time**: 3-5 minutes per qubit + +**Common Issues**: +- No clear optimum: Use default DRAG coefficient +- Phase error persists: Check pulse length or increase DRAG range + +--- + +### Step 13: Error Amp Rabi + +**Code File**: `calibrations/1Q_calibrations/04b_power_rabi.py` (with error amplification) + +**Purpose**: Refine π-pulse amplitude using error amplification technique. + +**Inputs**: +- `num_of_repetition`: Number of repetitions +- `amp_scaling_values`: Amplitude scaling range + +**Outputs**: +- `qubit.xy.operations["x180"].amplitude`: Refined π-pulse amplitude +- `single_qubit_gate_params`: Additional gate parameters + +**Dependencies**: Step 12 + +**Typical Execution Time**: 3-5 minutes per qubit + +--- + +### Step 14: Readout Optimization + +**Code File**: `calibrations/1Q_calibrations/08a_readout_frequency_optimization.py` or `08b_readout_power_optimization.py` + +**Purpose**: Optimize readout frequency and/or power for maximum state discrimination. + +**Inputs**: +- `intermediate_frequency_range`: Frequency range to optimize +- `readout_amp_scaling_values`: Power range to optimize + +**Outputs**: +- `qubit.resonator.frequency`: Optimized readout frequency +- `qubit.resonator.operations["readout"].amplitude`: Optimized readout amplitude + +**Dependencies**: Step 10 (Power Rabi) + +**Typical Execution Time**: 3-5 minutes per qubit + +--- + +### Step 15: IQ Blob + +**Code File**: `calibrations/1Q_calibrations/07_iq_blobs.py` + +**Purpose**: Measure IQ distributions for |0⟩ and |1⟩ states to enable state discrimination. + +**Inputs**: +- `num_shots`: Number of measurements + +**Outputs**: +- `qubit.resonator.IQ_rotation_angle`: Rotation angle for optimal discrimination +- `qubit.resonator.ge_discrimination_threshold`: State discrimination threshold + +**Dependencies**: Step 14 (Readout Optimization) + +**Typical Execution Time**: 1-2 minutes per qubit + +**Common Issues**: +- Overlapping blobs: Improve readout optimization or increase measurement time +- Blobs not separated: Check readout frequency and power + +--- + +### Step 16: Ramsey vs Qubit Flux + +**Code File**: `calibrations/1Q_calibrations/09_ramsey_vs_flux_calibration.py` + +**Purpose**: For flux-tunable qubits, fine-tune flux bias using Ramsey measurements. + +**Inputs**: +- `free_evolution_times`: Evolution time array +- `z_pulsed_amps`: Flux pulse amplitudes + +**Outputs**: +- `fine_tuned_joint_UPSS_voltages_set`: Fine-tuned UPSS voltages + +**Dependencies**: Step 11 (Ramsey), Step 6 (Resonator vs Flux) + +**Typical Execution Time**: 5-10 minutes per qubit + +--- + +### Step 17: ZZ off (JAZZ) + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/18a_coupler_zero_coarse.py` + +**Purpose**: Minimize ZZ coupling between qubits by tuning coupler flux. + +**Inputs**: +- `coupler_flux`: Coupler flux range +- `free_evolution_time`: Evolution time + +**Outputs**: +- `fine_tuned_joint_UPSS_coupler_voltage_set`: Optimal coupler bias voltages + +**Dependencies**: Step 16 + +**Typical Execution Time**: 10-15 minutes per qubit pair (iterative, 2-3 times) + +**Common Issues**: +- ZZ not minimized: Iterate more times or widen coupler flux range +- Instability: Check coupler connection and flux line + +--- + +### Step 18: T1 + +**Code File**: `calibrations/1Q_calibrations/05_T1.py` + +**Purpose**: Measure qubit energy relaxation time (T1). + +**Inputs**: +- `wait_times`: Wait time array (ns) + +**Outputs**: +- `qubit.T1`: T1 relaxation time (ns) + +**Dependencies**: Step 10 (Power Rabi), Step 14 (Readout Optimization) + +**Typical Execution Time**: 2-3 minutes per qubit + +**Common Issues**: +- T1 too short: Check qubit environment and connections +- Poor fit: Increase wait time range or number of shots + +--- + +### Step 19: T2* + +**Code File**: `calibrations/1Q_calibrations/06a_ramsey.py` (T2* extraction) + +**Purpose**: Measure qubit dephasing time (T2*). + +**Inputs**: +- `free_evolution_times`: Evolution time array +- `detuning_freqs`: Detuning frequencies + +**Outputs**: +- `qubit.T2*`: Dephasing time (ns) + +**Dependencies**: Step 18 (T1) + +**Typical Execution Time**: 2-3 minutes per qubit + +--- + +### Step 20: 1Q Randomized Benchmarking + +**Code File**: `calibrations/1Q_calibrations/11a_single_qubit_randomized_benchmarking.py` + +**Purpose**: Measure single-qubit gate fidelity using randomized benchmarking. + +**Inputs**: +- `num_random_sequence`: Number of random sequences +- `delta_clifford`: Clifford gate depth increment +- `circuit_depth`: Maximum circuit depth + +**Outputs**: +- `qubit.gate_fidelity["averaged"]`: Average gate fidelity +- `SQ_error_per_gate`: Error per gate + +**Dependencies**: Step 12 (DRAG), Step 14 (Readout Optimization) + +**Typical Execution Time**: 10-20 minutes per qubit + +**Common Issues**: +- Fidelity too low: Check previous calibrations, especially DRAG +- No decay visible: Increase circuit depth or check readout + +--- + +### Step 21: Cryoscope (IIR) + +**Code File**: `calibrations/1Q_calibrations/18_cryoscope.py` + +**Purpose**: Characterize and compensate for short-timescale flux pulse distortions using IIR filters. + +**Inputs**: +- `drive_frequency`: Drive frequency +- `flux_duration`: Flux pulse duration range + +**Outputs**: +- `qubit.z.digital_filter_IIR`: IIR filter coefficients for distortion compensation + +**Dependencies**: Step 10 (Power Rabi) + +**Typical Execution Time**: 5-10 minutes per qubit (interactive fitting) + +**Common Issues**: +- Poor fit: Adjust exponential time constants interactively +- Distortions persist: Check flux line and cabling + +--- + +### Step 22: XY-Z Delay + +**Code File**: `calibrations/1Q_calibrations/16_xyz_delay.py` + +**Purpose**: Calibrate the relative delay between XY (microwave) and Z (flux) control lines. + +**Inputs**: +- `relative_delay`: Delay sweep range (ns) + +**Outputs**: +- `qubit.z.delay`: Optimal Z delay relative to XY + +**Dependencies**: Step 10 (Power Rabi), Step 21 (Cryoscope) + +**Typical Execution Time**: 2-3 minutes per qubit + +--- + +### Step 23: XY-Coupler Delay + +**Code File**: `calibrations/1Q_calibrations/16b_xy_coupler_delay.py` + +**Purpose**: Calibrate the relative delay between XY and coupler control lines. + +**Inputs**: +- `relative_delay`: Delay sweep range (ns) + +**Outputs**: +- `coupler.delay`: Optimal coupler delay relative to XY + +**Dependencies**: Step 22 + +**Typical Execution Time**: 2-3 minutes per coupler + +--- + +### Step 24: EF Spectroscopy + +**Code File**: `calibrations/1Q_calibrations/12_Qubit_Spectroscopy_E_to_F.py` + +**Purpose**: Measure the |1⟩ ↔ |2⟩ transition frequency to determine anharmonicity. + +**Inputs**: +- `ef_frequency`: Frequency range for |1⟩↔|2⟩ transition + +**Outputs**: +- `qubit.anharmonicity`: Qubit anharmonicity (Hz) + +**Dependencies**: Step 8 (Qubit Spectroscopy) + +**Typical Execution Time**: 3-5 minutes per qubit + +--- + +### Step 25: EF Rabi and Readout Opt + +**Code File**: `calibrations/1Q_calibrations/13_power_rabi_ef.py` and `14_gef_readout_frequency_optimization.py` + +**Purpose**: Calibrate π-pulse for |1⟩↔|2⟩ transition and optimize readout for |2⟩ state. + +**Inputs**: +- `readout_amplitude`: Readout amplitude + +**Outputs**: +- `qubit.xy.operations["ef_x180"].amplitude`: EF π-pulse amplitude +- `qubit.resonator.gef_frequency`: Optimal readout frequency for |2⟩ state + +**Dependencies**: Step 24 + +**Typical Execution Time**: 5-10 minutes per qubit + +--- + +### Step 26: 02-11 Leakage & Cond-Z + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/19_chevron_11_02.py` + +**Purpose**: Initial CZ gate calibration using Chevron pattern to find operating point. + +**Inputs**: +- `flux_amplitude`: Flux amplitude range +- `gate_time`: Gate duration range + +**Outputs**: +- `qubit.flux_amp`: Initial qubit flux amplitude +- `coupler.flux_amp`: Initial coupler flux amplitude + +**Dependencies**: Step 17 (ZZ off), Step 21 (Cryoscope) + +**Typical Execution Time**: 10-15 minutes per qubit pair + +--- + +### Step 27: 1D Cond-Z vs Qubit Flux Bias + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/20_cz_conditional_phase.py` + +**Purpose**: Fine-tune CZ gate by measuring conditional phase vs qubit flux bias. + +**Inputs**: +- `qubit_flux_amplitude`: Flux amplitude range + +**Outputs**: +- `qubit.flux_amp`: Refined qubit flux amplitude for CZ + +**Dependencies**: Step 26 + +**Typical Execution Time**: 5-10 minutes per qubit pair + +--- + +### Step 28: Error Amplification 1D Cond-Z + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/20b_cz_conditional_phase_error_amp.py` + +**Purpose**: Refine CZ gate amplitude using error amplification. + +**Inputs**: +- `qubit_flux_amplitude`: Flux amplitude range + +**Outputs**: +- `qubit.flux_amp`: Final qubit flux amplitude + +**Dependencies**: Step 27 + +**Typical Execution Time**: 5-10 minutes per qubit pair + +--- + +### Step 29: Error Amplification of Leakage + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/20b_cz_conditional_phase_error_amp.py` (leakage component) + +**Purpose**: Minimize leakage to |02⟩ state during CZ gate. + +**Inputs**: +- `coupler_flux_amplitude`: Coupler flux amplitude range + +**Outputs**: +- `coupler.flux_amp`: Optimal coupler flux amplitude + +**Dependencies**: Step 28 + +**Typical Execution Time**: 5-10 minutes per qubit pair + +--- + +### Step 30: Trivial Phase Compensation + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/21_cz_phase_compensation.py` + +**Purpose**: Measure and compensate for single-qubit phases acquired during CZ gate. + +**Inputs**: +- `frame_rotation`: Frame rotation range + +**Outputs**: +- `qubit.phase_cor_qij`: Phase correction for qubit i when interacting with qubit j + +**Dependencies**: Step 29 + +**Typical Execution Time**: 5-10 minutes per qubit pair + +--- + +### Step 31: Bell State Tomography + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/24_Bell_State_Tomography.py` + +**Purpose**: Measure Bell state fidelity to verify CZ gate quality. + +**Inputs**: (Measurement only, no sweep parameters) + +**Outputs**: +- `entangled_fid`: Bell state fidelity + +**Dependencies**: Step 30 + +**Typical Execution Time**: 2-3 minutes per qubit pair + +**Common Issues**: +- Low fidelity: Re-check previous CZ calibration steps +- Measurement errors: Improve readout calibration + +--- + +### Step 32: Two-Qubit Randomized Benchmarking + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/22_two_qubit_standard_rb.py` or `23_two_qubit_interleaved_cz_rb.py` + +**Purpose**: Measure two-qubit gate fidelity using randomized benchmarking. + +**Inputs**: +- `circuit_depth`: Maximum circuit depth + +**Outputs**: +- `2q_fid`: Two-qubit gate fidelity +- `CZ_error_per_gate`: CZ gate error rate + +**Dependencies**: Step 31 + +**Typical Execution Time**: 20-30 minutes per qubit pair + +**Common Issues**: +- Fidelity too low: Re-calibrate CZ gate parameters +- Long execution time: Reduce circuit depth or number of sequences + +--- + +### Step 33: XEB (Cross-Entropy Benchmarking) + +**Code File**: Not yet implemented (alternative to Step 32) + +**Purpose**: Alternative method to measure two-qubit gate fidelity. + +**Inputs**: +- `circuit_depth`: Maximum circuit depth + +**Outputs**: +- `2q_fid`: Two-qubit gate fidelity + +**Dependencies**: Step 30 + +**Typical Execution Time**: 20-30 minutes per qubit pair + +--- + +### Step 34: (Additional characterization) + +**Code File**: Various characterization nodes + +**Purpose**: Additional measurements for system characterization. + +**Dependencies**: Previous steps + +--- + +## Complete Calibration Graphs + +The codebase includes complete calibration graphs that orchestrate multiple steps: + +### Fixed-Frequency Transmon Bringup + +**Code File**: `calibrations/1Q_calibrations/90_calibration_graph_bringup_fixed_frequency_transmon.py` + +**Steps Included**: Steps 2, 8, 10, 14, 15, 18, 19, 12, 20 + +**Purpose**: Complete bringup workflow for fixed-frequency transmons. + +### Flux-Tunable Transmon Bringup + +**Code File**: `calibrations/1Q_calibrations/80_calibration_graph_bringup_flux_tunable_transmon.py` + +**Steps Included**: Steps 1-20 with flux-dependent calibrations + +**Purpose**: Complete bringup workflow for flux-tunable transmons. + +### CZ Gate Calibration Graph + +**Code File**: `calibrations/CZ_calibration_fixed_couplers/99_CZ_calibration_graph.py` + +**Steps Included**: Steps 17, 21, 26-32 + +**Purpose**: Complete CZ gate calibration workflow. + +## Data Flow Between Steps + +```mermaid +graph LR + A[Step N] -->|Updates State| B[Quam State] + B -->|Reads Parameters| C[Step N+1] + C -->|Updates State| B +``` + +Each calibration step: +1. Reads parameters from the `Quam` state object (`node.machine`) +2. Executes the calibration experiment +3. Analyzes the data +4. Updates the `Quam` state with new parameters +5. Saves results to disk + +The next step in the workflow reads the updated state and uses the new parameters. + +## Typical Execution Times + +- **Single-qubit calibrations**: 1-5 minutes each +- **Two-qubit calibrations**: 5-15 minutes each +- **Characterization measurements**: 2-10 minutes each +- **Complete 1Q bringup**: 1-2 hours +- **Complete CZ calibration**: 2-3 hours + +## Common Workflow Patterns + +### Sequential Dependencies + +Most calibrations depend on previous steps. The workflow ensures: +- Readout is calibrated before qubit measurements +- Qubit frequency is known before gate calibration +- Single-qubit gates are calibrated before two-qubit gates + +### Parallel Execution + +Some calibrations can run in parallel: +- Multiple qubits can be calibrated simultaneously (if hardware supports) +- Independent characterization measurements can run in parallel + +### Iterative Refinement + +Some calibrations require iteration: +- ZZ off (JAZZ): Typically 2-3 iterations +- CZ gate calibration: May require multiple passes + +## Troubleshooting Workflow Issues + +### If a Step Fails + +1. Check prerequisites: Ensure all dependent steps completed successfully +2. Review parameters: Verify input parameters are reasonable +3. Check hardware: Verify connections and hardware status +4. Review data: Examine raw data and fit results +5. Adjust parameters: Modify sweep ranges or measurement settings +6. Re-run: Execute the step again with adjusted parameters + +### If Results Are Poor + +1. Review previous steps: Poor results may indicate issues in earlier calibrations +2. Check system stability: Verify qubit coherence times and system drifts +3. Optimize parameters: Fine-tune sweep ranges and measurement settings +4. Increase statistics: Use more shots or longer measurement times + +## Next Steps + +After understanding the workflow: + +1. Review [Code Patterns](code-patterns.md) to understand implementation +2. Study [Calibration Procedures](calibration-procedures.md) for detailed physics +3. Explore [Example Walkthrough](example-walkthrough.md) for hands-on learning diff --git a/docs/onboarding/code-patterns.md b/docs/onboarding/code-patterns.md new file mode 100644 index 000000000..5ca421c4a --- /dev/null +++ b/docs/onboarding/code-patterns.md @@ -0,0 +1,587 @@ +# Code Patterns Catalog + +This document catalogs common code patterns, structures, and best practices used throughout the calibration codebase. + +## Calibration Node Structure + +Every calibration node follows a consistent structure with these sections: + +### 1. Imports Section + +```python +# %% {Imports} +import numpy as np +import xarray as xr +from qm.qua import * +from qualang_tools.loops import from_array +from qualang_tools.multi_user import qm_session +from qualang_tools.results import progress_counter +from qualang_tools.units import unit +from qualibrate import QualibrationNode +from quam_config import Quam +from calibration_utils. import Parameters, ... +``` + +**Pattern**: Always import QUA with `from qm.qua import *` to access QUA language constructs directly. + +### 2. Node Initialization + +```python +# %% {Node initialisation} +description = """ + CALIBRATION DESCRIPTION +Brief description of what this calibration does. + +Prerequisites: + - List of required previous calibrations + - Any hardware requirements + +State update: + - What parameters are updated in the Quam state +""" + +node = QualibrationNode[Parameters, Quam]( + name="calibration_name", + description=description, + parameters=Parameters(), +) +``` + +**Pattern**: +- Use descriptive names that match the file name +- Include comprehensive description with prerequisites and state updates +- Type hint with `[Parameters, Quam]` for IDE support + +### 3. Custom Parameters (Debug Only) + +```python +# %% {Custom_param} +@node.run_action(skip_if=node.modes.external) +def custom_param(node: QualibrationNode[Parameters, Quam]): + """Override parameters for debugging when running locally.""" + # These parameters are ignored when run through GUI or as part of a graph + # node.parameters.qubits = ["q1"] + # node.parameters.num_shots = 100 + pass +``` + +**Pattern**: +- Use `skip_if=node.modes.external` to ensure this only runs in local mode +- Comment out parameter overrides by default +- Use for quick debugging without modifying the actual parameters + +### 4. Machine Loading + +```python +# Instantiate the QUAM class from the state file +node.machine = Quam.load() +``` + +**Pattern**: Always load the Quam state before creating the QUA program. + +### 5. QUA Program Creation + +```python +# %% {Create_QUA_program} +@node.run_action(skip_if=node.parameters.load_data_id is not None) +def create_qua_program(node: QualibrationNode[Parameters, Quam]): + """Create the sweep axes and generate the QUA program.""" + u = unit(coerce_to_integer=True) + node.namespace["qubits"] = qubits = get_qubits(node) + + # Define sweep axes + node.namespace["sweep_axes"] = { + "qubit": xr.DataArray(qubits.get_names()), + "sweep_param": xr.DataArray(sweep_values, attrs={"long_name": "...", "units": "..."}), + } + + # Create QUA program + with program() as node.namespace["qua_program"]: + # Declare variables + I, I_st, Q, Q_st, n, n_st = node.machine.declare_qua_variables() + sweep_var = declare(fixed) + + # Main program loop + for multiplexed_qubits in qubits.batch(): + # Initialize QPU + for qubit in multiplexed_qubits.values(): + node.machine.initialize_qpu(target=qubit) + align() + + # Averaging loop + with for_(n, 0, n < node.parameters.num_shots, n + 1): + save(n, n_st) + + # Sweep loop + with for_(*from_array(sweep_var, sweep_values)): + # Pulse sequence + for i, qubit in multiplexed_qubits.items(): + qubit.xy.play("x180") + qubit.wait(250 * u.ns) + align() + + # Measurement + for i, qubit in multiplexed_qubits.items(): + qubit.resonator.measure("readout", qua_vars=(I[i], Q[i])) + save(I[i], I_st[i]) + save(Q[i], Q_st[i]) + + # Stream processing + with stream_processing(): + n_st.save("n") + for i in range(num_qubits): + I_st[i].buffer(len(sweep_values)).average().save(f"I{i + 1}") + Q_st[i].buffer(len(sweep_values)).average().save(f"Q{i + 1}") +``` + +**Patterns**: +- Use `unit()` for time conversions with `coerce_to_integer=True` +- Get qubits with `get_qubits(node)` which handles batching +- Store sweep axes in `node.namespace["sweep_axes"]` as xarray DataArrays +- Use `node.machine.declare_qua_variables()` for standard I/Q/n variables +- Always `align()` after initialization and between operations +- Use `qubits.batch()` to handle multiplexed readout +- Buffer and average in `stream_processing()` section + +### 6. Simulation (Optional) + +```python +# %% {Simulate} +@node.run_action(skip_if=node.parameters.load_data_id is not None or not node.parameters.simulate) +def simulate_qua_program(node: QualibrationNode[Parameters, Quam]): + """Connect to the QOP and simulate the QUA program""" + qmm = node.machine.connect() + config = node.machine.generate_config() + samples, fig, wf_report = simulate_and_plot(qmm, config, node.namespace["qua_program"], node.parameters) + node.results["simulation"] = {"figure": fig, "wf_report": wf_report, "samples": samples} +``` + +**Pattern**: +- Skip if loading data or if simulation is disabled +- Store simulation results in `node.results["simulation"]` + +### 7. Execution + +```python +# %% {Execute} +@node.run_action(skip_if=node.parameters.load_data_id is not None or node.parameters.simulate) +def execute_qua_program(node: QualibrationNode[Parameters, Quam]): + """Execute the QUA program and fetch raw data.""" + qmm = node.machine.connect() + config = node.machine.generate_config() + + with qm_session(qmm, config, timeout=node.parameters.timeout) as qm: + node.namespace["job"] = job = qm.execute(node.namespace["qua_program"]) + data_fetcher = XarrayDataFetcher(job, node.namespace["sweep_axes"]) + + for dataset in data_fetcher: + progress_counter( + data_fetcher.get("n", 0), + node.parameters.num_shots, + start_time=data_fetcher.t_start, + ) + + node.log(job.execution_report()) + + node.results["ds_raw"] = dataset +``` + +**Patterns**: +- Use `qm_session()` context manager for safe execution +- Store job in `node.namespace["job"]` for potential reuse +- Use `XarrayDataFetcher` to convert QUA streams to xarray datasets +- Show progress with `progress_counter()` +- Log execution report for debugging +- Store raw dataset in `node.results["ds_raw"]` + +### 8. Load Historical Data (Optional) + +```python +# %% {Load_historical_data} +@node.run_action(skip_if=node.parameters.load_data_id is None) +def load_data(node: QualibrationNode[Parameters, Quam]): + """Load a previously acquired dataset.""" + load_data_id = node.parameters.load_data_id + node.load_from_id(load_data_id) + node.parameters.load_data_id = load_data_id + node.namespace["qubits"] = get_qubits(node) +``` + +**Pattern**: +- Allows re-analyzing old data without re-running experiment +- Preserve `load_data_id` after loading for reference + +### 9. Data Analysis + +```python +# %% {Analyse_data} +@node.run_action(skip_if=node.parameters.simulate) +def analyse_data(node: QualibrationNode[Parameters, Quam]): + """Analyse raw data into fit results.""" + from dataclasses import asdict + + node.results["ds_raw"] = process_raw_dataset(node.results["ds_raw"], node) + node.results["ds_fit"], fit_results = fit_raw_data(node.results["ds_raw"], node) + node.results["fit_results"] = {k: asdict(v) for k, v in fit_results.items()} + + log_fitted_results(node.results["fit_results"], log_callable=node.log) + node.outcomes = { + qubit_name: ("successful" if fit_result["success"] else "failed") + for qubit_name, fit_result in node.results["fit_results"].items() + } +``` + +**Patterns**: +- Process raw data first (unit conversions, calculations) +- Fit data to extract parameters +- Convert fit results to dictionaries for serialization +- Log results using `node.log()` +- Set `node.outcomes` to track success/failure per qubit + +### 10. Plotting + +```python +# %% {Plot_data} +@node.run_action(skip_if=node.parameters.simulate) +def plot_data(node: QualibrationNode[Parameters, Quam]): + """Plot the raw and fitted data.""" + import matplotlib.pyplot as plt + + fig = plot_raw_data_with_fit( + node.results["ds_raw"], + node.namespace["qubits"], + node.results["ds_fit"] + ) + plt.show() + node.results["figures"] = {"main": fig} +``` + +**Pattern**: +- Use utility functions from `calibration_utils` for consistent plotting +- Store figures in `node.results["figures"]` for later access + +### 11. State Update + +```python +# %% {Update_state} +@node.run_action(skip_if=node.parameters.simulate or not node.parameters.update_state) +def update_state(node: QualibrationNode[Parameters, Quam]): + """Update the relevant parameters if the qubit data analysis was successful.""" + + with node.record_state_updates(): + for q in node.namespace["qubits"]: + if not node.results["fit_results"][q.name]["success"]: + continue + + fit_result = node.results["fit_results"][q.name] + q.resonator.time_of_flight = fit_result["tof_to_add"] + q.resonator.opx_input_I.offset = fit_result["offset_I_to_add"] + # ... update other parameters +``` + +**Patterns**: +- Use `node.record_state_updates()` context manager to track changes +- Only update state if fit was successful +- Update parameters directly on qubit/resonator objects +- Changes are automatically saved when node saves + +### 12. Save Results + +```python +# %% {Save_results} +@node.run_action() +def save_results(node: QualibrationNode[Parameters, Quam]): + """Persist node results to disk.""" + node.save() +``` + +**Pattern**: Always save at the end to persist all results and state updates. + +## QUA Program Patterns + +### Variable Declaration + +```python +# Standard I/Q variables for multiplexed readout +I, I_st, Q, Q_st, n, n_st = node.machine.declare_qua_variables() + +# Sweep variables +sweep_var = declare(fixed) # For floating-point sweeps +sweep_var = declare(int) # For integer sweeps + +# Streams for raw ADC traces +adc_st = [declare_stream(adc_trace=True) for _ in range(num_qubits)] +``` + +### Looping Patterns + +```python +# Averaging loop +with for_(n, 0, n < node.parameters.num_shots, n + 1): + save(n, n_st) + # ... experiment code + +# Sweep loop with from_array +with for_(*from_array(sweep_var, sweep_values)): + # ... sweep code + +# Nested loops +with for_(outer, 0, outer < outer_max, outer + 1): + with for_(inner, 0, inner < inner_max, inner + 1): + # ... nested code +``` + +### Qubit Batching + +```python +# Handle multiplexed readout automatically +for multiplexed_qubits in qubits.batch(): + # Initialize all qubits in batch + for qubit in multiplexed_qubits.values(): + node.machine.initialize_qpu(target=qubit) + align() + + # Access by index for measurements + for i, qubit in multiplexed_qubits.items(): + qubit.resonator.measure("readout", qua_vars=(I[i], Q[i])) +``` + +### Pulse Sequences + +```python +# Single qubit gate +qubit.xy.play("x180") +qubit.xy.play("x90", amplitude_scale=0.5) + +# Wait +qubit.wait(250 * u.ns) + +# Measurement +qubit.resonator.measure("readout", qua_vars=(I[i], Q[i])) + +# Flux control +qubit.z.set_dc_offset(voltage) +qubit.z.settle() +qubit.z.play("const", duration=100 * u.ns, amplitude=voltage) +``` + +### Stream Processing + +```python +with stream_processing(): + # Save shot counter + n_st.save("n") + + # Buffer and average I/Q + for i in range(num_qubits): + I_st[i].buffer(len(sweep_values)).average().save(f"I{i + 1}") + Q_st[i].buffer(len(sweep_values)).average().save(f"Q{i + 1}") + + # Save raw ADC traces + for i in range(num_qubits): + adc_st[i].input1().average().save(f"adcI{i + 1}") + adc_st[i].input2().average().save(f"adcQ{i + 1}") + adc_st[i].input1().save(f"adc_single_runI{i + 1}") # Last run only +``` + +## Parameter Management Patterns + +### Parameter Class Structure + +```python +from qualibrate import NodeParameters +from qualibrate.parameters import RunnableParameters +from qualibration_libs.parameters import CommonNodeParameters, QubitsExperimentNodeParameters + +class NodeSpecificParameters(RunnableParameters): + """Node-specific configuration.""" + num_shots: int = 100 + sweep_range: tuple[float, float] = (-1.0, 1.0) + optional_param: Optional[float] = None + +class Parameters( + NodeParameters, + CommonNodeParameters, + NodeSpecificParameters, + QubitsExperimentNodeParameters, +): + """Aggregate parameter set.""" + pass +``` + +**Pattern**: Inherit from multiple parameter classes to combine common, node-specific, and qubit parameters. + +### Accessing Parameters + +```python +# In run actions +node.parameters.num_shots +node.parameters.qubits +node.parameters.sweep_range + +# Type hints for IDE support +def my_function(node: QualibrationNode[Parameters, Quam]): + node.parameters.num_shots # IDE knows this is an int +``` + +## Data Processing Patterns + +### Raw Data Processing + +```python +def process_raw_dataset(ds: xr.Dataset, node: QualibrationNode) -> xr.Dataset: + """Convert raw data to physical units.""" + # Convert ADC counts to volts + ds = ds.assign({key: -ds[key] / 2**12 for key in ("adcI", "adcQ")}) + + # Calculate derived quantities + ds = ds.assign({"IQ_abs": np.sqrt(ds["adcI"] ** 2 + ds["adcQ"] ** 2)}) + ds.IQ_abs.attrs = {"long_name": "IQ amplitude", "units": "V"} + + return ds +``` + +### Fitting Patterns + +```python +from dataclasses import dataclass +from typing import Dict, Tuple +import xarray as xr +from lmfit import Model + +@dataclass +class FitParameters: + """Fit results for a single qubit.""" + parameter1: float + parameter2: float + success: bool + +def fit_raw_data(ds: xr.Dataset, node: QualibrationNode) -> Tuple[xr.Dataset, Dict[str, FitParameters]]: + """Fit data and extract parameters.""" + fit_results = {} + + for qubit_name in ds.qubit.values: + qubit_data = ds.sel(qubit=qubit_name) + + # Perform fit + model = Model(my_model_function) + result = model.fit(qubit_data.values, x=qubit_data.sweep_param.values) + + # Extract parameters + fit_results[qubit_name] = FitParameters( + parameter1=result.params["param1"].value, + parameter2=result.params["param2"].value, + success=result.success, + ) + + # Create fit dataset + ds_fit = xr.Dataset({ + "fitted_curve": (["qubit", "sweep_param"], fitted_curves), + }) + + return ds_fit, fit_results +``` + +## Error Handling Patterns + +### Skip Conditions + +```python +# Skip if loading data +@node.run_action(skip_if=node.parameters.load_data_id is not None) + +# Skip if simulating +@node.run_action(skip_if=node.parameters.simulate) + +# Skip if external mode (GUI/graph) +@node.run_action(skip_if=node.modes.external) + +# Combined conditions +@node.run_action(skip_if=node.parameters.load_data_id is not None or node.parameters.simulate) +``` + +### Success/Failure Tracking + +```python +# Set outcomes based on fit success +node.outcomes = { + qubit_name: ("successful" if fit_result["success"] else "failed") + for qubit_name, fit_result in node.results["fit_results"].items() +} + +# Only update state for successful fits +for q in node.namespace["qubits"]: + if not node.results["fit_results"][q.name]["success"]: + continue + # ... update state +``` + +## State Management Patterns + +### Temporary State Updates + +```python +from qualibration_libs.core import tracked_updates + +# Make temporary changes that are reverted automatically +with tracked_updates(resonator, auto_revert=False) as resonator: + resonator.time_of_flight = node.parameters.time_of_flight_in_ns + # ... use modified resonator +# Changes are reverted here (if auto_revert=True) + +# Manual revert +tracked_resonator.revert_changes() +``` + +### Recording State Updates + +```python +# Track all state changes for logging/undo +with node.record_state_updates(): + q.resonator.frequency = new_frequency + q.xy.operations["x180"].amplitude = new_amplitude +# Changes are recorded and can be logged +``` + +## Best Practices + +### Code Organization + +1. **Use cell markers**: Use `# %% {Section}` markers for clear code organization +2. **Import organization**: Group imports logically (standard, third-party, local) +3. **Function documentation**: Always include docstrings explaining purpose and parameters +4. **Type hints**: Use type hints for better IDE support and documentation + +### Performance + +1. **Batch operations**: Use `qubits.batch()` for efficient multiplexed readout +2. **Minimize loops**: Use vectorized operations in data analysis when possible +3. **Stream processing**: Buffer and average in QUA to reduce data transfer + +### Maintainability + +1. **Reuse utilities**: Use functions from `calibration_utils` instead of duplicating code +2. **Consistent naming**: Follow existing naming conventions +3. **Error messages**: Provide clear error messages and logging +4. **Comments**: Comment complex logic and non-obvious decisions + +### Testing + +1. **Simulation mode**: Always test with `simulate=True` first +2. **Small datasets**: Use small `num_shots` for quick testing +3. **Load data**: Use `load_data_id` to test analysis without re-running experiments + +## Anti-Patterns to Avoid + +1. **Don't modify state outside `update_state`**: Always use the dedicated run action +2. **Don't skip `align()`**: Always align after initialization and between operations +3. **Don't hardcode parameters**: Use `node.parameters` instead +4. **Don't ignore failures**: Always check `fit_result["success"]` before updating state +5. **Don't forget units**: Always specify units in xarray DataArray attrs +6. **Don't mix concerns**: Keep QUA program, analysis, and plotting separate + +## Next Steps + +- Review [DSL and API Reference](dsl-api-reference.md) for QUA language details +- Study [Example Walkthrough](example-walkthrough.md) for a complete example +- Explore [Calibration Procedures](calibration-procedures.md) for physics details diff --git a/docs/onboarding/dev-environment.md b/docs/onboarding/dev-environment.md new file mode 100644 index 000000000..83fb64f5b --- /dev/null +++ b/docs/onboarding/dev-environment.md @@ -0,0 +1,385 @@ +# Development Environment Setup Guide + +This guide provides step-by-step instructions for setting up your development environment for the qubit calibration codebase. + +## Prerequisites + +Before starting, ensure you have: + +- **Python 3.10-3.12** installed +- **Git** installed +- **Docker Desktop** (optional, for DevContainer) +- **VS Code** (recommended, for DevContainer support) +- Access to QM hardware or simulator (for running calibrations) + +## Option 1: Docker DevContainer (Recommended) + +The easiest way to get started is using the provided DevContainer setup. + +### Step 1: Install Docker Desktop + +1. Download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/) +2. Start Docker Desktop +3. Verify installation: `docker --version` + +### Step 2: Install VS Code Extensions + +1. Install [VS Code](https://code.visualstudio.com/) +2. Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +### Step 3: Use the Installer (Windows) + +1. Run `Docker_installer_QUA/QM-Dev-Installer.exe` +2. Follow the installation wizard +3. The installer will: + - Enable WSL and VirtualMachinePlatform + - Install Docker Desktop + - Install VS Code + - Install required VS Code extensions + - Create the project template + +### Step 4: Open in DevContainer + +1. Open VS Code +2. Open the project folder: `File > Open Folder` +3. Select the `CS_installations` folder +4. Click `F1` or `Ctrl+Shift+P` to open command palette +5. Type "Dev Containers: Reopen in Container" +6. Wait for the container to build (first time may take several minutes) + +### Step 5: Verify Installation + +```bash +# Check Python version +python --version # Should be 3.10, 3.11, or 3.12 + +# Check uv is installed +uv --version + +# Navigate to calibration directory +cd qualibration_graphs/superconducting + +# Install dependencies +uv sync --group dev --prerelease=allow + +# Activate virtual environment +source .venv/bin/activate # Linux/Mac +# or +.venv\Scripts\activate # Windows +``` + +## Option 2: Local Python Environment + +If you prefer not to use Docker, you can set up a local Python environment. + +### Step 1: Install Python + +1. Download Python 3.10, 3.11, or 3.12 from [python.org](https://www.python.org/downloads/) +2. During installation, check "Add Python to PATH" +3. Verify: `python --version` + +### Step 2: Install uv (Recommended Package Manager) + +```bash +# Windows (PowerShell) +powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + +# Linux/Mac +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Or using pip +pip install uv +``` + +### Step 3: Clone Repository + +```bash +git clone git@github.com:qua-platform/CS_installations.git +cd CS_installations/qualibration_graphs/superconducting +``` + +### Step 4: Create Virtual Environment and Install Dependencies + +```bash +# Using uv (recommended) +uv sync --group dev --prerelease=allow +source .venv/bin/activate # Linux/Mac +# or +.venv\Scripts\activate # Windows + +# Alternative: using venv +python -m venv .venv +source .venv/bin/activate # Linux/Mac +# or +.venv\Scripts\activate # Windows +pip install -e ".[dev]" +``` + +### Step 5: Verify Installation + +```bash +# Check installed packages +pip list + +# Run tests +pytest tests/test_imports.py + +# Check pre-commit +pre-commit --version +``` + +## Setting Up Pre-commit Hooks + +Pre-commit hooks ensure code quality before commits. + +### Install Hooks + +```bash +cd qualibration_graphs/superconducting +pre-commit install +pre-commit install --hook-type commit-msg +``` + +### Test Hooks + +```bash +# Run on all files (first time) +pre-commit run --all-files + +# Run on staged files only +pre-commit run +``` + +### What Runs + +- **Black**: Code formatting (120 char line length) +- **Pylint**: Code quality checks +- **Commitizen**: Commit message validation +- **Generic checks**: Trailing whitespace, file endings, etc. + +## IDE Configuration + +### VS Code + +Recommended extensions: + +- **Python** (Microsoft) +- **Pylance** (Microsoft) - Python language server +- **Black Formatter** (Microsoft) - Code formatting +- **Dev Containers** (Microsoft) - Container support + +Settings (`.vscode/settings.json`): + +```json +{ + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "python.formatting.provider": "black", + "python.formatting.blackArgs": ["--line-length", "120"], + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + } +} +``` + +### PyCharm + +1. Open project in PyCharm +2. Configure Python interpreter: `File > Settings > Project > Python Interpreter` +3. Select the `.venv` interpreter +4. Install Black plugin and configure as formatter +5. Set line length to 120 + +## QM Hardware Connection + +### Prerequisites + +- QM hardware (OPX, OPX+, etc.) or simulator +- Network access to QOP (Quantum Operations Processor) +- Valid QM credentials + +### Configuration + +1. Create or load a Quam state file: + ```python + from quam_config import Quam + machine = Quam.load() # Loads from default location + ``` + +2. Connect to QOP: + ```python + qmm = machine.connect() + config = machine.generate_config() + ``` + +3. Test connection: + ```python + # Run hello_qua calibration + python calibrations/1Q_calibrations/00_hello_qua.py + ``` + +## Running Your First Calibration + +### Step 1: Prepare Quam State + +Ensure you have a valid Quam state file with your hardware configuration. + +### Step 2: Run Hello QUA + +```bash +cd qualibration_graphs/superconducting +python calibrations/1Q_calibrations/00_hello_qua.py +``` + +This tests the connection to the QOP without running a full calibration. + +### Step 3: Run Time of Flight + +```bash +python calibrations/1Q_calibrations/01a_time_of_flight.py +``` + +This is the first real calibration and will: +- Connect to hardware +- Run the calibration sequence +- Analyze data +- Update system state + +## Troubleshooting + +### Python Version Issues + +**Problem**: Wrong Python version + +**Solution**: +- Ensure Python 3.10-3.12 is installed +- Verify with `python --version` +- Use `py -3.11` on Windows if multiple versions installed + +### Dependency Installation Issues + +**Problem**: `uv sync` fails + +**Solution**: +- Update uv: `uv self update` +- Clear cache: `uv cache clean` +- Try with `--prerelease=allow` flag +- Check internet connection for git dependencies + +### Pre-commit Issues + +**Problem**: Pre-commit hooks fail + +**Solution**: +- Update hooks: `pre-commit autoupdate` +- Run manually: `pre-commit run --all-files` +- Check individual hook: `pre-commit run black --all-files` + +### Docker Issues + +**Problem**: DevContainer won't start + +**Solution**: +- Ensure Docker Desktop is running +- Check Docker resources (memory, CPU) +- Rebuild container: `Dev Containers: Rebuild Container` +- Check logs in VS Code output panel + +### QM Connection Issues + +**Problem**: Cannot connect to QOP + +**Solution**: +- Verify network connectivity +- Check QOP is running and accessible +- Verify credentials in Quam state +- Test with QM's connection tools + +### Import Errors + +**Problem**: Module not found errors + +**Solution**: +- Ensure virtual environment is activated +- Reinstall dependencies: `uv sync --group dev` +- Check Python path: `python -c "import sys; print(sys.path)"` +- Verify you're in the correct directory + +## Development Workflow + +### Daily Workflow + +1. **Activate environment**: + ```bash + cd qualibration_graphs/superconducting + source .venv/bin/activate # or .venv\Scripts\activate on Windows + ``` + +2. **Pull latest changes**: + ```bash + git pull + uv sync --group dev # Update dependencies if needed + ``` + +3. **Make changes**: + - Edit code + - Run tests: `pytest` + - Format code: `black .` + - Check linting: `pylint ` + +4. **Commit changes**: + ```bash + git add . + git commit -m "feat: your change description" + # Pre-commit hooks run automatically + ``` + +### Testing + +```bash +# Run all tests +pytest + +# Run specific test file +pytest tests/test_imports.py + +# Run with coverage +pytest --cov --cov-report=html + +# Run in verbose mode +pytest -v +``` + +### Code Quality + +```bash +# Format code +black . + +# Check linting +pylint + +# Type checking +mypy + +# Run all checks +pre-commit run --all-files +``` + +## Next Steps + +After setting up your environment: + +1. Read [Repository Map](repository-map.md) to understand the codebase structure +2. Review [Code Patterns](code-patterns.md) to learn coding conventions +3. Study [Example Walkthrough](example-walkthrough.md) for hands-on learning +4. Explore [Calibration Workflow](calibration-workflow-detailed.md) to understand the calibration process + +## Getting Help + +If you encounter issues: + +1. Check this guide's troubleshooting section +2. Review [Quick Reference](quick-reference.md) for common commands +3. Check the [Contributing Guide](../../qualibration_graphs/superconducting/CONTRIBUTING.md) +4. Ask in the development channel or tag a maintainer diff --git a/docs/onboarding/dsl-api-reference.md b/docs/onboarding/dsl-api-reference.md new file mode 100644 index 000000000..68b2171cf --- /dev/null +++ b/docs/onboarding/dsl-api-reference.md @@ -0,0 +1,592 @@ +# DSL and API Reference + +This document provides a quick reference for the QUA language, common APIs, utility functions, and parameter naming conventions used in the calibration codebase. + +## QUA Language Quick Reference + +QUA (Quantum Machines Assembly) is a domain-specific language for programming quantum control sequences. It's embedded in Python using the `qm.qua` module. + +### Basic Syntax + +```python +from qm.qua import * + +# QUA programs are created in a context manager +with program() as my_program: + # QUA code here + pass +``` + +### Variable Declaration + +```python +# Integer variable +n = declare(int) + +# Fixed-point variable (for floating-point values) +amp = declare(fixed) + +# Stream for data output +I_st = declare_stream() + +# ADC trace stream +adc_st = declare_stream(adc_trace=True) +``` + +### Control Flow + +```python +# For loop +with for_(n, 0, n < 10, n + 1): + # Loop body + pass + +# While loop +with while_(condition): + # Loop body + pass + +# If statement +with if_(condition): + # If body + pass + +# If-else +with if_(condition): + # If body + with else_(): + # Else body + pass +``` + +### Pulse Operations + +```python +# Play a pulse +qubit.xy.play("x180") +qubit.xy.play("x90", amplitude_scale=0.5) + +# Play with custom parameters +qubit.resonator.measure("readout", qua_vars=(I, Q), amplitude_scale=amp) + +# Wait +qubit.wait(250) # nanoseconds +wait(100, qubit.name) # wait on specific element + +# Align (synchronize all elements) +align() +align(qubit1.name, qubit2.name) # align specific elements +``` + +### Measurement + +```python +# Standard I/Q measurement +qubit.resonator.measure("readout", qua_vars=(I, Q)) + +# Raw ADC trace +qubit.resonator.measure("readout", stream=adc_st) + +# Save measurement results +save(I, I_st) +save(Q, Q_st) +``` + +### Stream Processing + +```python +with stream_processing(): + # Save single value + n_st.save("n") + + # Buffer and average + I_st.buffer(100).average().save("I_avg") + + # Multi-dimensional buffering + I_st.buffer(10).buffer(20).average().save("I_2d") + + # Save last value only + I_st.save("I_last") + + # ADC trace processing + adc_st.input1().average().save("adcI") + adc_st.input2().average().save("adcQ") +``` + +### Units + +```python +from qualang_tools.units import unit + +u = unit(coerce_to_integer=True) + +# Time conversions +duration = 250 * u.ns +duration = 0.25 * u.us +duration = 0.00025 * u.ms + +# Frequency conversions +freq = 100 * u.MHz +freq = 0.1 * u.GHz +``` + +### Common QUA Patterns + +#### Sweep Loop + +```python +from qualang_tools.loops import from_array + +sweep_values = np.linspace(-1, 1, 100) +amp = declare(fixed) + +with for_(*from_array(amp, sweep_values)): + qubit.xy.play("x180", amplitude_scale=amp) + qubit.resonator.measure("readout", qua_vars=(I, Q)) + save(I, I_st) + save(Q, Q_st) +``` + +#### Averaging Loop + +```python +n = declare(int) +n_st = declare_stream() + +with for_(n, 0, n < num_shots, n + 1): + save(n, n_st) + qubit.xy.play("x180") + qubit.resonator.measure("readout", qua_vars=(I, Q)) + save(I, I_st) + save(Q, Q_st) +``` + +#### Phase Reset + +```python +reset_phase(qubit.resonator.name) # Reset digital oscillator phase +``` + +## QualibrationNode API + +### Node Initialization + +```python +from qualibrate import QualibrationNode +from quam_config import Quam +from calibration_utils.my_calibration import Parameters + +node = QualibrationNode[Parameters, Quam]( + name="my_calibration", + description="Description of calibration", + parameters=Parameters(), +) +``` + +### Node Properties + +```python +# Access parameters +node.parameters.num_shots +node.parameters.qubits + +# Access machine state +node.machine # Quam instance +node.machine.qubits["q1"] # Access qubit +node.machine.connect() # Connect to QOP + +# Namespace for temporary data +node.namespace["qubits"] = qubits +node.namespace["qua_program"] = program +node.namespace["job"] = job + +# Results storage +node.results["ds_raw"] = dataset +node.results["ds_fit"] = fit_dataset +node.results["fit_results"] = fit_dict +node.results["figures"] = {"main": fig} + +# Outcomes tracking +node.outcomes = {"q1": "successful", "q2": "failed"} + +# Logging +node.log("Message") +node.log(job.execution_report()) +``` + +### Run Actions + +```python +@node.run_action() +def my_action(node: QualibrationNode[Parameters, Quam]): + """Action that always runs.""" + pass + +@node.run_action(skip_if=node.parameters.simulate) +def hardware_action(node: QualibrationNode[Parameters, Quam]): + """Action skipped during simulation.""" + pass + +@node.run_action(skip_if=node.modes.external) +def debug_action(node: QualibrationNode[Parameters, Quam]): + """Action only runs in local mode.""" + pass +``` + +### State Management + +```python +# Record state updates +with node.record_state_updates(): + q.resonator.frequency = new_frequency + q.xy.operations["x180"].amplitude = new_amplitude + +# Save node (saves results and state) +node.save() + +# Load from previous run +node.load_from_id(data_id) +``` + +## Qualibration Libraries API + +### Qubit Management + +```python +from qualibration_libs.parameters import get_qubits + +# Get qubits from node parameters +qubits = get_qubits(node) + +# Get qubit names +names = qubits.get_names() # ["q1", "q2", ...] + +# Batch for multiplexed readout +for multiplexed_qubits in qubits.batch(): + for i, qubit in multiplexed_qubits.items(): + # Access by index for measurements + pass +``` + +### Data Fetching + +```python +from qualibration_libs.data import XarrayDataFetcher + +# Create data fetcher +data_fetcher = XarrayDataFetcher(job, node.namespace["sweep_axes"]) + +# Iterate over datasets +for dataset in data_fetcher: + # Process dataset + progress_counter( + data_fetcher.get("n", 0), + node.parameters.num_shots, + start_time=data_fetcher.t_start, + ) +``` + +### Runtime Utilities + +```python +from qualibration_libs.runtime import simulate_and_plot + +# Simulate QUA program +samples, fig, wf_report = simulate_and_plot( + qmm, config, qua_program, node.parameters +) +``` + +### State Updates + +```python +from qualibration_libs.core import tracked_updates + +# Temporary state updates with auto-revert +with tracked_updates(resonator, auto_revert=True) as resonator: + resonator.time_of_flight = new_tof + # Use modified resonator +# Changes reverted automatically + +# Manual revert +with tracked_updates(resonator, auto_revert=False) as resonator: + resonator.time_of_flight = new_tof + tracked_resonator = resonator +# Later... +tracked_resonator.revert_changes() +``` + +## Calibration Utilities API + +Each calibration utility module typically exports: + +### Parameters + +```python +from calibration_utils.time_of_flight import Parameters + +params = Parameters() +params.num_shots = 100 +params.time_of_flight_in_ns = 28 +``` + +### Analysis Functions + +```python +from calibration_utils.time_of_flight import ( + process_raw_dataset, + fit_raw_data, + log_fitted_results, +) + +# Process raw data +ds_processed = process_raw_dataset(ds_raw, node) + +# Fit data +ds_fit, fit_results = fit_raw_data(ds_processed, node) + +# Log results +log_fitted_results(fit_results, log_callable=node.log) +``` + +### Plotting Functions + +```python +from calibration_utils.time_of_flight import ( + plot_single_run_with_fit, + plot_averaged_run_with_fit, +) + +fig1 = plot_single_run_with_fit(ds_raw, qubits, ds_fit) +fig2 = plot_averaged_run_with_fit(ds_raw, qubits, ds_fit) +``` + +## Quam (QUA-M) API + +### Loading State + +```python +from quam_config import Quam + +# Load from default location +machine = Quam.load() + +# Load from specific file +machine = Quam.load("path/to/state.json") +``` + +### Accessing Qubits + +```python +# Get qubit +qubit = machine.qubits["q1"] + +# Access properties +qubit.xy.frequency # Qubit frequency +qubit.xy.operations["x180"].amplitude # π-pulse amplitude +qubit.resonator.frequency # Readout frequency +qubit.resonator.time_of_flight # Time of flight +qubit.T1 # T1 relaxation time +qubit.T2 # T2* dephasing time +``` + +### QPU Initialization + +```python +# Initialize single qubit +machine.initialize_qpu(target=qubit) + +# Initialize all qubits in batch +for qubit in qubits: + machine.initialize_qpu(target=qubit) +``` + +### Configuration Generation + +```python +# Connect to QOP +qmm = machine.connect() + +# Generate config +config = machine.generate_config() +``` + +### Declaring QUA Variables + +```python +# Standard I/Q variables for multiplexed readout +I, I_st, Q, Q_st, n, n_st = machine.declare_qua_variables() +``` + +## Qualang Tools API + +### Loops + +```python +from qualang_tools.loops import from_array + +# Create sweep loop +sweep_values = np.linspace(-1, 1, 100) +amp = declare(fixed) + +with for_(*from_array(amp, sweep_values)): + # Loop body + pass +``` + +### Multi-User Session + +```python +from qualang_tools.multi_user import qm_session + +# Safe execution with timeout +with qm_session(qmm, config, timeout=60) as qm: + job = qm.execute(qua_program) + # Job executes here +# Session closes automatically +``` + +### Progress Counter + +```python +from qualang_tools.results import progress_counter + +# Show progress during execution +progress_counter( + current_value, + total_value, + start_time=start_time, +) +``` + +### Units + +```python +from qualang_tools.units import unit + +u = unit(coerce_to_integer=True) + +# Time +250 * u.ns +0.25 * u.us +0.00025 * u.ms + +# Frequency +100 * u.MHz +0.1 * u.GHz +``` + +## Parameter Naming Conventions + +### Node Parameters + +- **Naming**: Use descriptive names with units in suffix +- **Examples**: + - `num_shots`: Number of averages + - `time_of_flight_in_ns`: Time in nanoseconds + - `readout_amplitude_in_v`: Amplitude in volts + - `intermediate_frequency_range`: Frequency range tuple + - `amp_scaling_range`: Amplitude scaling range tuple + +### State Parameters + +- **Qubit frequency**: `qubit.xy.frequency` (Hz) +- **Resonator frequency**: `qubit.resonator.frequency` (Hz) +- **Intermediate frequency**: `qubit.xy.intermediate_frequency` (Hz) +- **Pulse amplitude**: `qubit.xy.operations["x180"].amplitude` +- **Pulse length**: `qubit.xy.operations["x180"].length` (ns) +- **Time of flight**: `qubit.resonator.time_of_flight` (ns) +- **T1/T2**: `qubit.T1`, `qubit.T2` (ns) + +### Dataset Coordinates + +- **Qubit dimension**: `"qubit"` (qubit names) +- **Sweep dimensions**: Descriptive names (e.g., `"amplitude"`, `"frequency"`) +- **Time dimensions**: `"readout_time"`, `"evolution_time"` + +### Dataset Variables + +- **I/Q data**: `"I1"`, `"Q1"`, `"I2"`, `"Q2"`, etc. +- **ADC traces**: `"adcI1"`, `"adcQ1"`, etc. +- **Fitted data**: `"fitted_curve"`, `"fit_params"`, etc. + +## Common Utility Functions + +### Data Processing + +```python +import numpy as np +import xarray as xr + +# Convert ADC counts to volts +ds = ds.assign({key: -ds[key] / 2**12 for key in ("adcI", "adcQ")}) + +# Calculate IQ amplitude +ds = ds.assign({"IQ_abs": np.sqrt(ds["adcI"] ** 2 + ds["adcQ"] ** 2)}) + +# Calculate phase +ds = ds.assign({"IQ_phase": np.arctan2(ds["adcQ"], ds["adcI"])}) +``` + +### Fitting + +```python +from lmfit import Model +from scipy.optimize import curve_fit + +# Using lmfit +model = Model(my_model_function) +result = model.fit(data, x=x_data, param1=initial_value) +fitted_value = result.params["param1"].value + +# Using scipy +popt, pcov = curve_fit(my_model_function, x_data, y_data) +fitted_value = popt[0] +``` + +### Plotting + +```python +import matplotlib.pyplot as plt +import plotly.graph_objects as go + +# Matplotlib +fig, ax = plt.subplots() +ax.plot(x, y) +ax.set_xlabel("X Label") +ax.set_ylabel("Y Label") +plt.show() + +# Plotly +fig = go.Figure() +fig.add_trace(go.Scatter(x=x, y=y)) +fig.update_layout(xaxis_title="X Label", yaxis_title="Y Label") +fig.show() +``` + +## Telemetry API (Optional) + +The telemetry module provides observability but is not currently used in calibration nodes. For reference: + +```python +from telemetry import init_telemetry, trace_function, trace_span + +# Initialize (typically in main script) +init_telemetry() + +# Function decorator +@trace_function("my_function", attributes={"component": "calibration"}) +def my_function(): + pass + +# Context manager +with trace_span("my_operation", attributes={"qubit": "q1"}): + # Operation code + pass +``` + +## Next Steps + +- Review [Code Patterns](code-patterns.md) for usage examples +- Study [Example Walkthrough](example-walkthrough.md) for complete examples +- Explore [Calibration Procedures](calibration-procedures.md) for physics context diff --git a/docs/onboarding/example-walkthrough.md b/docs/onboarding/example-walkthrough.md new file mode 100644 index 000000000..1014f5c87 --- /dev/null +++ b/docs/onboarding/example-walkthrough.md @@ -0,0 +1,413 @@ +# Example Walkthrough: Time of Flight Calibration + +This document provides a detailed, line-by-line walkthrough of the Time of Flight calibration to help you understand how calibrations work in practice. + +## Overview + +The Time of Flight calibration (`01a_time_of_flight.py`) is the first real calibration in the workflow. It measures the delay between sending a readout pulse and when the signal arrives at the ADC, and also calibrates I/Q channel offsets. + +## File Structure + +```python +# File: calibrations/1Q_calibrations/01a_time_of_flight.py +``` + +## Line-by-Line Explanation + +### Section 1: Imports + +```python +# %% {Imports} +from dataclasses import asdict +import matplotlib.pyplot as plt +import numpy as np +import xarray as xr +from qm.qua import * +from qualang_tools.multi_user import qm_session +from qualang_tools.results import progress_counter +from qualang_tools.units import unit +from qualibrate import QualibrationNode +from qualibration_libs.core import tracked_updates +from qualibration_libs.data import XarrayDataFetcher +from qualibration_libs.parameters import get_qubits +from qualibration_libs.runtime import simulate_and_plot +from quam_config import Quam +from calibration_utils.time_of_flight import ( + Parameters, + process_raw_dataset, + fit_raw_data, + log_fitted_results, + plot_single_run_with_fit, + plot_averaged_run_with_fit, +) +``` + +**Explanation**: +- Standard Python libraries for data handling (numpy, xarray, matplotlib) +- QUA language import (`from qm.qua import *`) - gives access to QUA constructs +- Qualibration framework imports (QualibrationNode, utilities) +- Calibration-specific utilities from `calibration_utils.time_of_flight` + +### Section 2: Node Initialization + +```python +# %% {Node initialisation} +DESCRIPTION = """ + TIME OF FLIGHT - OPX+ & LF-FEM +This sequence involves sending a readout pulse and capturing the raw ADC traces. +... +""" + +node = QualibrationNode[Parameters, Quam]( + name="01a_time_of_flight", + description=DESCRIPTION, + parameters=Parameters(), +) +``` + +**Explanation**: +- Creates a `QualibrationNode` instance +- `[Parameters, Quam]` provides type hints for IDE support +- `name` must be unique and match the file name +- `description` is shown in the GUI and should explain what the calibration does +- `parameters=Parameters()` creates default parameters + +### Section 3: Custom Parameters (Debug) + +```python +# %% {Custom_param} +@node.run_action(skip_if=node.modes.external) +def custom_param(node: QualibrationNode[Parameters, Quam]): + """Override parameters for debugging when running locally.""" + # node.parameters.qubits = ["q1"] + pass +``` + +**Explanation**: +- `@node.run_action` decorator registers this as a run action +- `skip_if=node.modes.external` means this only runs in local mode (not GUI/graph) +- Use this to quickly modify parameters for testing without changing the actual parameters class +- Commented out by default + +### Section 4: Load Machine State + +```python +# Instantiate the QUAM class from the state file +node.machine = Quam.load() +``` + +**Explanation**: +- Loads the Quam state from the default location (usually `quam_config/state.json`) +- The state contains all hardware configuration, qubit parameters, etc. +- This must be done before creating the QUA program + +### Section 5: Create QUA Program + +```python +# %% {Create_QUA_program} +@node.run_action(skip_if=node.parameters.load_data_id is not None) +def create_qua_program(node: QualibrationNode[Parameters, Quam]): + """Create the sweep axes and generate the QUA program.""" + u = unit(coerce_to_integer=True) + node.namespace["qubits"] = qubits = get_qubits(node) + num_qubits = len(qubits) +``` + +**Explanation**: +- `skip_if=node.parameters.load_data_id is not None` skips if loading old data +- `unit()` helper for time conversions with integer coercion +- `get_qubits(node)` gets qubits from node parameters, handles batching +- Store qubits in namespace for later use + +```python + node.namespace["tracked_resonators"] = [] + for q in qubits: + resonator = q.resonator + with tracked_updates(resonator, auto_revert=False, dont_assign_to_none=True) as resonator: + resonator.time_of_flight = node.parameters.time_of_flight_in_ns + resonator.operations["readout"].length = node.parameters.readout_length_in_ns + resonator.operations["readout"].amplitude = node.parameters.readout_amplitude_in_v + node.namespace["tracked_resonators"].append(resonator) +``` + +**Explanation**: +- `tracked_updates` makes temporary changes that can be reverted +- Sets readout parameters from node parameters +- Stores tracked resonators to revert changes later + +```python + node.namespace["sweep_axes"] = { + "qubit": xr.DataArray(qubits.get_names()), + "readout_time": xr.DataArray( + np.arange(0, node.parameters.readout_length_in_ns, 1), + attrs={"long_name": "readout time", "units": "ns"}, + ), + } +``` + +**Explanation**: +- Defines sweep axes for the dataset +- `xr.DataArray` creates labeled arrays with metadata +- These axes will be used when fetching data from QUA + +```python + with program() as node.namespace["qua_program"]: + n = declare(int) + n_st = declare_stream() + adc_st = [declare_stream(adc_trace=True) for _ in range(num_qubits)] +``` + +**Explanation**: +- `with program()` creates a QUA program context +- `declare(int)` creates an integer variable for the averaging loop +- `declare_stream()` creates a stream for data output +- `adc_trace=True` creates a stream for raw ADC traces + +```python + for multiplexed_qubits in qubits.batch(): + align() + with for_(n, 0, n < node.parameters.num_shots, n + 1): + save(n, n_st) + for i, qubit in multiplexed_qubits.items(): + reset_phase(qubit.resonator.name) + qubit.resonator.measure("readout", stream=adc_st[i]) + qubit.resonator.wait(node.machine.depletion_time * u.ns) + align() +``` + +**Explanation**: +- `qubits.batch()` handles multiplexed readout automatically +- `align()` synchronizes all elements +- `for_()` creates a QUA for loop for averaging +- `reset_phase()` resets digital oscillator phase for averaging +- `measure()` sends readout pulse and captures ADC trace +- `wait()` allows resonator to deplete before next measurement + +```python + with stream_processing(): + n_st.save("n") + for i in range(num_qubits): + adc_st[i].input1().average().save(f"adcI{i + 1}") + adc_st[i].input2().average().save(f"adcQ{i + 1}") + adc_st[i].input1().save(f"adc_single_runI{i + 1}") + adc_st[i].input2().save(f"adc_single_runQ{i + 1}") +``` + +**Explanation**: +- `stream_processing()` defines how streams are processed +- `.input1()` and `.input2()` access the two ADC inputs (I and Q) +- `.average()` averages over all shots +- `.save()` saves to a named output +- Saves both averaged and single-run data + +### Section 6: Simulation (Optional) + +```python +# %% {Simulate} +@node.run_action(skip_if=node.parameters.load_data_id is not None or not node.parameters.simulate) +def simulate_qua_program(node: QualibrationNode[Parameters, Quam]): + """Connect to the QOP and simulate the QUA program""" + qmm = node.machine.connect() + config = node.machine.generate_config() + samples, fig, wf_report = simulate_and_plot(qmm, config, node.namespace["qua_program"], node.parameters) + node.results["simulation"] = {"figure": fig, "wf_report": wf_report, "samples": samples} +``` + +**Explanation**: +- Skips if loading data or simulation disabled +- Connects to QOP and generates config +- Simulates the program and generates waveform report +- Stores simulation results for inspection + +### Section 7: Execution + +```python +# %% {Execute} +@node.run_action(skip_if=node.parameters.load_data_id is not None or node.parameters.simulate) +def execute_qua_program(node: QualibrationNode[Parameters, Quam]): + """Execute the QUA program and fetch raw data.""" + qmm = node.machine.connect() + config = node.machine.generate_config() + + with qm_session(qmm, config, timeout=node.parameters.timeout) as qm: + node.namespace["job"] = job = qm.execute(node.namespace["qua_program"]) + data_fetcher = XarrayDataFetcher(job, node.namespace["sweep_axes"]) + + for dataset in data_fetcher: + progress_counter( + data_fetcher.get("n", 0), + node.parameters.num_shots, + start_time=data_fetcher.t_start, + ) + + node.log(job.execution_report()) + + node.results["ds_raw"] = dataset +``` + +**Explanation**: +- Skips if loading data or simulating +- `qm_session()` context manager ensures safe execution +- `execute()` runs the QUA program on hardware +- `XarrayDataFetcher` converts QUA streams to xarray datasets +- `progress_counter()` shows execution progress +- `execution_report()` logs any warnings or errors +- Stores raw dataset in results + +### Section 8: Load Data (Optional) + +```python +# %% {Load_historical_data} +@node.run_action(skip_if=node.parameters.load_data_id is None) +def load_data(node: QualibrationNode[Parameters, Quam]): + """Load a previously acquired dataset.""" + load_data_id = node.parameters.load_data_id + node.load_from_id(node.parameters.load_data_id) + node.parameters.load_data_id = load_data_id + node.namespace["qubits"] = get_qubits(node) +``` + +**Explanation**: +- Only runs if `load_data_id` is set +- Loads previous run's data and results +- Allows re-analyzing without re-running experiment + +### Section 9: Data Analysis + +```python +# %% {Analyse_data} +@node.run_action(skip_if=node.parameters.simulate) +def analyse_data(node: QualibrationNode[Parameters, Quam]): + """Analyse raw data into fit results.""" + node.results["ds_raw"] = process_raw_dataset(node.results["ds_raw"], node) + node.results["ds_fit"], fit_results = fit_raw_data(node.results["ds_raw"], node) + node.results["fit_results"] = {k: asdict(v) for k, v in fit_results.items()} + + log_fitted_results(node.results["fit_results"], log_callable=node.log) + node.outcomes = { + qubit_name: ("successful" if fit_result["success"] else "failed") + for qubit_name, fit_result in node.results["fit_results"].items() + } +``` + +**Explanation**: +- `process_raw_dataset()` converts ADC counts to volts, calculates IQ amplitude +- `fit_raw_data()` fits the data to extract time of flight and offsets +- Converts fit results to dictionaries for serialization +- Logs results and sets outcomes (success/failure per qubit) + +### Section 10: Plotting + +```python +# %% {Plot_data} +@node.run_action(skip_if=node.parameters.simulate) +def plot_data(node: QualibrationNode[Parameters, Quam]): + """Plot the raw and fitted data.""" + fig_single_run_fit = plot_single_run_with_fit( + node.results["ds_raw"], node.namespace["qubits"], node.results["ds_fit"] + ) + fig_averaged_run_fit = plot_averaged_run_with_fit( + node.results["ds_raw"], node.namespace["qubits"], node.results["ds_fit"] + ) + plt.show() + node.results["figures"] = { + "single_run": fig_single_run_fit, + "averaged_run": fig_averaged_run_fit, + } +``` + +**Explanation**: +- Uses utility functions for consistent plotting +- Creates plots for both single run and averaged data +- Stores figures in results for later access + +### Section 11: State Update + +```python +# %% {Update_state} +@node.run_action(skip_if=node.parameters.simulate) +def update_state(node: QualibrationNode[Parameters, Quam]): + """Update the relevant parameters if the qubit data analysis was successful.""" + + for tracked_resonator in node.namespace.get("tracked_resonators", []): + tracked_resonator.revert_changes() + + with node.record_state_updates(): + controllers_to_update = np.unique(node.results["ds_fit"].con.values).tolist() + for q in node.namespace["qubits"]: + if not node.results["fit_results"][q.name]["success"]: + continue + + fit_result = node.results["fit_results"][q.name] + if node.parameters.time_of_flight_in_ns is not None: + q.resonator.time_of_flight = node.parameters.time_of_flight_in_ns + fit_result["tof_to_add"] + else: + q.resonator.time_of_flight = fit_result["tof_to_add"] + # ... update offsets +``` + +**Explanation**: +- Reverts temporary changes made earlier +- `record_state_updates()` tracks all state changes +- Only updates state if fit was successful +- Updates time of flight and I/Q offsets based on fit results + +### Section 12: Save Results + +```python +# %% {Save_results} +@node.run_action() +def save_results(node: QualibrationNode[Parameters, Quam]): + """Persist node results to disk.""" + node.save() +``` + +**Explanation**: +- Always runs (no skip condition) +- Saves all results, state updates, and metadata to disk +- Results can be loaded later using `load_data_id` + +## Expected Output + +After running, you should see: + +1. **Execution progress**: Progress bar showing shot completion +2. **Execution report**: Any warnings or errors +3. **Fit results**: Logged time of flight and offsets for each qubit +4. **Plots**: Visualizations of raw data and fits +5. **Saved files**: Results saved to disk + +## Common Modifications + +### Change Number of Shots + +```python +# In custom_param function +node.parameters.num_shots = 200 # Increase averaging +``` + +### Change Readout Parameters + +```python +# In custom_param function +node.parameters.readout_amplitude_in_v = 0.05 # Increase amplitude +node.parameters.readout_length_in_ns = 2000 # Longer readout +``` + +### Test with Simulation + +```python +# In custom_param function +node.parameters.simulate = True # Run simulation instead of hardware +``` + +## Next Steps + +After understanding this example: + +1. **Run it yourself**: Execute the calibration and examine the output +2. **Modify parameters**: Try different values and see the effects +3. **Study other calibrations**: Compare with similar calibrations +4. **Create your own**: Use this as a template for new calibrations + +See [Code Patterns](code-patterns.md) for more examples and patterns. diff --git a/docs/onboarding/quick-reference.md b/docs/onboarding/quick-reference.md new file mode 100644 index 000000000..c5026a311 --- /dev/null +++ b/docs/onboarding/quick-reference.md @@ -0,0 +1,248 @@ +# Quick Reference Card + +A quick reference for common commands, file locations, and troubleshooting. + +## Common Commands + +### Environment Setup + +```bash +# Activate virtual environment +source .venv/bin/activate # Linux/Mac +.venv\Scripts\activate # Windows + +# Install/update dependencies +uv sync --group dev --prerelease=allow + +# Install pre-commit hooks +pre-commit install +pre-commit install --hook-type commit-msg +``` + +### Running Calibrations + +```bash +# Run a calibration node +python calibrations/1Q_calibrations/01a_time_of_flight.py + +# Run a calibration graph +python calibrations/1Q_calibrations/90_calibration_graph_bringup_fixed_frequency_transmon.py + +# Run with simulation +# Set simulate=True in custom_param function +``` + +### Code Quality + +```bash +# Format code +black . + +# Check linting +pylint + +# Type checking +mypy + +# Run pre-commit on all files +pre-commit run --all-files + +# Run pre-commit on staged files +pre-commit run +``` + +### Testing + +```bash +# Run all tests +pytest + +# Run specific test +pytest tests/test_imports.py + +# Run with coverage +pytest --cov --cov-report=html + +# Run in verbose mode +pytest -v +``` + +### Git Workflow + +```bash +# Create feature branch +git checkout -b feature/my-feature + +# Commit with conventional commits +git commit -m "feat: add new calibration" + +# Push branch +git push origin feature/my-feature +``` + +## File Locations + +### Calibration Nodes + +``` +calibrations/ +├── 1Q_calibrations/ # Single-qubit calibrations +│ ├── 00_hello_qua.py # Entry point +│ ├── 01a_time_of_flight.py # First calibration +│ └── ... +└── CZ_calibration_fixed_couplers/ # Two-qubit calibrations + └── ... +``` + +### Calibration Utilities + +``` +calibration_utils/ +├── time_of_flight/ +│ ├── parameters.py +│ ├── analysis.py +│ └── plotting.py +└── ... +``` + +### Configuration + +``` +qualibration_graphs/superconducting/ +├── pyproject.toml # Dependencies +├── .pre-commit-config.yaml # Pre-commit hooks +├── .pylintrc # Linting rules +└── workflow.md # Calibration workflow +``` + +## Key Functions + +### Node Initialization + +```python +node = QualibrationNode[Parameters, Quam]( + name="calibration_name", + description="Description", + parameters=Parameters(), +) +``` + +### QUA Program + +```python +with program() as node.namespace["qua_program"]: + I, I_st, Q, Q_st, n, n_st = node.machine.declare_qua_variables() + # ... QUA code +``` + +### Execution + +```python +qmm = node.machine.connect() +config = node.machine.generate_config() +with qm_session(qmm, config, timeout=node.parameters.timeout) as qm: + job = qm.execute(node.namespace["qua_program"]) +``` + +### State Update + +```python +with node.record_state_updates(): + q.resonator.frequency = new_frequency + q.xy.operations["x180"].amplitude = new_amplitude +``` + +## Parameter Ranges + +| Parameter | Typical Range | Units | +|-----------|---------------|-------| +| Qubit frequency | 3-7 | GHz | +| Resonator frequency | 5-8 | GHz | +| Readout amplitude | 0.01-0.1 | V | +| Drive amplitude (π) | 0.1-0.5 | normalized | +| Time of flight | 20-50 | ns | +| T1 | 10-100 | μs | +| T2* | 1-50 | μs | + +## Troubleshooting Checklist + +### Connection Issues + +- [ ] QOP is running and accessible +- [ ] Network connectivity working +- [ ] Credentials in Quam state correct +- [ ] Firewall not blocking connection + +### Calibration Fails + +- [ ] Prerequisites completed +- [ ] Parameter ranges reasonable +- [ ] Hardware connections secure +- [ ] Sufficient averaging (num_shots) +- [ ] Check execution report for errors + +### Code Quality Issues + +- [ ] Virtual environment activated +- [ ] Dependencies installed +- [ ] Pre-commit hooks installed +- [ ] Code formatted with Black +- [ ] Linting passes + +### Import Errors + +- [ ] Virtual environment activated +- [ ] Dependencies installed: `uv sync --group dev` +- [ ] In correct directory +- [ ] Python path correct + +## Common Patterns + +### Sweep Loop + +```python +from qualang_tools.loops import from_array +amp = declare(fixed) +with for_(*from_array(amp, sweep_values)): + qubit.xy.play("x180", amplitude_scale=amp) +``` + +### Averaging Loop + +```python +with for_(n, 0, n < node.parameters.num_shots, n + 1): + save(n, n_st) + # ... experiment +``` + +### Measurement + +```python +qubit.resonator.measure("readout", qua_vars=(I[i], Q[i])) +save(I[i], I_st[i]) +save(Q[i], Q_st[i]) +``` + +### Stream Processing + +```python +with stream_processing(): + I_st[i].buffer(len(sweep_values)).average().save(f"I{i + 1}") + Q_st[i].buffer(len(sweep_values)).average().save(f"Q{i + 1}") +``` + +## Useful Links + +- [Repository Map](repository-map.md) +- [Code Patterns](code-patterns.md) +- [DSL Reference](dsl-api-reference.md) +- [Calibration Workflow](calibration-workflow-detailed.md) +- [Contributing Guide](../../qualibration_graphs/superconducting/CONTRIBUTING.md) + +## Getting Help + +1. Check this quick reference +2. Review relevant documentation +3. Search GitHub issues +4. Ask in development channel +5. Tag maintainers in PR diff --git a/docs/onboarding/repository-map.md b/docs/onboarding/repository-map.md new file mode 100644 index 000000000..51fd10176 --- /dev/null +++ b/docs/onboarding/repository-map.md @@ -0,0 +1,318 @@ +# Repository Map + +This document provides a comprehensive overview of the repository structure, module organization, and key entry points for the qubit calibration codebase. + +## Directory Structure + +``` +CS_installations/ +├── .github/ +│ └── workflows/ +│ └── ci.yml # CI/CD pipeline configuration +├── Docker_installer_QUA/ # One-click installer for development environment +│ ├── build-installer.md +│ ├── qm-dev-installer.iss # Inno Setup installer script +│ └── payload/ # Installer payload files +│ ├── bootstrap-qm-devcontainer.ps1 +│ ├── install.ps1 +│ ├── resume.ps1 +│ └── template/ # DevContainer template +│ ├── .devcontainer/ +│ ├── notebooks/ +│ ├── README.md +│ ├── requirements.txt +│ └── src/ +├── qualibration_graphs/ # Main calibration codebase +│ └── superconducting/ # Superconducting qubit calibrations +│ ├── calibrations/ # Calibration node implementations +│ │ ├── 1Q_calibrations/ # Single-qubit calibration nodes (35 files) +│ │ │ ├── 00_hello_qua.py # Entry point: basic QUA connectivity test +│ │ │ ├── 01a_time_of_flight.py +│ │ │ ├── 01a_mixer_calibration.py +│ │ │ ├── 02a_resonator_spectroscopy.py +│ │ │ ├── 03a_qubit_spectroscopy.py +│ │ │ ├── 04a_rabi_chevron.py +│ │ │ ├── 04b_power_rabi.py +│ │ │ ├── 05_T1.py +│ │ │ ├── 06a_ramsey.py +│ │ │ ├── 06b_echo.py +│ │ │ ├── 07_iq_blobs.py +│ │ │ ├── 08a_readout_frequency_optimization.py +│ │ │ ├── 08b_readout_power_optimization.py +│ │ │ ├── 09_ramsey_vs_flux_calibration.py +│ │ │ ├── 10b_drag_calibration_180_minus_180.py +│ │ │ ├── 11a_single_qubit_randomized_benchmarking.py +│ │ │ ├── 11b_single_qubit_randomized_benchmarking_interleaved.py +│ │ │ ├── 12_Qubit_Spectroscopy_E_to_F.py +│ │ │ ├── 13_power_rabi_ef.py +│ │ │ ├── 14_gef_readout_frequency_optimization.py +│ │ │ ├── 15_iq_blobs_gef.py +│ │ │ ├── 16_xyz_delay.py +│ │ │ ├── 16b_xy_coupler_delay.py +│ │ │ ├── 17_pi_vs_flux_long_distortions.py +│ │ │ ├── 18_cryoscope.py +│ │ │ ├── 80_calibration_graph_bringup_flux_tunable_transmon.py +│ │ │ ├── 81_calibration_graph_retuning_flux_tunable_transmon.py +│ │ │ ├── 90_calibration_graph_bringup_fixed_frequency_transmon.py +│ │ │ └── 91_calibration_graph_retuning_fixed_frequency_transmon.py +│ │ └── CZ_calibration_fixed_couplers/ # Two-qubit CZ gate calibrations (9 files) +│ │ ├── 18a_coupler_zero_coarse.py +│ │ ├── 19_chevron_11_02.py +│ │ ├── 20_cz_conditional_phase.py +│ │ ├── 20b_cz_conditional_phase_error_amp.py +│ │ ├── 21_cz_phase_compensation.py +│ │ ├── 22_two_qubit_standard_rb.py +│ │ ├── 23_two_qubit_interleaved_cz_rb.py +│ │ ├── 24_Bell_State_Tomography.py +│ │ ├── 99_CZ_calibration_graph.py +│ │ └── README.md +│ ├── calibration_utils/ # Reusable calibration utilities (66 files) +│ │ ├── bell_state_tomography/ +│ │ ├── chevron_cz/ +│ │ ├── coupler_zero_point/ +│ │ ├── cryoscope/ +│ │ ├── cz_conditional_phase/ +│ │ ├── cz_conditional_phase_error_amp/ +│ │ ├── cz_phase_compensation/ +│ │ ├── drag_calibration_180_minus180/ +│ │ ├── hello_qua/ +│ │ ├── iq_blobs/ +│ │ ├── iq_blobs_ef/ +│ │ ├── mixer_calibration/ +│ │ ├── pi_flux/ +│ │ ├── power_rabi/ +│ │ ├── qubit_spectroscopy/ +│ │ ├── qubit_spectroscopy_vs_flux/ +│ │ ├── rabi_chevron/ +│ │ ├── ramsey/ +│ │ ├── ramsey_versus_flux_calibration/ +│ │ ├── readout_frequency_optimization/ +│ │ ├── readout_gef_frequency_optimization/ +│ │ ├── readout_optimization_3d/ +│ │ ├── readout_power_optimization/ +│ │ ├── resonator_spectroscopy/ +│ │ ├── resonator_spectroscopy_vs_amplitude/ +│ │ ├── resonator_spectroscopy_vs_flux/ +│ │ ├── single_qubit_randomized_benchmarking/ +│ │ ├── single_qubit_randomized_benchmarking_interleaved/ +│ │ ├── stark_detuning_calibration/ +│ │ ├── T1/ +│ │ ├── T2echo/ +│ │ ├── time_of_flight/ +│ │ ├── time_of_flight_mw/ +│ │ ├── two_qubit_interleaved_rb/ +│ │ └── xyx_delay/ +│ ├── quam_config/ # QUA-M configuration management +│ ├── tests/ # Test suite +│ │ ├── test_imports.py +│ │ └── test_sanity.py +│ ├── .pre-commit-config.yaml # Pre-commit hooks configuration +│ ├── .pylintrc # Pylint configuration +│ ├── CONTRIBUTING.md # Contribution guidelines +│ ├── README.md # Main README +│ ├── workflow.md # Calibration workflow diagram (34 steps) +│ ├── pyproject.toml # Project configuration and dependencies +│ └── uv.lock # Locked dependency versions +├── telemetry/ # Telemetry module (OpenTelemetry/Uptrace) +│ ├── __init__.py +│ ├── config.py # Telemetry configuration +│ ├── context_managers.py # Context managers for spans +│ ├── decorators.py # Function decorators for tracing +│ ├── utils.py # Utility functions +│ ├── telemetry_examples.py # Usage examples +│ ├── README.md # Telemetry documentation +│ └── TELEMETRY_EXAMPLES_EXPLAINED.md +└── README.md # Root README +``` + +## Key Entry Points + +### For New Contributors + +1. **`qualibration_graphs/superconducting/calibrations/1Q_calibrations/00_hello_qua.py`** + - Simplest calibration node + - Tests QOP connectivity + - Good starting point to understand node structure + +2. **`qualibration_graphs/superconducting/calibrations/1Q_calibrations/01a_time_of_flight.py`** + - First real calibration + - Demonstrates full calibration workflow + - Shows data analysis and state updates + +3. **`qualibration_graphs/superconducting/calibrations/1Q_calibrations/90_calibration_graph_bringup_fixed_frequency_transmon.py`** + - Complete calibration graph example + - Shows how multiple nodes are orchestrated + - Demonstrates dependency management + +### For Understanding Architecture + +1. **`qualibration_graphs/superconducting/workflow.md`** + - Visual workflow of all 34 calibration steps + - Shows dependencies between calibrations + +2. **`qualibration_graphs/superconducting/CONTRIBUTING.md`** + - Development setup instructions + - Code quality standards + - Contribution workflow + +3. **`qualibration_graphs/superconducting/pyproject.toml`** + - All dependencies and their versions + - Project configuration + - Build system settings + +## Module Organization + +### Calibration Nodes (`calibrations/`) + +Each calibration node follows a consistent structure: +- **Node initialization**: Creates `QualibrationNode` instance +- **Custom parameters**: Debug-only parameter overrides +- **QUA program creation**: Defines the quantum program +- **Simulation**: Optional simulation of the program +- **Execution**: Runs the program on hardware +- **Data analysis**: Processes raw data +- **Plotting**: Visualizes results +- **State update**: Updates system state +- **Results saving**: Persists data + +### Calibration Utilities (`calibration_utils/`) + +Each utility module typically contains: +- **`parameters.py`**: Parameter class definitions +- **`analysis.py`**: Data processing and fitting functions +- **`plotting.py`**: Visualization functions +- **`node.py`**: (Optional) Node-specific helper functions +- **`__init__.py`**: Module exports + +### Configuration (`quam_config/`) + +Contains QUA-M configuration management: +- System state definitions +- Hardware configuration +- Qubit and resonator definitions + +## Module Dependencies + +```mermaid +graph TB + CalibrationNodes[Calibration Nodes] --> CalibrationUtils[Calibration Utils] + CalibrationNodes --> Qualibrate[qualibrate] + CalibrationNodes --> QuamConfig[quam_config] + CalibrationNodes --> QualibrationLibs[qualibration-libs] + CalibrationUtils --> QualibrationLibs + CalibrationNodes --> QUA[qm-qua] + CalibrationNodes --> QualangTools[qualang-tools] + CalibrationNodes --> DataScience[numpy, scipy, xarray] + CalibrationNodes --> Visualization[plotly, matplotlib] + CalibrationNodes --> Fitting[lmfit, qiskit-experiments] +``` + +### Core Dependencies + +- **`qualibrate`**: Calibration framework and node system +- **`quam`**: Quantum Machine configuration management +- **`qm-qua`**: QUA language and QOP connection +- **`qualang-tools`**: Helper utilities for QUA programming +- **`qualibration-libs`**: Shared calibration libraries +- **`quam-builder`**: QUA-M builder utilities + +### Data Science Stack + +- **`numpy`**: Numerical computations +- **`scipy`**: Scientific computing +- **`xarray`**: Labeled multi-dimensional arrays +- **`lmfit`**: Non-linear least-squares fitting +- **`qiskit-experiments`**: Quantum experiment analysis + +### Visualization + +- **`plotly`**: Interactive plotting +- **`matplotlib`**: Static plotting + +## File Purpose Index + +### Configuration Files + +| File | Purpose | +|------|---------| +| `pyproject.toml` | Project metadata, dependencies, tool configuration | +| `uv.lock` | Locked dependency versions | +| `.pre-commit-config.yaml` | Pre-commit hooks (formatting, linting) | +| `.pylintrc` | Pylint code quality rules | +| `.github/workflows/ci.yml` | CI/CD pipeline | + +### Documentation Files + +| File | Purpose | +|------|---------| +| `README.md` | Main project documentation | +| `CONTRIBUTING.md` | Contribution guidelines | +| `workflow.md` | Calibration workflow visualization | +| `telemetry/README.md` | Telemetry module documentation | + +### Calibration Files + +| Pattern | Purpose | +|---------|---------| +| `00_*.py` | Initialization/test nodes | +| `01a_*.py` | First calibration steps (time of flight, mixer) | +| `02a_*.py` | Resonator spectroscopy | +| `03a_*.py` | Qubit spectroscopy | +| `04a_*.py`, `04b_*.py` | Rabi calibrations | +| `05_*.py` | T1 measurements | +| `06a_*.py`, `06b_*.py` | Ramsey and echo | +| `07_*.py` | IQ blob measurements | +| `08a_*.py`, `08b_*.py` | Readout optimization | +| `09_*.py` | Flux-dependent calibrations | +| `10*.py` | DRAG calibration | +| `11*.py` | Randomized benchmarking | +| `80_*.py`, `81_*.py` | Flux-tunable transmon graphs | +| `90_*.py`, `91_*.py` | Fixed-frequency transmon graphs | +| `99_*.py` | Complete calibration graphs | + +## Import Patterns + +### Standard Imports + +Most calibration nodes import: + +```python +# Core QUA and QM libraries +from qm.qua import * +from qualang_tools.loops import from_array +from qualang_tools.multi_user import qm_session +from qualang_tools.results import progress_counter +from qualang_tools.units import unit + +# Calibration framework +from qualibrate import QualibrationNode +from quam_config import Quam +from qualibration_libs.parameters import get_qubits +from qualibration_libs.runtime import simulate_and_plot +from qualibration_libs.data import XarrayDataFetcher + +# Data science +import numpy as np +import xarray as xr + +# Calibration-specific utilities +from calibration_utils. import Parameters, ... +``` + +## Statistics + +- **Total Python files**: ~195 files +- **Calibration nodes**: 44 files +- **Calibration utilities**: 66 files +- **Test files**: 2 files +- **Configuration files**: 5 files +- **Documentation files**: 4 files + +## Next Steps + +After understanding the repository structure: + +1. Read [Development Environment Setup](dev-environment.md) +2. Review [Code Patterns](code-patterns.md) +3. Study [Calibration Workflow](calibration-workflow-detailed.md) +4. Explore [DSL and API Reference](dsl-api-reference.md) diff --git a/qualibration_graphs/superconducting/CONTRIBUTING.md b/qualibration_graphs/superconducting/CONTRIBUTING.md index 704ef7818..e6edead99 100644 --- a/qualibration_graphs/superconducting/CONTRIBUTING.md +++ b/qualibration_graphs/superconducting/CONTRIBUTING.md @@ -2,6 +2,27 @@ Welcome! This repository contains tools for building and analyzing calibration graphs for superconducting qubits. This guide explains how to set up your development environment, how we use pre-commit hooks, how tests are structured, and what we expect from contributions. +## New to the Codebase? + +If you're new to this codebase, we recommend starting with the **Zero-to-Hero Onboarding Guide**: + +👉 **[Start Here: ONBOARDING.md](../../docs/ONBOARDING.md)** + +The onboarding guide will take you through: +- Setting up your development environment +- Understanding the codebase structure +- Making your first contribution +- Deep dive into calibration procedures +- Advanced topics and best practices + +### Quick Links + +- **[Repository Map](../../docs/onboarding/repository-map.md)**: Understand the codebase structure +- **[Development Environment Setup](../../docs/onboarding/dev-environment.md)**: Get your environment running +- **[Code Patterns](../../docs/onboarding/code-patterns.md)**: Learn coding conventions +- **[Example Walkthrough](../../docs/onboarding/example-walkthrough.md)**: Step-by-step code explanation +- **[Quick Reference](../../docs/onboarding/quick-reference.md)**: Common commands and troubleshooting + --- ## 1. Getting Started @@ -200,6 +221,58 @@ When you open the PR: - Indicate which test tiers you updated or verified. - Link any related GitHub Issues or design docs. +### PR Checklist + +Use this checklist when preparing your PR: + +- [ ] Code follows the patterns in [Code Patterns](../../docs/onboarding/code-patterns.md) +- [ ] All tests pass locally +- [ ] Pre-commit hooks pass +- [ ] Code is formatted with Black (120 char line length) +- [ ] Pylint checks pass +- [ ] Commit messages follow Conventional Commits +- [ ] PR description explains the change and impact +- [ ] Documentation updated if needed +- [ ] New calibration nodes include proper description and prerequisites + +### Common Contribution Patterns + +#### Adding a New Calibration Node + +1. **Copy a similar calibration** as a template (e.g., `01a_time_of_flight.py`) +2. **Create utility module** in `calibration_utils/` with: + - `parameters.py`: Parameter class + - `analysis.py`: Data processing and fitting + - `plotting.py`: Visualization functions + - `__init__.py`: Module exports +3. **Follow the standard structure** (see [Code Patterns](../../docs/onboarding/code-patterns.md)) +4. **Add to workflow.md** if it's part of the standard workflow +5. **Test thoroughly** with simulation and hardware + +#### Modifying an Existing Calibration + +1. **Understand the current implementation** by reading the code +2. **Check dependencies** - ensure your change doesn't break dependent calibrations +3. **Update documentation** if parameters or behavior change +4. **Test with existing data** using `load_data_id` if possible +5. **Verify state updates** are correct + +#### Fixing a Bug + +1. **Reproduce the issue** with a minimal example +2. **Identify the root cause** - check related calibrations +3. **Fix the issue** following existing patterns +4. **Add a test** to prevent regression +5. **Update documentation** if the fix changes behavior + +#### Improving Performance + +1. **Profile the code** to identify bottlenecks +2. **Optimize QUA programs** - use batching, minimize data transfer +3. **Vectorize analysis** - use NumPy/xarray operations +4. **Test that results are unchanged** - optimization shouldn't change physics +5. **Document performance improvements** in PR description + --- ## 6. Questions @@ -210,4 +283,28 @@ If you’re unsure about: - How to structure calibration or signal validation tests, - How to interpret pre-commit or CI errors, -please ask in the development channel or tag a maintainer in your PR. +please: + +1. Check the [Onboarding Guide](../../docs/ONBOARDING.md) and related documentation +2. Review [Code Patterns](../../docs/onboarding/code-patterns.md) for examples +3. Look at similar calibrations in the codebase +4. Ask in the development channel or tag a maintainer in your PR + +## 7. Code Review Checklist + +When reviewing PRs, check: + +- **Functionality**: Does the code work as intended? +- **Patterns**: Does it follow [Code Patterns](../../docs/onboarding/code-patterns.md)? +- **Tests**: Are there adequate tests? +- **Documentation**: Is the code well-documented? +- **State management**: Are state updates correct and safe? +- **Error handling**: Are errors handled appropriately? +- **Performance**: Is the code efficient? + +## 8. Additional Resources + +- **[Calibration Workflow](../../docs/onboarding/calibration-workflow-detailed.md)**: Complete 34-step workflow +- **[Calibration Procedures](../../docs/onboarding/calibration-procedures.md)**: Physics and implementation details +- **[DSL and API Reference](../../docs/onboarding/dsl-api-reference.md)**: QUA language and APIs +- **[Quick Reference](../../docs/onboarding/quick-reference.md)**: Common commands and troubleshooting