{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fae381bf",
   "metadata": {},
   "source": [
    "# Submitting Cirq Circuits to Azure Quantum with the QDK\n",
    "\n",
    "This notebook demonstrates how to run Cirq `Circuit` jobs on Azure Quantum using `AzureQuantumService` from the QDK."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d696a7c",
   "metadata": {},
   "source": [
    "The workflow demonstrated here:\n",
    "\n",
    "1. Build a Cirq `Circuit` with named measurement keys.\n",
    "2. Reference an existing Azure Quantum workspace with `AzureQuantumService`.\n",
    "3. Browse available targets via `service.targets()`.\n",
    "4. Call `service.create_job(program=circuit, repetitions=..., target=...)` and fetch results (`job.results()`)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22d78680",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "\n",
    "This notebook assumes the `qdk` package with Azure Quantum and Cirq support is installed. You can install everything with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb556730",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install \"qdk[azure,cirq]\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b9ed32",
   "metadata": {},
   "source": [
    "This installs:\n",
    "- The base `qdk` package (compiler, OpenQASM/QIR tooling)\n",
    "- Azure Quantum client dependencies for submission\n",
    "- Cirq for circuit construction\n",
    "\n",
    "After installing, restart the notebook kernel if it was already running. You can verify installation with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7531d5a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cirq, qdk, qdk.azure  # should import without errors"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2b111db",
   "metadata": {},
   "source": [
    "## Build a simple Cirq circuit\n",
    "\n",
    "We start with a Bell-state circuit with two named measurement keys — one per qubit. Named keys are required so the result dictionary has clearly labeled registers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db4e96e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cirq\n",
    "\n",
    "q0, q1 = cirq.LineQubit.range(2)\n",
    "circuit = cirq.Circuit(\n",
    "    cirq.H(q0),\n",
    "    cirq.CNOT(q0, q1),\n",
    "    cirq.measure(q0, key=\"q0\"),\n",
    "    cirq.measure(q1, key=\"q1\"),\n",
    ")\n",
    "print(circuit)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f582bc13",
   "metadata": {},
   "source": [
    "## Configure Azure Quantum workspace connection\n",
    "\n",
    "To connect to an Azure workspace replace the following variables with your own values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0344527e",
   "metadata": {},
   "outputs": [],
   "source": [
    "subscription_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n",
    "resource_group = 'myresourcegroup'\n",
    "workspace_name = 'myworkspace'\n",
    "location = 'westus'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "184b6e57",
   "metadata": {},
   "source": [
    "## Submit the circuit to Azure Quantum\n",
    "\n",
    "`AzureQuantumService` exposes Azure Quantum targets as Cirq-compatible target objects. When you call `service.targets()`, the SDK returns one of two kinds of target:\n",
    "\n",
    "- **Provider-specific targets** — Some hardware vendors (e.g. IonQ, Quantinuum) ship dedicated Cirq target classes with native integration for their APIs, handling gate translation and result parsing using hardware-specific logic.\n",
    "- **Generic QIR targets** — For any other target that accepts QIR input, the SDK automatically wraps it as an `AzureGenericQirCirqTarget`. These compile the Cirq circuit to OpenQASM 3 and then to QIR internally, so you don't have to manage those steps manually.\n",
    "\n",
    "In practice `service.targets()` selects the right type for each target — you use the same `service.create_job()` call regardless of which target type is returned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f336702",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qdk.azure import Workspace\n",
    "from azure.quantum.cirq import AzureQuantumService\n",
    "\n",
    "workspace = Workspace(\n",
    "    subscription_id=subscription_id,\n",
    "    resource_group=resource_group,\n",
    "    name=workspace_name,\n",
    "    location=location,\n",
    ")\n",
    "\n",
    "service = AzureQuantumService(workspace)\n",
    "\n",
    "# List available targets\n",
    "for target in service.targets():\n",
    "    print(f\"{target.name:45s}  {type(target).__name__}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "417d7818",
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "\n",
    "# Replace with any target name from the list above\n",
    "target_name = \"rigetti.sim.qvm\"\n",
    "\n",
    "job = service.create_job(\n",
    "    program=circuit,\n",
    "    repetitions=100,\n",
    "    name=\"cirq-bell-job\",\n",
    "    target=target_name,\n",
    ")\n",
    "print(f\"Job {job.job_id()} submitted — waiting for results...\")\n",
    "\n",
    "result = job.results()\n",
    "\n",
    "# Combine separate measurement keys into joint bitstrings\n",
    "keys = sorted(result.measurements.keys())\n",
    "joint = Counter(\n",
    "    \"\".join(str(int(result.measurements[k][i][0])) for k in keys)\n",
    "    for i in range(len(result.measurements[keys[0]]))\n",
    ")\n",
    "total = sum(joint.values())\n",
    "print(f\"\\nResults ({total} shots)  [keys: {', '.join(keys)}]:\")\n",
    "for bitstring, count in sorted(joint.items()):\n",
    "    print(f\"  {bitstring}: {count:4d}  ({count/total:.1%})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1478e88a",
   "metadata": {},
   "source": [
    "## Handling qubit loss on noisy hardware\n",
    "\n",
    "On some hardware backends — particularly neutral-atom and trapped-ion devices — a qubit may be lost before measurement (e.g. an atom is ejected from the trap). When this happens, the backend records `\"-\"` in the bitstring position for that qubit rather than `\"0\"` or `\"1\"`. Because loss shots contain non-binary characters, they cannot be included in standard measurement arrays, which assume a fixed binary alphabet. The SDK therefore separates them automatically.\n",
    "\n",
    "The `cirq.ResultDict` returned by `job.results()` exposes two ways to access shots:\n",
    "\n",
    "| Field | What it contains |\n",
    "|---|---|\n",
    "| **`result.measurements[key]`** | NumPy int8 array of accepted shots only (no `\"-\"`), shape `(accepted_shots, num_qubits)` |\n",
    "| **`result.raw_measurements()[key]`** | String array of all shots (loss shots have `\"-\"`), same key structure as `measurements` |\n",
    "| **`result.raw_shots`** | The original shot objects exactly as returned by the backend |\n",
    "\n",
    "Use `result.measurements` for any downstream analysis that expects clean binary arrays. Use `result.raw_measurements()` and `result.raw_shots` to inspect loss patterns — for example, to calculate the overall loss rate or identify which qubit positions are being lost most frequently.\n",
    "\n",
    "> **Tip**: A high loss rate may indicate hardware instability or a circuit that is too deep for the current calibration."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
