{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "711c35fa",
   "metadata": {},
   "source": [
    "# How to run coherence measurements with Boulder Opal\n",
    "\n",
    "**Measure T1 energy relaxation, T2 Ramsey dephasing, and T2 echo coherence times on a single transmon**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "711cbody",
   "metadata": {},
   "source": [
    "The `T1`, `T2`, and `T2Echo` experiments measure the three standard single-qubit coherence times on an individual transmon. T1 measures energy relaxation from the excited state, T2 measures total dephasing from a Ramsey sequence, and T2 echo measures the dephasing rate that remains after a refocusing π pulse suppresses low-frequency noise.\n",
    "\n",
    "Running these experiments requires a device that has already been calibrated through `TransmonDiscovery` so that qubit frequencies and drive amplitudes are populated. If you do not have one, complete the [get started tutorial](https://docs.q-ctrl.com/boulder-opal/autocalibration/get-started-with-boulder-opal-autocalibration) first."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c37e713",
   "metadata": {},
   "source": [
    "## 1. Set the device context\n",
    "\n",
    "Create a client and select the target device. All experiments run against the currently active device."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d5da6d30",
   "metadata": {},
   "outputs": [],
   "source": [
    "await client.set_current_device(\"<your-device-name>\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff44c5e1",
   "metadata": {},
   "source": [
    "## 2. Collect all coherence data with a routine\n",
    "\n",
    "The most direct way to obtain coherence data for a given qubit is the Transmon Coherence routine. It runs the T1, T2, and T2 echo experiments in sequence with input parameters pre-populated from the current device state, and saves the resulting coherence times to the virtual device. Use the per-experiment workflow in steps 3 to 5 instead if you want full control over delay grids, shot counts, oscillation counts, or other experiment options."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a29ab24",
   "metadata": {},
   "outputs": [],
   "source": [
    "from boulderopalscaleup.routines import TransmonCoherence\n",
    "\n",
    "target_transmon = \"q0\"\n",
    "\n",
    "tc_routine = TransmonCoherence(transmon=[target_transmon])\n",
    "tc_result = await client.run_routine(tc_routine)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ea299b1",
   "metadata": {},
   "source": [
    "## 3. Measure T1 relaxation time\n",
    "\n",
    "T1 is the energy relaxation time of the excited state. The experiment prepares $\\lvert 1 \\rangle$, waits for a variable delay, measures the survival probability, and fits an exponential decay to return T1 in nanoseconds. Pass the target transmon ID and a list of delays to sweep. `LogspaceIterable` distributes the delay points logarithmically, which is useful when T1 is not yet known to a narrow range."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ca89d71",
   "metadata": {},
   "outputs": [],
   "source": [
    "from boulderopalscaleup.common import LogspaceIterable\n",
    "from boulderopalscaleup.experiments import T1\n",
    "\n",
    "t1_experiment = T1(\n",
    "    transmon=target_transmon,\n",
    "    delays_ns=LogspaceIterable(start=2, stop=5.3, count=51),\n",
    "    shot_count=500,\n",
    ")\n",
    "t1_job_id = await client.run_experiment(t1_experiment)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f7a805d",
   "metadata": {},
   "source": [
    "## 4. Measure T2 dephasing time (Ramsey)\n",
    "\n",
    "T2 measures total dephasing with a Ramsey sequence: two $\\pi/2$ pulses separated by a variable free-evolution delay. The `oscillation_count` sets the target number of Ramsey fringes across the delay range, which fixes an artificial detuning large enough to keep the fringes resolved by the sampled delay grid and the fit well conditioned.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a505128",
   "metadata": {},
   "outputs": [],
   "source": [
    "from boulderopalscaleup.experiments import T2\n",
    "\n",
    "t2_experiment = T2(\n",
    "    transmon=target_transmon,\n",
    "    delays_ns=list(range(0, 50_001, 1000)),\n",
    "    oscillation_count=10,\n",
    "    shot_count=500,\n",
    ")\n",
    "t2_job_id = await client.run_experiment(t2_experiment)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54408e3c",
   "metadata": {},
   "source": [
    "## 5. Measure T2 echo coherence time\n",
    "\n",
    "`T2Echo` inserts a refocusing $\\pi$ pulse halfway between the two Ramsey $\\pi/2$ pulses. The $\\pi$ pulse reverses the sign of phase accumulated from noise that is quasi-static over the echo delay, so slow frequency fluctuations cancel and the measured decay reflects only the faster noise components. The echo time is typically longer than the bare T2 and is a better measure of the intrinsic dephasing rate.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3464b1c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from boulderopalscaleup.experiments import T2Echo\n",
    "\n",
    "t2_echo_experiment = T2Echo(\n",
    "    transmon=target_transmon,\n",
    "    delays_ns=list(range(0, 100_001, 2000)),\n",
    "    oscillation_count=10,\n",
    "    shot_count=500,\n",
    ")\n",
    "t2_echo_job_id = await client.run_experiment(t2_echo_experiment)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c17e152",
   "metadata": {},
   "source": [
    "## 6. Compare results across qubits\n",
    "\n",
    "The device data sheet collects the latest calibrated parameters for every qubit on the active device. After running the `TransmonCoherence` routine, or the per-qubit experiments above on multiple qubits, the sheet contains T1 and T2 values that you can compare across the device."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fada47b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "await client.display_device_data_sheet()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "875a168e",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "- Run `TransmonCoherence` to automate coherence measurements across all qubits in a single routine call: see the [Experiments and routines reference](https://docs.q-ctrl.com/boulder-opal/autocalibration/apply/how-to-run-an-automated-routine-with-boulder-opal).\n",
    "- Use `OneQubitRB` to characterize average gate error rates: see the user guide for your backend ([OPX+](https://docs.q-ctrl.com/boulder-opal/autocalibration/discover/setting-up-boulder-opal-with-quantummachines-opx-controllers) or [Qblox](https://docs.q-ctrl.com/boulder-opal/autocalibration/discover/setting-up-boulder-opal-with-qblox-controllers))."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
