{ "cells": [ { "cell_type": "markdown", "id": "d0eed8fc", "metadata": {}, "source": [ "# A tested local workflow and an explicit hardware handoff\n", "\n", "**Level:** Undergraduate · **Book connections:** P03, P10, P16, P17, P20, P21, P24\n", "\n", "After this notebook you can:\n", "\n", "- Run the Runtime Sampler interface against a local simulator.\n", "- Separate preparation, target compilation, submission and provenance.\n", "- Prepare an optional QPU path with explicit credentials and bounded shots.\n", "\n", "Run cells from top to bottom in the pinned environment described in `README.md`. All default execution is local. Exact amplitudes are simulator information; a QPU supplies sampled outcomes." ] }, { "cell_type": "code", "execution_count": null, "id": "066eeca6", "metadata": {}, "outputs": [], "source": [ "from importlib.metadata import version\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from IPython.display import display\n", "from qiskit import QuantumCircuit\n", "from qiskit.quantum_info import Statevector\n", "\n", "EXPECTED = {\"qiskit\": \"2.6.1\", \"qiskit-aer\": \"0.18.2\", \"qiskit-ibm-runtime\": \"0.51.0\"}\n", "for package, expected in EXPECTED.items():\n", " actual = version(package)\n", " assert actual == expected, f\"Use the supplied environment: {package}={actual}, expected {expected}\"\n", "print(\"Verified package versions:\", EXPECTED)\n", "plt.rcParams.update({\"figure.figsize\": (8, 3.5), \"font.size\": 11, \"axes.spines.top\": False,\n", " \"axes.spines.right\": False, \"axes.prop_cycle\": plt.cycler(color=[\"#006DAE\", \"#00A6B2\", \"#9E4A78\"])})\n", "SEED = 20261106\n", "\n", "def draw(circuit):\n", " \"\"\"Render a real circuit; each wire is a qubit, time runs left to right.\"\"\"\n", " fig = circuit.draw(output=\"mpl\", fold=90, style=\"iqp\")\n", " display(fig)\n", " plt.close(fig)\n", "\n", "def book_label(qiskit_label):\n", " \"\"\"For a single register, show q0 first as in the book.\"\"\"\n", " return qiskit_label[::-1]" ] }, { "cell_type": "markdown", "id": "9f659092", "metadata": {}, "source": [ "**Read the wires and bit strings carefully.** Qiskit draws `q0` on the top wire, but a\n", "standard statevector basis label or measured single-register count key is written\n", "`q(n−1)…q1q0`: `q0` has integer weight $2^0$. The book often writes `q0` first.\n", "These notebooks keep native Qiskit labels in calculations and use `book_label`\n", "only for an explicitly labelled display. Reversing a label changes the display;\n", "it does not apply a SWAP gate. All count examples here use one classical register\n", "with measurement `qj → cj`; do not reverse arbitrary multi-register strings." ] }, { "cell_type": "markdown", "id": "fe678272", "metadata": {}, "source": [ "## 1. Use the service interface locally first\n", "\n", "Runtime's client-side `Sampler` can use an Aer backend in local testing mode. The cell\n", "below uses that path and performs no remote job. Its `meas` register comes\n", "from `measure_all()`. Keep the register name explicit when reading results.\n", "\n", "The actual device will add finite calibration-dependent errors, routing and\n", "queueing. A local success establishes software plumbing and ideal behavior;\n", "it does not predict a device's fidelity or guarantee availability." ] }, { "cell_type": "code", "execution_count": null, "id": "0a9aa955", "metadata": {}, "outputs": [], "source": [ "from qiskit_aer import AerSimulator\n", "from qiskit_ibm_runtime import QiskitRuntimeService\n", "from qiskit_ibm_runtime.executor_sampler import Sampler as RuntimeSampler\n", "from qiskit.transpiler import generate_preset_pass_manager\n", "from importlib.metadata import version\n", "import os\n", "import platform\n", "from datetime import datetime, timezone\n", "\n", "bell = QuantumCircuit(2)\n", "bell.h(0)\n", "bell.cx(0, 1)\n", "bell.measure_all()\n", "draw(bell)\n", "local_backend = AerSimulator(seed_simulator=SEED)\n", "local_isa = generate_preset_pass_manager(backend=local_backend, optimization_level=1,\n", " seed_transpiler=SEED).run(bell)\n", "local_sampler = RuntimeSampler(mode=local_backend, options={\"simulator\": {\"seed_simulator\": SEED}})\n", "local_result = local_sampler.run([local_isa], shots=256).result()\n", "local_counts = local_result[0].data.meas.get_counts()\n", "assert set(local_counts) <= {\"00\", \"11\"}\n", "assert sum(local_counts.values()) == 256\n", "print(\"Local Runtime-interface counts:\", local_counts)" ] }, { "cell_type": "markdown", "id": "015556f2", "metadata": {}, "source": [ "## 2. Optional hardware path: opt in explicitly\n", "\n", "Default execution leaves `RUN_ON_QPU=False`, never contacts an account and never\n", "submits a remote job. To run hardware later, obtain access and check your\n", "provider's current allocation/pricing. Set these environment variables outside\n", "the notebook: `IBM_QUANTUM_API_KEY`, `IBM_QUANTUM_INSTANCE` (your instance CRN),\n", "and `IBM_QUANTUM_BACKEND` (an explicitly chosen device name).\n", "\n", "Do not paste a key into a cell, print credentials, commit `.env` files or call\n", "`save_account` from this shared notebook. The function requires the three\n", "environment values and passes the key directly; it does not fall back to a\n", "saved account. Review the displayed shot/runtime limits before changing the flag.\n", "\n", "`max_execution_time` is a service execution limit, not a monetary price cap or\n", "a queue timeout. The documented `dry_run=True` option is also server-side and\n", "can contact the service; we use no remote dry run in default execution." ] }, { "cell_type": "code", "execution_count": null, "id": "653f3f0b", "metadata": {}, "outputs": [], "source": [ "RUN_ON_QPU = False # Deliberate edit required. Environment variables cannot enable this flag.\n", "HARDWARE_SHOTS = 256\n", "MAX_EXECUTION_SECONDS = 120\n", "\n", "def submit_hardware_job(circuit, *, shots=HARDWARE_SHOTS):\n", " if not (1 <= shots <= 1024):\n", " raise ValueError(\"This teaching workflow permits 1–1024 shots\")\n", " required = [\"IBM_QUANTUM_API_KEY\", \"IBM_QUANTUM_INSTANCE\", \"IBM_QUANTUM_BACKEND\"]\n", " if any(not os.environ.get(name) for name in required):\n", " raise RuntimeError(\"Set the three required IBM_QUANTUM_* environment variables outside the notebook\")\n", " service = QiskitRuntimeService(\n", " channel=\"ibm_quantum_platform\", token=os.environ[\"IBM_QUANTUM_API_KEY\"],\n", " instance=os.environ[\"IBM_QUANTUM_INSTANCE\"],\n", " )\n", " backend = service.backend(os.environ[\"IBM_QUANTUM_BACKEND\"])\n", " if backend.num_qubits < circuit.num_qubits:\n", " raise ValueError(\"Chosen backend is too small\")\n", " pass_manager = generate_preset_pass_manager(backend=backend, optimization_level=1,\n", " seed_transpiler=SEED)\n", " isa = pass_manager.run(circuit)\n", " sampler = RuntimeSampler(mode=backend, options={\"max_execution_time\": MAX_EXECUTION_SECONDS})\n", " job = sampler.run([isa], shots=shots)\n", " # No credentials or instance identifiers are returned or displayed.\n", " provenance = {\"utc_submitted\": datetime.now(timezone.utc).isoformat(),\n", " \"backend\": backend.name, \"job_id\": job.job_id(), \"shots\": shots,\n", " \"max_execution_seconds\": MAX_EXECUTION_SECONDS,\n", " \"transpiler_seed\": SEED, \"optimization_level\": 1,\n", " \"compiled_depth\": isa.depth(), \"compiled_operations\": dict(isa.count_ops()),\n", " \"python\": platform.python_version(), \"packages\": dict(EXPECTED)}\n", " return job, provenance\n", "\n", "if RUN_ON_QPU:\n", " hardware_job, hardware_provenance = submit_hardware_job(bell)\n", " print(hardware_provenance)\n", "else:\n", " print(\"Local validation complete. Remote submission is disabled.\")" ] }, { "cell_type": "markdown", "id": "9c602a23", "metadata": {}, "source": [ "## 3. Retrieve only a job you deliberately submitted\n", "\n", "Retrieval is separate from submission so rerunning a result cell does not\n", "launch a new job. Waiting may take time; the following cell also stays idle\n", "under the default flag. A measured `01` or `10` on a real device is evidence\n", "about the experiment, not automatically a software failure. Compare it with\n", "the circuit, mapping, calibration and error model." ] }, { "cell_type": "code", "execution_count": null, "id": "11111a7b", "metadata": {}, "outputs": [], "source": [ "if RUN_ON_QPU:\n", " hardware_result = hardware_job.result()\n", " hardware_counts = hardware_result[0].data.meas.get_counts()\n", " assert sum(hardware_counts.values()) == HARDWARE_SHOTS\n", " disagreement = (hardware_counts.get(\"01\", 0)+hardware_counts.get(\"10\", 0))/HARDWARE_SHOTS\n", " print(\"QPU counts:\", hardware_counts)\n", " print(\"Observed Z-basis disagreement fraction:\", disagreement)\n", "else:\n", " print(\"No remote result was requested.\")" ] }, { "cell_type": "markdown", "id": "f2065cdd", "metadata": {}, "source": [ "## Checkpoint and practice\n", "\n", "Does successful local execution guarantee a QPU will accept the same uncompiled\n", "circuit? **No:** compile for the selected device's instruction set and current\n", "connectivity, and check service limits before submission.\n", "\n", "1. Add a local ideal test of the premeasurement state before submitting anything.\n", "2. Add a local noise model from notebook 04 and state which errors it represents.\n", "3. Design a result record containing circuit parameters, device, date, shots,\n", " compiler settings and uncertainty. Keep credentials and personal account data out.\n", "\n", "**Takeaway:** local tests, device compilation, explicit submission and evidence\n", "recording are distinct responsibilities in a reliable quantum application." ] }, { "cell_type": "markdown", "id": "b31d7cf3", "metadata": {}, "source": [ "## Sources\n", "\n", "- [IBM: transpile with pass managers](https://quantum.cloud.ibm.com/docs/en/guides/transpile-with-pass-managers)\n", "- [Qiskit Aer: AerSimulator](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.AerSimulator)\n", "- [IBM: local testing mode](https://quantum.cloud.ibm.com/docs/en/guides/local-testing-mode)\n", "- [IBM: current Sampler examples](https://quantum.cloud.ibm.com/docs/en/guides/sampler-examples)\n", "- [IBM: QiskitRuntimeService API](https://quantum.cloud.ibm.com/docs/en/api/qiskit-ibm-runtime/qiskit-runtime-service)\n", "- [IBM: initialize an account](https://quantum.cloud.ibm.com/docs/en/guides/initialize-account)" ] } ], "metadata": { "kernelspec": { "display_name": "Quantum book (pinned Python)", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.13.2" }, "quantum_book": { "chapters": [ "P03", "P10", "P16", "P17", "P20", "P21", "P24" ], "expected_packages": { "qiskit": "2.6.1", "qiskit-aer": "0.18.2", "qiskit-ibm-runtime": "0.51.0" }, "level": "Undergraduate", "network_on_default_run": false, "verified_release_date": "2026-11-06" } }, "nbformat": 4, "nbformat_minor": 5 }