{ "cells": [ { "cell_type": "markdown", "id": "6cf13420", "metadata": {}, "source": [ "# Tutorial: Hardware Export - LabOne Q, RFSoC/QICK, and ARTIQ\n", "\n", "This tutorial shows how to export waveforms from `qiskit-waveform-sim`\n", "to various hardware control systems:\n", "\n", "1. **Zurich Instruments LabOne Q** - `OutputSimulator` compatible format\n", "2. **Xilinx RFSoC / QICK** - DAC-ready int16 waveforms\n", "3. **ARTIQ / Sinara** - DDS/URUKUL compatible format\n", "4. **Generic AWG** - CSV/HDF5 for arbitrary waveform generators" ] }, { "cell_type": "code", "execution_count": 1, "id": "7a51f089", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:24.361828Z", "iopub.status.busy": "2026-10-04T19:13:24.361564Z", "iopub.status.idle": "2026-10-04T19:13:25.223429Z", "shell.execute_reply": "2026-10-04T19:13:25.222767Z" } }, "outputs": [], "source": [ "import json\n", "\n", "import numpy as np\n", "from qiskit import QuantumCircuit\n", "from qiskit.providers.fake_provider import GenericBackendV2\n", "from qiskit.transpiler import generate_preset_pass_manager\n", "\n", "from qiskit_waveform_sim import TargetWaveformSimulator" ] }, { "cell_type": "markdown", "id": "0d1c037a", "metadata": {}, "source": [ "## Setup: Create a Test Circuit" ] }, { "cell_type": "code", "execution_count": 2, "id": "fb4decaa", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:25.225156Z", "iopub.status.busy": "2026-10-04T19:13:25.224946Z", "iopub.status.idle": "2026-10-04T19:13:25.872031Z", "shell.execute_reply": "2026-10-04T19:13:25.871443Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit duration: 16752 dt = 3.72 µs\n", "Sample rate: 4.50 GSa/s\n" ] } ], "source": [ "backend = GenericBackendV2(num_qubits=2, seed=42)\n", "\n", "qc = QuantumCircuit(2, 2)\n", "qc.h(0)\n", "qc.cx(0, 1)\n", "qc.rx(np.pi/4, 0)\n", "qc.rzz(np.pi/2, 0, 1)\n", "qc.measure([0, 1], [0, 1])\n", "\n", "pm = generate_preset_pass_manager(\n", " optimization_level=1, backend=backend, scheduling_method=\"alap\"\n", ")\n", "scheduled_qc = pm.run(qc)\n", "\n", "sim = TargetWaveformSimulator(backend.target).compile(scheduled_qc)\n", "\n", "total_dt = sim.total_duration_dt\n", "dt_sec = sim.dt\n", "print(f\"Circuit duration: {total_dt} dt = {total_dt * dt_sec * 1e6:.2f} µs\")\n", "print(f\"Sample rate: {1/dt_sec/1e9:.2f} GSa/s\")" ] }, { "cell_type": "markdown", "id": "8bc01b3b", "metadata": {}, "source": [ "## 1. Extract Full Waveforms" ] }, { "cell_type": "code", "execution_count": 3, "id": "7aee04ba", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:25.873547Z", "iopub.status.busy": "2026-10-04T19:13:25.873311Z", "iopub.status.idle": "2026-10-04T19:13:25.878803Z", "shell.execute_reply": "2026-10-04T19:13:25.878434Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "d0: 16752 samples, 8 events\n", "d1: 16752 samples, 1 events\n", "m0: 16752 samples, 1 events\n", "m1: 16752 samples, 1 events\n", "u(0,1): 16752 samples, 6 events\n" ] } ], "source": [ "# Extract all channels for full circuit duration\n", "waveforms = {}\n", "for ch in sim.get_channels():\n", " snip = sim.get_snippet(ch, start_dt=0, length_dt=total_dt)\n", " waveforms[ch] = {\n", " \"time_ns\": snip.time_ns,\n", " \"I\": snip.wave.real,\n", " \"Q\": snip.wave.imag,\n", " \"phase_rad\": snip.phase_rad,\n", " \"dt_sec\": snip.dt_sec,\n", " \"events\": [\n", " {\n", " \"op_name\": ev.op_name,\n", " \"start_dt\": ev.start_dt,\n", " \"duration_dt\": ev.duration_dt,\n", " \"shape\": ev.shape,\n", " \"amp\": complex(ev.amp),\n", " \"beta\": ev.beta,\n", " \"sigma_dt\": ev.sigma_dt,\n", " \"risefall_dt\": ev.risefall_dt,\n", " \"frame_phase_rad\": ev.frame_phase_rad,\n", " }\n", " for ev in snip.events\n", " ],\n", " }\n", " print(f\"{ch}: {len(snip.wave)} samples, {len(snip.events)} events\")" ] }, { "cell_type": "markdown", "id": "95e7e276", "metadata": {}, "source": [ "## 2. Export to LabOne Q Format" ] }, { "cell_type": "code", "execution_count": 4, "id": "6079e2e7", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:25.880001Z", "iopub.status.busy": "2026-10-04T19:13:25.879886Z", "iopub.status.idle": "2026-10-04T19:13:26.229534Z", "shell.execute_reply": "2026-10-04T19:13:26.229035Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exported LabOne Q format to waveforms_laboneq.json\n" ] } ], "source": [ "def export_to_laboneq(waveforms, dt_sec, filename=\"waveforms_laboneq.json\"):\n", " \"\"\"\n", " Export waveforms in a format compatible with LabOne Q's waveform\n", " upload. LabOne Q expects waveforms as complex arrays with\n", " associated sampling rate.\n", " \"\"\"\n", " export_data = {\n", " \"sampling_rate_hz\": 1.0 / dt_sec,\n", " \"waveforms\": {},\n", " }\n", "\n", " for ch, data in waveforms.items():\n", " # LabOne Q uses complex64 for waveform data\n", " wave_complex = data[\"I\"] + 1j * data[\"Q\"]\n", "\n", " export_data[\"waveforms\"][ch] = {\n", " \"samples\": [[float(c.real), float(c.imag)] for c in wave_complex.astype(np.complex64).tolist()],\n", " \"length\": len(wave_complex),\n", " \"markers\": np.zeros(len(wave_complex), dtype=np.uint8).tolist(),\n", " }\n", "\n", " with open(filename, 'w') as f:\n", " json.dump(export_data, f, indent=2)\n", "\n", " print(f\"Exported LabOne Q format to {filename}\")\n", " return export_data\n", "\n", "# Note: For actual LabOne Q integration, use their Python API:\n", "# from zhinst.toolkit import Session\n", "# session = Session(\"localhost\") # or device IP\n", "# device = session.connect_device(\"DEV1234\")\n", "# awg = device.awgs[0]\n", "# awg.upload_waveform(\"wave_d0\", waveform_data)\n", "\n", "laboneq_data = export_to_laboneq(waveforms, dt_sec)" ] }, { "cell_type": "markdown", "id": "f567ac73", "metadata": {}, "source": [ "## 3. Export to RFSoC / QICK Format" ] }, { "cell_type": "code", "execution_count": 5, "id": "11dbbb19", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:26.231517Z", "iopub.status.busy": "2026-10-04T19:13:26.231286Z", "iopub.status.idle": "2026-10-04T19:13:26.238484Z", "shell.execute_reply": "2026-10-04T19:13:26.237962Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exported RFSoC/QICK format to waveforms_rfsoc.npz\n" ] } ], "source": [ "def export_to_rfsoc_qick(waveforms, dt_sec, filename=\"waveforms_rfsoc.npz\"):\n", " \"\"\"\n", " Export waveforms for Xilinx RFSoC running QICK firmware.\n", " QICK expects int16 DAC values (signed 16-bit) for I and Q.\n", " \"\"\"\n", " # RFSoC DAC typically 14-bit effective, scale to int16 range\n", " DAC_MAX = 32767 # 2^15 - 1\n", "\n", " export_data = {\n", " \"sampling_rate_hz\": 1.0 / dt_sec,\n", " \"channels\": {},\n", " }\n", "\n", " for ch, data in waveforms.items():\n", " # Scale to DAC range (assuming input normalized to [-1, 1])\n", " I_dac = np.clip(np.round(data[\"I\"] * DAC_MAX), -DAC_MAX, DAC_MAX).astype(np.int16)\n", " Q_dac = np.clip(np.round(data[\"Q\"] * DAC_MAX), -DAC_MAX, DAC_MAX).astype(np.int16)\n", "\n", " export_data[\"channels\"][ch] = {\n", " \"I\": I_dac,\n", " \"Q\": Q_dac,\n", " \"length\": len(I_dac),\n", " }\n", "\n", " np.savez_compressed(filename, **export_data)\n", "\n", " print(f\"Exported RFSoC/QICK format to {filename}\")\n", " return export_data\n", "\n", "# For actual QICK usage:\n", "# from qick import QickConfig\n", "# soc = QickConfig(\"config.json\")\n", "# soc.load_waveforms(waveforms_dict)\n", "\n", "rfsoc_data = export_to_rfsoc_qick(waveforms, dt_sec)" ] }, { "cell_type": "markdown", "id": "28c44365", "metadata": {}, "source": [ "## 4. Export to ARTIQ / Sinara Format" ] }, { "cell_type": "code", "execution_count": 6, "id": "77f0c5b4", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:26.240033Z", "iopub.status.busy": "2026-10-04T19:13:26.239875Z", "iopub.status.idle": "2026-10-04T19:13:26.243475Z", "shell.execute_reply": "2026-10-04T19:13:26.242986Z" } }, "outputs": [], "source": [ "def export_to_artiq(waveforms, dt_sec, filename=\"waveforms_artiq.h5\"):\n", " \"\"\"\n", " Export waveforms for ARTIQ/Sinara DDS/URUKUL systems.\n", " Uses HDF5 format with datasets for each channel.\n", " Requires h5py: pip install h5py\n", " \"\"\"\n", " try:\n", " import h5py\n", " except ImportError:\n", " print(\"h5py not installed, skipping ARTIQ export\")\n", " return None\n", "\n", " with h5py.File(filename, 'w') as f:\n", " f.attrs['sampling_rate_hz'] = 1.0 / dt_sec\n", " f.attrs['dt_sec'] = dt_sec\n", "\n", " for ch, data in waveforms.items():\n", " grp = f.create_group(ch)\n", " grp.create_dataset('I', data=data[\"I\"].astype(np.float32))\n", " grp.create_dataset('Q', data=data[\"Q\"].astype(np.float32))\n", " grp.create_dataset('phase_rad', data=data[\"phase_rad\"].astype(np.float32))\n", " grp.create_dataset('time_ns', data=data[\"time_ns\"].astype(np.float64))\n", "\n", " # Store event metadata as attributes\n", " for i, ev in enumerate(data[\"events\"]):\n", " ev_grp = grp.create_group(f\"event_{i}\")\n", " for k, v in ev.items():\n", " if isinstance(v, complex):\n", " ev_grp.attrs[f\"{k}_real\"] = v.real\n", " ev_grp.attrs[f\"{k}_imag\"] = v.imag\n", " else:\n", " ev_grp.attrs[k] = v\n", "\n", " print(f\"Exported ARTIQ format to {filename}\")\n", " return filename\n", "\n", "# artiq_file = export_to_artiq(waveforms, dt_sec)" ] }, { "cell_type": "markdown", "id": "717bb659", "metadata": {}, "source": [ "## 5. Export to Generic CSV/HDF5 for AWGs" ] }, { "cell_type": "code", "execution_count": 7, "id": "33e208c2", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:26.244796Z", "iopub.status.busy": "2026-10-04T19:13:26.244678Z", "iopub.status.idle": "2026-10-04T19:13:26.450433Z", "shell.execute_reply": "2026-10-04T19:13:26.449846Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exported d0 to waveform_d0.csv (16752 rows)\n", "Exported d1 to waveform_d1.csv (16752 rows)\n", "Exported m0 to waveform_m0.csv (16752 rows)\n", "Exported m1 to waveform_m1.csv (16752 rows)\n", "Exported u(0,1) to waveform_u(0,1).csv (16752 rows)\n" ] } ], "source": [ "def export_to_csv(waveforms, dt_sec, prefix=\"waveform\"):\n", " \"\"\"Export each channel to CSV for generic AWG import.\"\"\"\n", " for ch, data in waveforms.items():\n", " filename = f\"{prefix}_{ch}.csv\"\n", " # Stack time, I, Q, phase\n", " csv_data = np.column_stack([\n", " data[\"time_ns\"],\n", " data[\"I\"],\n", " data[\"Q\"],\n", " data[\"phase_rad\"],\n", " ])\n", " header = \"time_ns,I,Q,phase_rad\"\n", " np.savetxt(filename, csv_data, delimiter=\",\", header=header, comments='')\n", " print(f\"Exported {ch} to {filename} ({len(csv_data)} rows)\")\n", "\n", "export_to_csv(waveforms, dt_sec)" ] }, { "cell_type": "code", "execution_count": 8, "id": "aeb7b372", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:26.451972Z", "iopub.status.busy": "2026-10-04T19:13:26.451792Z", "iopub.status.idle": "2026-10-04T19:13:26.939335Z", "shell.execute_reply": "2026-10-04T19:13:26.938877Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exported HDF5 to waveforms.h5\n" ] } ], "source": [ "def export_to_hdf5(waveforms, dt_sec, filename=\"waveforms.h5\"):\n", " \"\"\"Export all channels to single HDF5 file.\"\"\"\n", " try:\n", " import h5py\n", " except ImportError:\n", " print(\"h5py not installed, skipping HDF5 export\")\n", " return\n", "\n", " with h5py.File(filename, 'w') as f:\n", " f.attrs['sampling_rate_hz'] = 1.0 / dt_sec\n", " f.attrs['dt_sec'] = dt_sec\n", " f.attrs['total_duration_ns'] = total_dt * dt_sec * 1e9\n", "\n", " for ch, data in waveforms.items():\n", " grp = f.create_group(ch)\n", " grp.create_dataset('I', data=data[\"I\"].astype(np.float32), compression='gzip')\n", " grp.create_dataset('Q', data=data[\"Q\"].astype(np.float32), compression='gzip')\n", " grp.create_dataset('phase_rad', data=data[\"phase_rad\"].astype(np.float32), compression='gzip')\n", " grp.create_dataset('time_ns', data=data[\"time_ns\"].astype(np.float64), compression='gzip')\n", "\n", " print(f\"Exported HDF5 to {filename}\")\n", "\n", "export_to_hdf5(waveforms, dt_sec)" ] }, { "cell_type": "markdown", "id": "fbe5bbfe", "metadata": {}, "source": [ "## 6. Parameterized Pulse Templates for Calibration" ] }, { "cell_type": "code", "execution_count": 9, "id": "883f4d7e", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:26.941165Z", "iopub.status.busy": "2026-10-04T19:13:26.940847Z", "iopub.status.idle": "2026-10-04T19:13:26.945896Z", "shell.execute_reply": "2026-10-04T19:13:26.945514Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exported pulse templates to pulse_templates.json\n" ] } ], "source": [ "# For calibration workflows, export parametric pulse templates\n", "# that can be scaled at runtime\n", "\n", "def export_pulse_templates(sim, filename=\"pulse_templates.json\"):\n", " \"\"\"\n", " Export pulse templates with parametric formulas instead of\n", " pre-computed samples. Hardware can evaluate these at runtime.\n", " \"\"\"\n", " templates = {}\n", "\n", " for ch in sim.get_channels():\n", " events = sim.events_by_channel.get(ch, [])\n", " channel_templates = []\n", "\n", " for ev in events:\n", " if ev.duration_dt == 0:\n", " continue\n", "\n", " template = {\n", " \"name\": ev.op_name,\n", " \"channel\": ch,\n", " \"start_dt\": ev.start_dt,\n", " \"duration_dt\": ev.duration_dt,\n", " \"shape\": ev.shape,\n", " \"parameters\": {\n", " \"amplitude\": float(abs(ev.amp)),\n", " \"phase_rad\": float(np.angle(ev.amp)),\n", " \"frame_phase_rad\": ev.frame_phase_rad,\n", " },\n", " }\n", "\n", " if ev.shape == \"drag\":\n", " template[\"parameters\"].update({\n", " \"beta\": ev.beta,\n", " \"sigma_dt\": ev.sigma_dt,\n", " })\n", " elif ev.shape == \"gaussian_square\":\n", " template[\"parameters\"].update({\n", " \"risefall_dt\": ev.risefall_dt,\n", " })\n", "\n", " # channel_templates.append(template)\n", " channel_templates.append(template)\n", "\n", " templates[ch] = channel_templates\n", "\n", " with open(filename, 'w') as f:\n", " json.dump(templates, f, indent=2)\n", "\n", " print(f\"Exported pulse templates to {filename}\")\n", " return templates\n", "\n", "templates = export_pulse_templates(sim)" ] }, { "cell_type": "markdown", "id": "272c5c9f", "metadata": {}, "source": [ "## 7. Verify Exported Waveforms" ] }, { "cell_type": "code", "execution_count": 10, "id": "a974dc13", "metadata": { "execution": { "iopub.execute_input": "2026-10-04T19:13:26.947500Z", "iopub.status.busy": "2026-10-04T19:13:26.947298Z", "iopub.status.idle": "2026-10-04T19:13:26.953765Z", "shell.execute_reply": "2026-10-04T19:13:26.953277Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "waveforms_laboneq.json: 5861.7 KB\n", "waveforms_rfsoc.npz: 4.3 KB\n", "\n", "RFSoC loaded keys: ['sampling_rate_hz', 'channels']\n", " channels: \n" ] } ], "source": [ "# Quick verification: check exported files exist and have correct structure\n", "import os\n", "\n", "for f in [\"waveforms_laboneq.json\", \"waveforms_rfsoc.npz\"]:\n", " if os.path.exists(f):\n", " size = os.path.getsize(f)\n", " print(f\"{f}: {size/1024:.1f} KB\")\n", "\n", "# Load and verify RFSoC format\n", "rfsoc_loaded = np.load(\"waveforms_rfsoc.npz\", allow_pickle=True)\n", "print(f\"\\nRFSoC loaded keys: {list(rfsoc_loaded.keys())}\")\n", "for k in rfsoc_loaded.keys():\n", " if k != \"sampling_rate_hz\":\n", " print(f\" {k}: {type(rfsoc_loaded[k])}\")" ] }, { "cell_type": "markdown", "id": "a4a0755b", "metadata": {}, "source": [ "## Summary\n", "\n", "| Target Platform | Format | Key Function |\n", "|----------------|--------|--------------|\n", "| LabOne Q | JSON (complex64) | `export_to_laboneq()` |\n", "| RFSoC/QICK | NPZ (int16 I/Q) | `export_to_rfsoc_qick()` |\n", "| ARTIQ/Sinara | HDF5 (float32) | `export_to_artiq()` |\n", "| Generic AWG | CSV / HDF5 | `export_to_csv()` / `export_to_hdf5()` |\n", "| Calibration | JSON (parametric) | `export_pulse_templates()` |\n", "\n", "All export functions are provided as examples - adapt them to your\n", "specific hardware API requirements. The core `TargetWaveformSimulator`\n", "provides the exact I/Q samples with correct frame tracking that\n", "any hardware platform needs." ] } ], "metadata": { "kernelspec": { "display_name": "cwq", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.0" } }, "nbformat": 4, "nbformat_minor": 5 }