{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Selene emulation in Nexus\n", "\n", "Selene is Quantinuum's emulation toolkit, offering different simulation backends, noise models and emulation runtime modes (including full emulation of the Helios runtime).\n", "\n", "Programs for Selene are currently written in [Guppy](https://pypi.org/project/guppylang/).\n", "\n", "You can run Selene in your local programming environment, however Quantinuum Nexus provides Selene instances (with advanced features available) in the cloud.\n", "\n", "To use this we'll follow the basic pattern:\n", "\n", "1. Write your Guppy program and compile it (to an intermediate representation called HUGR).\n", "2. Upload this HUGR to the Nexus database so you can use it in Nexus jobs.\n", "3. Define your SeleneConfig (or SelenePlusConfig) - a way to select the simulation/error/runtime mode for your job.\n", "4. Submit an ExecuteJob to Nexus - along with your HUGRRefs and Selene configuration.\n", "5. Wait for the job to complete and collect the results.\n", "\n", "\n", "This notebook outlines examples of each of the Selene simulator backends in Nexus.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime\n", "from pprint import pprint\n", "from typing import no_type_check\n", "\n", "from guppylang import guppy\n", "from guppylang.std.builtins import array, comptime, result\n", "from guppylang.std.quantum import cx, h, measure, qubit, t\n", "\n", "import qnexus as qnx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set up a Nexus Project" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "qnx.login()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_project_ref = qnx.projects.get_or_create(name=\"Selene emulation in Nexus\")\n", "qnx.context.set_active_project(my_project_ref)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Statevector simulation\n", "\n", "Let's prepare a simple program and run it on Selene using our `StatevectorSimulator`, which is built on the [QuEST statevector simulator](https://quest.qtechtheory.org/).\n", "\n", "Unless specified, all Selene simulations use:\n", "- SimpleRuntime (i.e. not emulating the runtime of any particular hardware)\n", "- NoErrorModel (i.e. a noiseless simulation)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Guppy program to prepare GHZ state on 4 qubits\n", "@guppy\n", "@no_type_check\n", "def main() -> None:\n", " qubit_top = qubit()\n", " h(qubit_top)\n", " qubits = array(qubit() for _ in range(3))\n", " for q in qubits:\n", " cx(qubit_top, q)\n", " m = measure(q)\n", " result(\"result\", m)\n", " m_top = measure(qubit_top)\n", " result(\"result\", m_top)\n", "\n", "\n", "hugr = main.compile()\n", "\n", "# Upload the compiled HUGR program to Nexus database, get a reference to it\n", "ghz_hugr_program_ref = qnx.hugr.upload(\n", " hugr_package=hugr,\n", " name=\"4 qubit GHZ State Prep\",\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define our Selene configuration\n", "config = qnx.models.SeleneConfig(\n", " n_qubits=4, \n", " simulator=qnx.models.StatevectorSimulator()\n", ")\n", "\n", "selene_quest_job_ref = qnx.start_execute_job(\n", " programs=[ghz_hugr_program_ref],\n", " n_shots=[100],\n", " backend_config=config,\n", " name=f\"4 qubit GHZ on on {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "# Might take time during busy periods\n", "qnx.jobs.wait_for(selene_quest_job_ref, timeout=None)\n", "\n", "qsys_result = qnx.jobs.results(selene_quest_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Stabilizer simulation\n", "\n", "Now we will try using the `StabilizerSimulator`, which is built on [Stim](https://github.com/quantumlib/stim).\n", "\n", "As a stabilizer simulator it can handle many more qubits, as long as only Clifford gates are used. We don't set an upper bound but expect that simulating 1000s of qubits should be possible." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N = 200\n", "\n", "\n", "@guppy\n", "@no_type_check\n", "def entangle(qs: array[qubit, comptime(N)]) -> None:\n", " h(qs[0])\n", " for i in range(len(qs)):\n", " if i != 0:\n", " cx(qs[0], qs[i])\n", "\n", "\n", "@guppy\n", "@no_type_check\n", "def main() -> None: # noqa: F811\n", " qubits = array(qubit() for _ in range(comptime(N)))\n", "\n", " entangle(qubits)\n", "\n", " for q in qubits:\n", " m = measure(q)\n", " result(\"result\", m)\n", "\n", "\n", "hugr = main.compile()\n", "\n", "\n", "# Upload the compiled HUGR program to Nexus database, get a reference to it\n", "many_qubits_hugr_program_ref = qnx.hugr.upload(\n", " hugr_package=hugr,\n", " name=\"Many Entangled Qubits\",\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Our Selene Stim configuration\n", "config = qnx.models.SeleneConfig(\n", " n_qubits=N, \n", " simulator=qnx.models.StabilizerSimulator(angle_threshold=0.01)\n", ")\n", "\n", "selene_stim_job_ref = qnx.start_execute_job(\n", " programs=[many_qubits_hugr_program_ref],\n", " n_shots=[100],\n", " backend_config=config,\n", " name=f\"Many entangled qubits on {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "qnx.jobs.wait_for(selene_stim_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(selene_stim_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Coinflip simulation\n", "\n", "The `CoinflipSimulator` does not maintain any quantum state, but instead performs a 'coin flip' for any measurement operation - with a configurable bias." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N = 100\n", "\n", "\n", "@guppy\n", "@no_type_check\n", "def entangle_and_t(qs: array[qubit, comptime(N)]) -> None:\n", " h(qs[0])\n", " for i in range(len(qs)):\n", " if i != 0:\n", " cx(qs[0], qs[i])\n", " t(qs[i])\n", "\n", "\n", "@guppy\n", "@no_type_check\n", "def main() -> None: # noqa: F811\n", " qubits = array(qubit() for _ in range(comptime(N)))\n", "\n", " entangle_and_t(qubits)\n", "\n", " for q in qubits:\n", " m = measure(q)\n", " result(\"result\", m)\n", "\n", "\n", "hugr = main.compile()\n", "\n", "\n", "# Upload the compiled HUGR program to Nexus database, get a reference to it\n", "many_t_gates_hugr_program_ref = qnx.hugr.upload(\n", " hugr_package=hugr,\n", " name=\"Many T gates\",\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "config = qnx.models.SeleneConfig(\n", " n_qubits=N,\n", " simulator=qnx.models.CoinflipSimulator(bias=1.0,),\n", ")\n", "\n", "selene_coinflip_job_ref = qnx.start_execute_job(\n", " programs=[many_t_gates_hugr_program_ref],\n", " n_shots=[100],\n", " backend_config=config,\n", " name=f\"Many T Gates on {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "qnx.jobs.wait_for(selene_coinflip_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(selene_coinflip_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classical Replay simulation\n", "\n", "The `ClassicalReplaySimulator` offers additional debugging capabilities for Guppy programs. Like the `CoinflipSimulator` it doesn't maintain quantum state but instead measurements will correspond to pre-defined boolean values. You must provide a list of these measurement results in advance for each shot." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@guppy\n", "@no_type_check\n", "def main() -> None: # noqa: F811\n", " q0: qubit = qubit()\n", " h(q0)\n", " c0: bool = measure(q0)\n", " result(\"c0\", c0)\n", " if c0:\n", " q1: qubit = qubit()\n", " h(q1)\n", " c1: bool = measure(q1)\n", " result(\"c1\", c1)\n", "\n", "\n", "hugr = main.compile()\n", "\n", "# Upload the compiled HUGR program to Nexus database, get a reference to it\n", "conditional_hugr_program = qnx.hugr.upload(\n", " hugr_package=hugr,\n", " name=\"Conditional HUGR Program\",\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "config = qnx.models.SeleneConfig(\n", " n_qubits=2, \n", " simulator=qnx.models.ClassicalReplaySimulator(measurements=[[True, False], [True, True], [False], [False]]),\n", ")\n", "\n", "selene_classical_replay_job_ref = qnx.start_execute_job(\n", " programs=[conditional_hugr_program],\n", " n_shots=[4],\n", " backend_config=config,\n", " name=f\"Conditional actions on {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "qnx.jobs.wait_for(selene_classical_replay_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(selene_classical_replay_job_ref)[0].download_result()\n", "\n", "pprint(qsys_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Selene Error Models\n", "\n", "We currently provide a simple `DepolarizingErrorModel`. This can be used for any Selene configuration but will not have an effect on the debugging tools (e.g. `CoinflipSimulator` and `ClassicalReplaySimulator`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "config = qnx.models.SeleneConfig(\n", " n_qubits=4,\n", " error_model=qnx.models.DepolarizingErrorModel(\n", " p_init=0.99,\n", " p_1q=0.99,\n", " p_meas=0.99,\n", " ),\n", ")\n", "\n", "noisy_selene_quest_job_ref = qnx.start_execute_job(\n", " programs=[ghz_hugr_program_ref],\n", " n_shots=[100],\n", " backend_config=config,\n", " name=f\"Noisy 4 qubit GHZ for {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "qnx.jobs.wait_for(noisy_selene_quest_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(noisy_selene_quest_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can compare this noisy result with the noiseless one we obtained earlier." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "qsys_result = qnx.jobs.results(selene_quest_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Advanced SelenePlus Features\n", "\n", "You will need access to advanced emulation features to use `SelenePlus`, which allows you to:\n", "\n", "- emulate the Helios runtime with `HeliosRuntime`\n", "- configure error for Helios emulation with `QSystemErrorModel` and `HeliosCustomErrorModel`\n", "- use Quantinuum's `MatrixProductStateSimulator`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Advanced Error Configuration\n", "\n", "`QSystemErrorModel` will use a preconfigured noise model set by Quantinuum that most closely matches the quantum hardware.\n", "\n", "`HeliosCustomErrorModel` will let you specify the individual error parameters by hand." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Emulation of Helios hardware with Quantinuum's error model\n", "config = qnx.models.SelenePlusConfig(\n", " n_qubits=4,\n", " runtime=qnx.models.HeliosRuntime(),\n", " error_model=qnx.models.QSystemErrorModel(),\n", ")\n", "\n", "\n", "# Emulation of Helios with a custom error model defined by you\n", "config = qnx.models.SelenePlusConfig(\n", " n_qubits=4,\n", " runtime=qnx.models.HeliosRuntime(),\n", " error_model=qnx.models.HeliosCustomErrorModel(\n", " error_params=qnx.models.HeliosErrorParams(\n", " p_init=0.98,\n", " p1_scale=0.97,\n", " p_crosstalk_init=0.95,\n", " # For more parameters see the docstring\n", " )\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matrix Product State simulator\n", "\n", "Our `MatrixProductStateSimulator` is developed in-house at Quantinuum.\n", "\n", "The amount of qubits you can simulate depends on the level of fidelity you find acceptable (set via configuration - see docstring for details)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "config = qnx.SelenePlusConfig(\n", " n_qubits=N,\n", " simulator=qnx.models.MatrixProductStateSimulator(\n", " backend=\"auto\",\n", " seed=123141,\n", " zero_threshold=0.01,\n", " chi=50,\n", " ),\n", ")\n", "\n", "\n", "selene_lean_job_ref = qnx.start_execute_job(\n", " programs=[many_t_gates_hugr_program_ref],\n", " n_shots=[100],\n", " backend_config=config,\n", " name=f\"Many T Gates on {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "qnx.jobs.wait_for(selene_lean_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(selene_lean_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SelenePlus Runtimes\n", "\n", "In additional to the default `SimpleRuntime`. We currently provide a `HeliosRuntime` - this will emulate the real runtime environment of Quantinuum's Helios system." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "config = qnx.models.SelenePlusConfig(\n", " n_qubits=4,\n", " runtime=qnx.models.HeliosRuntime(),\n", ")\n", "\n", "noisy_selene_quest_job_ref = qnx.start_execute_job(\n", " programs=[ghz_hugr_program_ref],\n", " n_shots=[100],\n", " backend_config=config,\n", " name=f\"Noisy 4 qubit GHZ for {config.__class__.__name__} {datetime.now()}\",\n", ")\n", "\n", "qnx.jobs.wait_for(noisy_selene_quest_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(noisy_selene_quest_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cloud-hosted Helios emulation\n", "\n", "The above has notebook details how to use the Selene platform in Nexus, however if you have access to advanced emulation features then you will also have access to a lightweight cloud-hosted Helios emulator such as `Helios-1E-lite`.\n", "\n", "This system uses `SelenePlus` features under the hood, and can be accessed with the `HeliosConfig`.\n", "\n", "By default it will use `StatevectorSimulator` and `QSystemErrorModel`, but these can be configured if desired." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run on Helios-1E-lite in the cloud with default settings\n", "config = qnx.models.HeliosConfig(\n", " system_name=\"Helios-1E-lite\",\n", " emulator_config=qnx.models.HeliosEmulatorConfig(\n", " n_qubits=20,\n", " )\n", ")\n", "\n", "default_helios_lite_job_ref = qnx.start_execute_job(\n", " programs=[ghz_hugr_program_ref],\n", " backend_config=config,\n", " name=\"Helios-1E-lite test Job\",\n", " n_shots=[100],\n", ")\n", "\n", "qnx.jobs.wait_for(default_helios_lite_job_ref)\n", "\n", "qsys_result = qnx.jobs.results(default_helios_lite_job_ref)[0].download_result()\n", "\n", "qsys_result.collated_counts()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.9" } }, "nbformat": 4, "nbformat_minor": 2 }