From 10c719b3b76b90b11b81097692440d18b5db8f29 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Tue, 7 May 2024 10:24:19 +0200 Subject: [PATCH 01/18] Add QAOA sample from Pasqal --- ...ic-unconstrained-binary-optimization.ipynb | 819 ++++++++++++++++++ 1 file changed, 819 insertions(+) create mode 100644 samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb new file mode 100644 index 00000000..a6996bf8 --- /dev/null +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -0,0 +1,819 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# QAOA and QAA to solve a QUBO problem" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Restart the kernel once you have run this cell\n", + "!pip install -U -q azure-quantum\n", + "\n", + "try:\n", + " import pulser\n", + "except ImportError:\n", + " # This installs two python packages:\n", + " # - pulser (core package)\n", + " # - pulser_simulation\n", + " # If you don't need simulation in your own workflow, you may run instead:\n", + " # !pip -q install pulser-core\n", + " # https://pulser.readthedocs.io/en/stable/installation.html\n", + " !pip -q install pulser" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from pulser import Pulse, Sequence, Register\n", + "from pulser_simulation import QutipEmulator\n", + "from pulser.devices import DigitalAnalogDevice\n", + "from pulser.waveforms import InterpolatedWaveform\n", + "from scipy.optimize import minimize\n", + "from scipy.spatial.distance import pdist, squareform" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Introduction " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, we illustrate how to solve a Quadratic Unconstrained Binary Optimization (QUBO) instance using an ensemble of Rydberg atoms in analog mode.\n", + "\n", + "QUBO has been extensively studied [Glover, et al., 2018](https://arxiv.org/pdf/1811.11538.pdf) and is used to model and solve numerous categories of optimization problems including important instances of network flows, scheduling, max-cut, max-clique, vertex cover and other graph and management science problems, integrating them into a unified modeling framework.\n", + "\n", + "Mathematically, a QUBO instance consists of a symmetric matrix $Q$ of size $(N \\times N)$, and the optimization problem associated with it is to find the bitstring $z=(z_1, \\dots, z_N) \\in \\{0, 1 \\}^N$ that minimizes the quantity\n", + "$$f(z) = z^{T}Qz$$ \n", + "\n", + "\n", + "In this tutorial, we will demonstrate how a QUBO instance can be mapped and solved using neutral atoms." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we are given the following QUBO matrix $Q$:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "Q = np.array(\n", + " [\n", + " [-10.0, 19.7365809, 19.7365809, 5.42015853, 5.42015853],\n", + " [19.7365809, -10.0, 20.67626392, 0.17675796, 0.85604541],\n", + " [19.7365809, 20.67626392, -10.0, 0.85604541, 0.17675796],\n", + " [5.42015853, 0.17675796, 0.85604541, -10.0, 0.32306662],\n", + " [5.42015853, 0.85604541, 0.17675796, 0.32306662, -10.0],\n", + " ]\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because the QUBO is small, we can classically check all solutions and mark the optimal ones. This will help us later in the tutorial to visualize the quality of our quantum approach." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "bitstrings = [np.binary_repr(i, len(Q)) for i in range(2 ** len(Q))]\n", + "costs = []\n", + "# this takes exponential time with the dimension of the QUBO\n", + "for b in bitstrings:\n", + " z = np.array(list(b), dtype=int)\n", + " cost_qutip_emulator = z.T @ Q @ z\n", + " costs.append(cost_qutip_emulator)\n", + "zipped = zip(bitstrings, costs)\n", + "sort_zipped = sorted(zipped, key=lambda x: x[1])\n", + "print(sort_zipped[:3])" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This QUBO admits `01011` and `00111` as optimal solutions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Embedding a QUBO onto an atomic register" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now illustrate how to use Pulser to embbed the QUBO matrix $Q$ on a neutral-atom device.\n", + "\n", + "The key idea is to encode the off-diagonal terms of $Q$ by using the Rydberg interaction between atoms. As the interaction $U$ depends on the pairwise distance ($U=C_6/r_{ij}^6$) between atoms $i$ and $j$, we attempt to find the optimal positions of the atoms in the Register that replicate best the off-diagonal terms of $Q$:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def evaluate_mapping(new_coords, *args):\n", + " \"\"\"Cost function to minimize. Ideally, the pairwise\n", + " distances are conserved\"\"\"\n", + " Q, shape = args\n", + " new_coords = np.reshape(new_coords, shape)\n", + " new_Q = squareform(DigitalAnalogDevice.interaction_coeff / pdist(new_coords) ** 6)\n", + " return np.linalg.norm(new_Q - Q)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "shape = (len(Q), 2)\n", + "costs = []\n", + "np.random.seed(0)\n", + "x0 = np.random.random(shape).flatten()\n", + "res = minimize(\n", + " evaluate_mapping,\n", + " x0,\n", + " args=(Q, shape),\n", + " method=\"Nelder-Mead\",\n", + " tol=1e-6,\n", + " options={\"maxiter\": 200000, \"maxfev\": None},\n", + ")\n", + "coords = np.reshape(res.x, (len(Q), 2))" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can then plot the obtained coordinates in a Register using:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "qubits = {str(i): c for i, c in enumerate(coords)}\n", + "reg = Register(qubits)\n", + "reg.draw(\n", + " blockade_radius=DigitalAnalogDevice.rydberg_blockade_radius(1.0),\n", + " draw_graph=False,\n", + " draw_half_radius=True,\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Building the quantum algorithm " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the QUBO $Q$ is encoded in the Register, we can peprare the following Ising Hamiltonian $H_Q$:\n", + "\n", + "$$ H_Q= \\sum_{i=1}^N \\frac{\\hbar\\Omega}{2} \\sigma_i^x - \\sum_{i=1}^N \\frac{\\hbar \\delta}{2} \\sigma_i^z+\\sum_{j \\lt i}\\frac{C_6}{|\\textbf{r}_i-\\textbf{r}_j|^{6}} n_i n_j. $$\n", + "\n", + "In the case where our mapping of the atoms is perfect, the last sum replicates exactly the off-diagonal terms of $Q$. In that case, the next step is to prepare the ground-state of $H_Q$ to output the optimal bitstrings.\n", + "\n", + "To do so we present two different approaches, namely the Quantum Approximation Optimization Algorithm (QAOA) and the Quantum Adiabatic Algorithm (QAA) that have been introduced to prepare ground-states of Hamiltonians." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QAOA" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This algorithm (see [Farhi, et al., 2014](https://arxiv.org/pdf/1411.4028.pdf)) has gained a lot of traction lately as a gate-based quantum algorithm. It has shown promising results in a number of applications and yields decent results for low-depth circuits.\n", + "\n", + "All atoms are initially in the groundstate $|00\\dots0\\rangle$ of the `ground-rydberg` basis. We then apply $p$ layers of alternating non-commutative Hamiltonians. The first one, called the mixing Hamiltonian $H_M$, is realized by taking $\\Omega = 1$ rad/µs, and $\\delta = 0$ rad/µs in the Hamiltonian equation. The second Hamiltonian $H_Q$ is realized with $\\Omega =0$ rad/µs and $\\delta = 1.$ rad/µs. $H_M$ and $H_Q$ are applied turn in turn with parameters $\\tau$ and $t$ respectively. A classical optimizer is then used to estimate the optimal parameters. \n", + "\n", + "Instead of creating a new `Sequence` everytime the quantum loop is called, we are going to create a parametrized `Sequence` and give that to the quantum loop." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "LAYERS = 2\n", + "\n", + "# Parametrized sequence\n", + "seq = Sequence(reg, DigitalAnalogDevice)\n", + "seq.declare_channel(\"ch0\", \"rydberg_global\")\n", + "\n", + "t_list = seq.declare_variable(\"t_list\", size=LAYERS)\n", + "s_list = seq.declare_variable(\"s_list\", size=LAYERS)\n", + "\n", + "for t, s in zip(t_list, s_list):\n", + " pulse_1 = Pulse.ConstantPulse(1000 * t, 1.0, 0.0, 0)\n", + " pulse_2 = Pulse.ConstantPulse(1000 * s, 0.0, 1.0, 0)\n", + "\n", + " seq.add(pulse_1, \"ch0\")\n", + " seq.add(pulse_2, \"ch0\")\n", + "\n", + "seq.measure(\"ground-rydberg\")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we have the parameters that we want to apply, we use the `.build()` method to assign these values into a `assigned_seq` sequence. It is this sequence which is simulated every time the quantum loop is called." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Experimentally, we don't have access to the state vector $|\\psi\\rangle$. We therefore make it more realistic by taking samples from the state vector that results from running the simulation with `simul.run()`. This is done with the built-in method `results.sample_final_state()`, in which we add the measurement basis which was declared at the end of the sequence, and the number of samples desired. Currently, the repetition rate of the machine is $5$ Hz." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def quantum_loop(parameters):\n", + " params = np.array(parameters)\n", + " t_params, s_params = np.reshape(params.astype(int), (2, LAYERS))\n", + " assigned_seq = seq.build(t_list=t_params, s_list=s_params)\n", + " simul = QutipEmulator.from_sequence(assigned_seq, sampling_rate=0.01)\n", + " results = simul.run()\n", + " count_dict = results.sample_final_state() # sample from the state vector\n", + " return count_dict" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "np.random.seed(123) # ensures reproducibility of the tutorial\n", + "guess = {\n", + " \"t\": np.random.uniform(8, 10, LAYERS),\n", + " \"s\": np.random.uniform(1, 3, LAYERS),\n", + "}" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "example_dict = quantum_loop(np.r_[guess[\"t\"], guess[\"s\"]])" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can then plot the distribution of the samples, to see the most frequent bitstrings sampled." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def plot_distribution(C):\n", + " C = dict(sorted(C.items(), key=lambda item: item[1], reverse=True))\n", + " indexes = [\"01011\", \"00111\"] # QUBO solutions\n", + " color_dict = {key: \"r\" if key in indexes else \"g\" for key in C}\n", + " plt.figure(figsize=(12, 6))\n", + " plt.xlabel(\"bitstrings\")\n", + " plt.ylabel(\"counts\")\n", + " plt.bar(C.keys(), C.values(), width=0.5, color=color_dict.values())\n", + " plt.xticks(rotation=\"vertical\")\n", + " plt.show()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_distribution(example_dict)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The bitstrings `01011` and `00111` (in red) correspond to the two optimal solutions (calculated at the beginning of the notebook). The goal of QAOA is to choregraph interferences between the basis states, in order to maximize the frequency of the optimal solution states. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Optimization " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We estimate the cost of a sampled state vector by making an average over the samples. This is done by taking the corresponding bitstring ${\\bf z}=(z_1, \\ldots, z_N)$ and calculating\n", + "\n", + "$$\n", + "C({\\bf z}) = {\\bf z}^\\top \\cdot Q \\cdot {\\bf z}\n", + "$$\n", + "\n", + "Determining the cost of a given bitstring takes polynomial time. The average estimate is then used in the classical loop to optimize the variational parameters $\\tau$ and $t$." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def get_cost_colouring(bitstring, Q):\n", + " z = np.array(list(bitstring), dtype=int)\n", + " cost = z.T @ Q @ z\n", + " return cost\n", + "\n", + "\n", + "def get_cost(counter, Q):\n", + " cost = sum(counter[key] * get_cost_colouring(key, Q) for key in counter)\n", + " return cost / sum(counter.values()) # Divide by total samples" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To perform a minimization loop, we define the following function that will be called at each step by SciPy. `*args` enables to pass the QUBO value, and `params` contains the trial value to score, which changes at each step." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def func(param, *args):\n", + " Q = args[0]\n", + " C = quantum_loop(param)\n", + " cost = get_cost(C, Q)\n", + " return cost" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QAOA for depth $p = 2$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now use a classical optimizer `minimize` in order to find the best variational parameters. This function takes as arguments `func`, the QUBO $Q$ and an initial point `x0` for the simplex in Nelder-Mead minimization. As the optimizer might get trapped in local minima, we repeat the optimization 20 times and select the parameters that yield the best approximation ratio.\n", + "\n", + "Note: the next cell may take up to 2 minutes to complete." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "scores = []\n", + "params = []\n", + "for repetition in range(20):\n", + " guess = {\n", + " \"t\": np.random.uniform(1, 10, LAYERS),\n", + " \"s\": np.random.uniform(1, 10, LAYERS),\n", + " }\n", + "\n", + " try:\n", + " res = minimize(\n", + " func,\n", + " args=Q,\n", + " x0=np.r_[guess[\"t\"], guess[\"s\"]],\n", + " method=\"Nelder-Mead\",\n", + " tol=1e-5,\n", + " options={\"maxiter\": 10},\n", + " )\n", + " scores.append(res.fun)\n", + " params.append(res.x)\n", + " except Exception as e:\n", + " pass" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now plot the sample that we woud obtain using the optimal variational parameters." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "optimal_count_dict = quantum_loop(params[np.argmin(scores)])\n", + "plot_distribution(optimal_count_dict)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "QAOA is capable of finding good variational parameters $\\tau$ and $t$. Now, sampling from this final state $|\\psi(t_{f})\\rangle$ will return both optimal strings with high probability." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, using QAOA to solve the problem is not the best idea; it's difficult to yield a >90% quality solution without going to high depths of the QAOA, implying that the growing closed-loop optimization can rapidly become expensive, with no guarantee of convergence. We therefore propose another approach called the Quantum Adiabatic Algorithm (QAA). This fast, reliant and exclusively analog method shows optimal convergence to the solution." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quantum Adiabatic Algorithm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The idea behind the adiabatic algorithm (see [Albash, Lidar, 2018](https://arxiv.org/pdf/1611.04471.pdf)) is to slowly evolve the system from an easy-to-prepare groundstate to the groundstate of $H_Q$. If done slowly enough, the system of atoms stays in the instantaneous ground-state.\n", + "\n", + "In our case, we continuously vary the parameters $\\Omega(t), \\delta(t)$ in time, starting with $\\Omega(0)=0, \\delta(0)<0$ and ending with $\\Omega(0)=0, \\delta>0$. The ground-state of $H(0)$ corresponds to the initial state $|00000\\rangle$ and the ground-state of $H(t_f)$ corresponds to the ground-state of $H_Q$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "The Rydberg blockade radius is directly linked to the Rabi frequency $\\Omega$ and is obtained using `DigitalAnalogDevice.rydberg_blockade_radius()`. In this notebook, $\\Omega$ is initially fixed to a frequency of 1 rad/µs. We can therefore build the adjacency matrix $A$ of $G$ in the following way:" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure that we are not exciting the system to states that are too excited, we keep $\\Omega \\in [0, \\Omega_{\\text{max}}]$, and choose $\\Omega_{\\text{max}}$ as the median of the values of Q to ensures that the adiabatic path is efficient." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# We choose a median value between the min and the max\n", + "Omega = np.median(Q[Q > 0].flatten())\n", + "delta_0 = -5 # just has to be negative\n", + "delta_f = -delta_0 # just has to be positive\n", + "T = 4000 # time in ns, we choose a time long enough to ensure the propagation of information in the system" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "adiabatic_pulse = Pulse(\n", + " InterpolatedWaveform(T, [1e-9, Omega, 1e-9]),\n", + " InterpolatedWaveform(T, [delta_0, 0, delta_f]),\n", + " 0,\n", + ")\n", + "seq = Sequence(reg, DigitalAnalogDevice)\n", + "seq.declare_channel(\"ising\", \"rydberg_global\")\n", + "seq.add(adiabatic_pulse, \"ising\")\n", + "seq.draw()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "simul = QutipEmulator.from_sequence(seq)\n", + "results = simul.run()\n", + "final = results.get_final_state()\n", + "count_dict = results.sample_final_state()\n", + "print(results.sample_final_state())" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_distribution(count_dict)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See how fast and performant this method is! In only a few micro-seconds, we find an excellent solution." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How does the time evolution affect the quality of the results?" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "cost_qutip_emulator = []\n", + "for T in 1000 * np.linspace(1, 10, 10):\n", + " seq = Sequence(reg, DigitalAnalogDevice)\n", + " seq.declare_channel(\"ising\", \"rydberg_global\")\n", + " adiabatic_pulse = Pulse(\n", + " InterpolatedWaveform(T, [1e-9, Omega, 1e-9]),\n", + " InterpolatedWaveform(T, [delta_0, 0, delta_f]),\n", + " 0,\n", + " )\n", + " seq.add(adiabatic_pulse, \"ising\")\n", + " simul = QutipEmulator.from_sequence(seq)\n", + " results = simul.run()\n", + " final = results.get_final_state()\n", + " count_dict = results.sample_final_state()\n", + " cost_qutip_emulator.append(get_cost(count_dict, Q) / 3)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "def plot_cost(cost, title):\n", + " plt.figure(figsize=(12, 6))\n", + " plt.plot(range(1, 11), np.array(cost), \"--o\")\n", + " plt.xlabel(\"total time evolution (µs)\", fontsize=14)\n", + " plt.ylabel(\"cost\", fontsize=14)\n", + " plt.title(title, fontsize=16)\n", + " plt.show()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_cost(cost_qutip_emulator, \"Qutip emulator\")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## 4. Now let's run against Pasqal's emulator" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import json\n", + "\n", + "def prepare_input_data(seq):\n", + " input_data = {}\n", + " input_data[\"sequence_builder\"] = json.loads(seq.to_abstract_repr())\n", + " return json.dumps(input_data)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def submit_job(target, seq):\n", + " job = target.submit(\n", + " input_data=prepare_input_data(seq), \n", + " input_data_format=\"pasqal.pulser.v1\", \n", + " output_data_format=\"pasqal.pulser-results.v1\",\n", + " name=\"Pasqal sequence\",\n", + " input_params={\n", + " \"shots\": 10\n", + " }\n", + " )\n", + "\n", + " print(f\"Queued job: {job.id}\")\n", + " job.wait_until_completed()\n", + " print(f\"Job completed with state: {job.details.status}\")\n", + " result = job.get_results()\n", + "\n", + " return result" + ], + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Please set your `resource_id` and `location` below." + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from azure.quantum import Workspace\n", + "workspace = Workspace(\n", + " resource_id = \"\",\n", + " location = \"\",\n", + ")\n", + "target = workspace.get_targets(name=\"pasqal.sim.emu-tn\")\n", + "\n", + "if isinstance(target, list):\n", + " if len(target) == 0:\n", + " raise RuntimeError(\"No emulators found for that name\")\n", + " raise RuntimeError(target)\n", + "\n", + "# Open a session on the backend to group all jobs together\n", + "from azure.quantum.job.session import Session, SessionJobFailurePolicy \n", + "session = target.open_session(name=\"QUBO Problem\", job_failure_policy=SessionJobFailurePolicy.CONTINUE)\n", + "target.latest_session = session" + ], + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Note: the next cell may take up to 5 minutes to complete." + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Compute the time evolution on PASQAL emulator\n", + "cost_emutn = []\n", + "try:\n", + " for T in 1000 * np.linspace(1, 10, 10):\n", + " seq = Sequence(reg, DigitalAnalogDevice)\n", + " seq.declare_channel(\"ising\", \"rydberg_global\")\n", + " adiabatic_pulse = Pulse(\n", + " InterpolatedWaveform(T, [1e-9, Omega, 1e-9]),\n", + " InterpolatedWaveform(T, [delta_0, 0, delta_f]),\n", + " 0,\n", + " )\n", + " seq.add(adiabatic_pulse, \"ising\")\n", + "\n", + " from collections import Counter\n", + " histogram_results = submit_job(target, seq)\n", + " count_dict = Counter(histogram_results)\n", + "\n", + " cost_emutn.append(get_cost(count_dict, Q) / 3)\n", + "\n", + "except Exception as e:\n", + " print(e)\n", + "session.close()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "plot_cost(cost_emutn, \"Pasqal Tensor Network-based emulator\")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "#### Finally, let's compare the results of the two emulators" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "def plot_costs(title, costs):\n", + " plt.figure(figsize=(12, 6))\n", + " for label, cost in costs.items():\n", + " plt.plot(range(1, 11), np.array(cost), \"--o\", label=label)\n", + " plt.xlabel(\"total time evolution (µs)\", fontsize=14)\n", + " plt.ylabel(\"cost\", fontsize=14)\n", + " plt.title(title, fontsize=16)\n", + " plt.legend()\n", + " plt.show()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "costs = {\n", + " 'Qutip emulator': cost_qutip_emulator,\n", + " 'Pasqal emulator': cost_emutn\n", + "}\n", + "plot_costs(\"Qutip emulator vs Pasqal tensor network-based emulator\", costs)" + ], + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "celltoolbar": "Tags", + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + }, + "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.11.2" + }, + "vscode": { + "interpreter": { + "hash": "e088768f7ff7b4294439f8ed10f7eed9e3b885124bc20d9d06cc2a37b1883330" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 141f6b0082880e8a2165c65890e78f05a582a421 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Tue, 7 May 2024 12:41:29 +0200 Subject: [PATCH 02/18] Add hello-world sample from Pasqal --- samples/hello-world/HW-pasqal-pulser.ipynb | 357 +++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 samples/hello-world/HW-pasqal-pulser.ipynb diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb new file mode 100644 index 00000000..8b64c14f --- /dev/null +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -0,0 +1,357 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 👋🌍 Hello, world: Submit a Pulser job to Pasqal\n", + "\n", + "In this notebook, we'll review the basics of Azure Quantum by submitting a simple *job*, or quantum program, to [Pasqal](https://pasqal.com/). We will use [Pulser](https://pulser.readthedocs.io/) to express the quantum job in an analog way." + ], + "id": "8493267a95ed8cbd" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "## Submit a simple job to Pasqal using Azure Quantum\n", + "Azure Quantum provides several ways to express quantum programs. In this example we are using Pulser, the analog quantum computing library of Pasqal. All code in this example will be written in Python.\n", + "\n", + "Let's begin. When you see a code block, hover over it and click the triangle play-button to execute it. To avoid any compilation issues, this should be done in order from top to bottom." + ], + "id": "808523d9ed21b01b" + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "try:\n", + " import pulser\n", + "except ImportError:\n", + " !pip -q install pulser-core\n", + " print(\"Please restart kernel to complete the installation of pulser.\")" + ], + "id": "261dab3c8880aeee" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": "### 1. Connect to the Azure Quantum workspace\n", + "id": "658fc276abc991eb" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.quantum import Workspace\n", + "\n", + "workspace = Workspace(\n", + " resource_id = \"\",\n", + " location = \"\",\n", + ")" + ], + "id": "4f3047eec84a00af" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": "Let's see whether the Pasqal provider is enabled in this workspace with the following command:\n", + "id": "d3ad6d0abf4de566" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "target = workspace.get_targets(name=\"pasqal.sim.emu-tn\")\n", + "print(target)" + ], + "id": "95dbec7c0e4f59df" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "### ❕ Do you see `Target name=\"pasqal.sim.emu-tn\"` ? If so, you're ready to keep going.\n", + "\n", + "Don't see it? You may need to add Pasqal to your workspace to run this sample. Navigate to the **Providers** page in the portal and click **+Add** to add the Pasqal provider. Don't worry, there's a free credits plan available." + ], + "id": "64dc34808344e811" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "## Pasqal: The quantum provider\n", + "Azure Quantum partners with third-party companies to deliver solutions to quantum jobs. These company offerings are called *providers*. Each provider can offer multiple *targets* with different capabilities. See the table below for Pasqal's targets.\n", + "\n", + "| Target name | Target ID | Number of qubits | Description |\n", + "|-------------|----------------------|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|\n", + "| Emu-TN | `pasqal.sim.emu-tn` | 100 qubits 1D and 2D networks | Simulates the time-evolution of a quantum state using the Schrödinger equation corresponding to the actions that the lasers perform. |\n", + "| Fresnel1 | `pasqal.qpu.fresnel` | 100 qubits | PASQAL's neutral atoms quantum computer. |\n", + "\n", + "For this example, we will use `pasqal.sim.emu-tn`. To learn more about Pasqal's targets, check out our [documentation](https://learn.microsoft.com/azure/quantum/provider-pasqal)." + ], + "id": "32fb070cacaba76d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Build the quantum program\n", + "\n", + "Let's create a simple Pulser sequence to run on an array (register) of neutral atoms." + ], + "id": "a58addf868b244dd" + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "import math\n", + "import pulser\n", + "\n", + "reg = pulser.Register.rectangle(1, 2, spacing=8, prefix=\"atom\")\n", + "reg.draw()\n", + "\n", + "duration = 1000 # Typical: ~1 µsec\n", + "pi_pulse = pulser.Pulse.ConstantDetuning(\n", + " pulser.BlackmanWaveform(duration, math.pi), 0.0, 0.0\n", + ")\n", + "\n", + "seq = pulser.Sequence(reg, pulser.DigitalAnalogDevice)\n", + "\n", + "seq.declare_channel(\"ryd\", \"rydberg_local\", \"atom0\")\n", + "\n", + "seq.add(pi_pulse, \"ryd\")\n", + "seq.target(\"atom1\", \"ryd\")\n", + "seq.add(pi_pulse, \"ryd\")\n", + "seq.measure()\n", + "\n", + "seq.draw()" + ], + "id": "9681bbcd96eee008" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "Each pulse acts on a set of atoms at a certain moment of time. The entire `Sequence` is stored by Pulser and can then be either simulated or sent to a real device.\n", + "\n", + "We've just built a sequence sending the same π-pulse to two atoms, sequentially, using the same channel. With Pasqal's idealized simulator, we will be able to simulate the quantum evolution of both atoms over time." + ], + "id": "c3d23fc53155e243" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "### 3. Submit the quantum program to Pasqal", + "id": "95f8578dfb2660f4" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using the Pasqal simulator target, call \"run\" to submit the job. We'll\n", + "# use 10 repetitions (simulated runs).\n", + "input_args = dict(\n", + " input_data=dict(sequence_builder=seq.to_abstract_repr()),\n", + " input_params={\"count\": 100}, # Number of shots\n", + ")\n", + "\n", + "job = target.submit(\n", + " **input_args, \n", + " input_data_format=\"pasqal.pulser.v1\", \n", + " output_data_format=\"pasqal.pulser-results.v1\",\n", + " name=\"Pasqal sequence\",\n", + ")\n", + "\n", + "# Print the job ID.\n", + "print(\"Job id:\", job.id)" + ], + "id": "ed2e79aac388baa9" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "The job ID can be used to retrieve the results later using the [get_job method](https://learn.microsoft.com/python/azure-quantum/azure.quantum.workspace?#azure-quantum-workspace-get-job) or by viewing it under the **Job management** section of the portal." + ], + "id": "bb4c8c91778444da" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "### 4. Obtain the job results\n", + "Let's await the job execution by calling `job.get_results()`. This may take a minute or so ⏳. Your job is being packaged and sent to Pasqal, where it will wait its turn to be run." + ], + "id": "f2111a90ca2d482a" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Await job results.\n", + "print(\"Awaiting job results...\")\n", + "job.wait_until_completed()\n", + "result = job.get_results()\n", + "\n", + "# To view each measured qubit state, you can print the result.\n", + "print(\"Job finished. Measure count for each state:\")\n", + "print(result)" + ], + "id": "4bf1813d0e966f42" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": "**See the result above? Congratulations, you've submitted a Pasqal job with Azure Quantum! 👏**", + "id": "a13268e76598db7b" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5. Estimate costs\n", + "\n", + "To estimate the costs of running this program on a simulator or hardware, you can use the `target.estimate_cost` method.\n", + "\n", + "Note that cost emulation for Pasqal emulators has not been implemented yet." + ], + "id": "c873638c19d5265c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "estimate = target.estimate_cost(**input_args)\n", + "print(estimate)" + ], + "id": "e6c76075e7c69fb9" + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "### 6. Next steps\n", + "Next, you can try running a program on Pasqal's hardware target. Just replace `pasqal.sim.emu-tn` with `pasqal.qpu.fresnel`. Don't worry - your work here is automatically saved.\n", + "\n", + "To learn more about submitting Pulser sequences to Azure Quantum, review [this documentation](https://learn.microsoft.com/en-us/azure/quantum/quickstart-microsoft-provider-format#platform-pasqal).\n", + "\n", + "To learn more about job pricing, review the [Azure Quantum documentation on job costs](https://learn.microsoft.com/en-us/azure/quantum/pricing#pasqal)." + ], + "id": "2e02e1b62a75939e" + } + ], + "metadata": { + "kernel_info": { + "name": "python3" + }, + "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.7.3" + }, + "nteract": { + "version": "nteract-front-end@1.0.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 7380d945c13b368f5acc721aac6aa809583f8a93 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Tue, 7 May 2024 16:49:43 +0200 Subject: [PATCH 03/18] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nicolas Ménétrier --- samples/hello-world/HW-pasqal-pulser.ipynb | 2 +- .../qaoa-for-quadratic-unconstrained-binary-optimization.ipynb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 8b64c14f..821571ed 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -211,7 +211,7 @@ "# use 10 repetitions (simulated runs).\n", "input_args = dict(\n", " input_data=dict(sequence_builder=seq.to_abstract_repr()),\n", - " input_params={\"count\": 100}, # Number of shots\n", + " input_params={\"shots\": 100}, # Number of shots\n", ")\n", "\n", "job = target.submit(\n", diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index a6996bf8..daae463d 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -17,6 +17,7 @@ "\n", "try:\n", " import pulser\n", + " import pulser_simulation\n", "except ImportError:\n", " # This installs two python packages:\n", " # - pulser (core package)\n", @@ -24,7 +25,7 @@ " # If you don't need simulation in your own workflow, you may run instead:\n", " # !pip -q install pulser-core\n", " # https://pulser.readthedocs.io/en/stable/installation.html\n", - " !pip -q install pulser" + " !pip -q install --upgrade pulser" ], "outputs": [], "execution_count": null From 55d8c90ccadc4d8ea5712f22ee9ebff80ddeff05 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Tue, 7 May 2024 16:53:50 +0200 Subject: [PATCH 04/18] Add hint about where to find resource_id and location --- samples/hello-world/HW-pasqal-pulser.ipynb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 821571ed..a569d097 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -61,6 +61,8 @@ "source": [ "from azure.quantum import Workspace\n", "\n", + "# Your `resource_id` and `location` should be available on the Overview page of your Quantum Workspace.\n", + "\n", "workspace = Workspace(\n", " resource_id = \"\",\n", " location = \"\",\n", From 233f1771cb8ba060e2de8a289c641589f6539aa2 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Tue, 14 May 2024 09:31:27 +0200 Subject: [PATCH 05/18] Apply suggestions from a-corni Co-authored-by: Antoine Cornillot <61453516+a-corni@users.noreply.github.com> --- samples/hello-world/HW-pasqal-pulser.ipynb | 6 +++--- ...oa-for-quadratic-unconstrained-binary-optimization.ipynb | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index a569d097..501ac77c 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -6,7 +6,7 @@ "source": [ "# 👋🌍 Hello, world: Submit a Pulser job to Pasqal\n", "\n", - "In this notebook, we'll review the basics of Azure Quantum by submitting a simple *job*, or quantum program, to [Pasqal](https://pasqal.com/). We will use [Pulser](https://pulser.readthedocs.io/) to express the quantum job in an analog way." + "In this notebook, we'll review the basics of Azure Quantum by submitting a simple *job*, or quantum program, to [Pasqal](https://pasqal.com/). We will use [Pulser](https://pulser.readthedocs.io/) to define a quantum job that can run on Pasqal's hardware." ], "id": "8493267a95ed8cbd" }, @@ -21,7 +21,7 @@ }, "source": [ "## Submit a simple job to Pasqal using Azure Quantum\n", - "Azure Quantum provides several ways to express quantum programs. In this example we are using Pulser, the analog quantum computing library of Pasqal. All code in this example will be written in Python.\n", + "Azure Quantum provides several ways to express quantum programs. In this example we are using Pulser, the quantum computing library of Pasqal to program arrays of neutral atoms. All code in this example will be written in Python.\n", "\n", "Let's begin. When you see a code block, hover over it and click the triangle play-button to execute it. To avoid any compilation issues, this should be done in order from top to bottom." ], @@ -147,7 +147,7 @@ "source": [ "### 2. Build the quantum program\n", "\n", - "Let's create a simple Pulser sequence to run on an array (register) of neutral atoms." + "Let's create a simple Pulser Sequence to run on an array of neutral atoms (a Register)." ], "id": "a58addf868b244dd" }, diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index daae463d..c12e5840 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -484,7 +484,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "However, using QAOA to solve the problem is not the best idea; it's difficult to yield a >90% quality solution without going to high depths of the QAOA, implying that the growing closed-loop optimization can rapidly become expensive, with no guarantee of convergence. We therefore propose another approach called the Quantum Adiabatic Algorithm (QAA). This fast, reliant and exclusively analog method shows optimal convergence to the solution." + "However, using QAOA to solve the problem is not the best idea; it's difficult to yield a >90% quality solution without going to high depths of the QAOA, implying that the growing closed-loop optimization can rapidly become expensive, with no guarantee of convergence. + We therefore propose to investigate another approach called the Quantum Adiabatic Algorithm (QAA). This method is exclusively analog and can be well implemented on Pasqal's Fresnel machines that are tailored to solve analog quantum jobs. It is fast, reliable and shows optimal convergence to the solution." ] }, { From bc65571629cbfb243e818f26e9e20f6362912e93 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Wed, 22 May 2024 14:13:57 +0200 Subject: [PATCH 06/18] Add requirements-pulser.txt and show cost estimation of real QPU --- azure-quantum/requirements-pulser.txt | 1 + samples/hello-world/HW-pasqal-pulser.ipynb | 19 +++---------------- 2 files changed, 4 insertions(+), 16 deletions(-) create mode 100644 azure-quantum/requirements-pulser.txt diff --git a/azure-quantum/requirements-pulser.txt b/azure-quantum/requirements-pulser.txt new file mode 100644 index 00000000..769e0e87 --- /dev/null +++ b/azure-quantum/requirements-pulser.txt @@ -0,0 +1 @@ +pulser-core>=0.18,<0.19 \ No newline at end of file diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 501ac77c..54a94b62 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -27,20 +27,6 @@ ], "id": "808523d9ed21b01b" }, - { - "metadata": {}, - "cell_type": "code", - "outputs": [], - "execution_count": null, - "source": [ - "try:\n", - " import pulser\n", - "except ImportError:\n", - " !pip -q install pulser-core\n", - " print(\"Please restart kernel to complete the installation of pulser.\")" - ], - "id": "261dab3c8880aeee" - }, { "cell_type": "markdown", "metadata": { @@ -294,7 +280,7 @@ "\n", "To estimate the costs of running this program on a simulator or hardware, you can use the `target.estimate_cost` method.\n", "\n", - "Note that cost emulation for Pasqal emulators has not been implemented yet." + "Note that cost emulation for Pasqal emulators has not been implemented yet, so we show the cost of real QPUs below." ], "id": "c873638c19d5265c" }, @@ -304,7 +290,8 @@ "metadata": {}, "outputs": [], "source": [ - "estimate = target.estimate_cost(**input_args)\n", + "qpu_target = workspace.get_targets(name=\"pasqal.qpu.fresnel\")\n", + "estimate = qpu_target.estimate_cost(**input_args)\n", "print(estimate)" ], "id": "e6c76075e7c69fb9" From 6a3d902443fc934d3f180c465cef1de9063c30b8 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Wed, 22 May 2024 18:23:19 +0200 Subject: [PATCH 07/18] Remove redundant wait and fix link --- samples/hello-world/HW-pasqal-pulser.ipynb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 54a94b62..1c6a03a7 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -251,7 +251,6 @@ "source": [ "# Await job results.\n", "print(\"Awaiting job results...\")\n", - "job.wait_until_completed()\n", "result = job.get_results()\n", "\n", "# To view each measured qubit state, you can print the result.\n", @@ -309,7 +308,7 @@ "### 6. Next steps\n", "Next, you can try running a program on Pasqal's hardware target. Just replace `pasqal.sim.emu-tn` with `pasqal.qpu.fresnel`. Don't worry - your work here is automatically saved.\n", "\n", - "To learn more about submitting Pulser sequences to Azure Quantum, review [this documentation](https://learn.microsoft.com/en-us/azure/quantum/quickstart-microsoft-provider-format#platform-pasqal).\n", + "To learn more about submitting Pulser sequences to Azure Quantum, review [this documentation](https://learn.microsoft.com/en-us/azure/quantum/quickstart-microsoft-provider-format?tabs=tabid-portal%2Ctabid-pyquil#submit-a-circuit-to-pasqal-using-pulser-sdk).\n", "\n", "To learn more about job pricing, review the [Azure Quantum documentation on job costs](https://learn.microsoft.com/en-us/azure/quantum/pricing#pasqal)." ], From 996c7eb9f96a90d5ccb0dd468e1ed7f409b81823 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Wed, 22 May 2024 19:25:12 +0200 Subject: [PATCH 08/18] Split one markdown string --- ...oa-for-quadratic-unconstrained-binary-optimization.ipynb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index c12e5840..69ab0c7a 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -484,8 +484,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "However, using QAOA to solve the problem is not the best idea; it's difficult to yield a >90% quality solution without going to high depths of the QAOA, implying that the growing closed-loop optimization can rapidly become expensive, with no guarantee of convergence. - We therefore propose to investigate another approach called the Quantum Adiabatic Algorithm (QAA). This method is exclusively analog and can be well implemented on Pasqal's Fresnel machines that are tailored to solve analog quantum jobs. It is fast, reliable and shows optimal convergence to the solution." + "However, using QAOA to solve the problem is not the best idea; it's difficult to yield a >90% quality solution without going to high depths of the QAOA, implying that the growing closed-loop optimization can rapidly become expensive, with no guarantee of convergence. ", + "We therefore propose to investigate another approach called the Quantum Adiabatic Algorithm (QAA). ", + "This method is exclusively analog and can be well implemented on Pasqal's Fresnel machines that are tailored to solve analog quantum jobs. ", + "It is fast, reliable and shows optimal convergence to the solution." ] }, { From 35b2fd2f658179cbe442ebf6fac79ce218b9aaa7 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Thu, 23 May 2024 10:31:48 +0200 Subject: [PATCH 09/18] Align targets listing with other HW notebooks --- samples/hello-world/HW-pasqal-pulser.ipynb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 1c6a03a7..263bb3a9 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -84,8 +84,9 @@ }, "outputs": [], "source": [ - "target = workspace.get_targets(name=\"pasqal.sim.emu-tn\")\n", - "print(target)" + "print(\"This workspace's targets:\")\n", + "for target in workspace.get_targets():\n", + " print(\"-\", target.name)" ], "id": "95dbec7c0e4f59df" }, @@ -99,7 +100,7 @@ } }, "source": [ - "### ❕ Do you see `Target name=\"pasqal.sim.emu-tn\"` ? If so, you're ready to keep going.\n", + "### ❕ Do you see pasqal.sim.emu-tn in your list of targets? If so, you're ready to keep going.\n", "\n", "Don't see it? You may need to add Pasqal to your workspace to run this sample. Navigate to the **Providers** page in the portal and click **+Add** to add the Pasqal provider. Don't worry, there's a free credits plan available." ], From 9f7cd63ba53e1ecd90eb12c473a86cfc1bdb960e Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Thu, 23 May 2024 11:02:58 +0200 Subject: [PATCH 10/18] Remove "price estimation" step as it is not implemented by Pasqal --- samples/hello-world/HW-pasqal-pulser.ipynb | 26 +--------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 263bb3a9..780b6d62 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -272,30 +272,6 @@ "source": "**See the result above? Congratulations, you've submitted a Pasqal job with Azure Quantum! 👏**", "id": "a13268e76598db7b" }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5. Estimate costs\n", - "\n", - "To estimate the costs of running this program on a simulator or hardware, you can use the `target.estimate_cost` method.\n", - "\n", - "Note that cost emulation for Pasqal emulators has not been implemented yet, so we show the cost of real QPUs below." - ], - "id": "c873638c19d5265c" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "qpu_target = workspace.get_targets(name=\"pasqal.qpu.fresnel\")\n", - "estimate = qpu_target.estimate_cost(**input_args)\n", - "print(estimate)" - ], - "id": "e6c76075e7c69fb9" - }, { "cell_type": "markdown", "metadata": { @@ -306,7 +282,7 @@ } }, "source": [ - "### 6. Next steps\n", + "### 5. Next steps\n", "Next, you can try running a program on Pasqal's hardware target. Just replace `pasqal.sim.emu-tn` with `pasqal.qpu.fresnel`. Don't worry - your work here is automatically saved.\n", "\n", "To learn more about submitting Pulser sequences to Azure Quantum, review [this documentation](https://learn.microsoft.com/en-us/azure/quantum/quickstart-microsoft-provider-format?tabs=tabid-portal%2Ctabid-pyquil#submit-a-circuit-to-pasqal-using-pulser-sdk).\n", From fbf03cf5bf99f5a88ec763a5e1c669afb1fceb10 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 27 May 2024 15:24:06 +0200 Subject: [PATCH 11/18] Fix error 422 + remove unnecessary import --- samples/hello-world/HW-pasqal-pulser.ipynb | 1 + ...ic-unconstrained-binary-optimization.ipynb | 22 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 780b6d62..d38905cd 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -203,6 +203,7 @@ " input_params={\"shots\": 100}, # Number of shots\n", ")\n", "\n", + "target = workspace.get_targets(name=\"pasqal.sim.emu-tn\")\n", "job = target.submit(\n", " **input_args, \n", " input_data_format=\"pasqal.pulser.v1\", \n", diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index 69ab0c7a..dc3a1d6f 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -8,28 +8,6 @@ "# QAOA and QAA to solve a QUBO problem" ] }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "# Restart the kernel once you have run this cell\n", - "!pip install -U -q azure-quantum\n", - "\n", - "try:\n", - " import pulser\n", - " import pulser_simulation\n", - "except ImportError:\n", - " # This installs two python packages:\n", - " # - pulser (core package)\n", - " # - pulser_simulation\n", - " # If you don't need simulation in your own workflow, you may run instead:\n", - " # !pip -q install pulser-core\n", - " # https://pulser.readthedocs.io/en/stable/installation.html\n", - " !pip -q install --upgrade pulser" - ], - "outputs": [], - "execution_count": null - }, { "cell_type": "code", "metadata": {}, From 152238a75fda06021dcb00ae383b52711ee69b8f Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 17:25:48 +0200 Subject: [PATCH 12/18] Inline one dict + fix one typo in number of shots --- samples/hello-world/HW-pasqal-pulser.ipynb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index d38905cd..0587718e 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -197,15 +197,11 @@ "outputs": [], "source": [ "# Using the Pasqal simulator target, call \"run\" to submit the job. We'll\n", - "# use 10 repetitions (simulated runs).\n", - "input_args = dict(\n", - " input_data=dict(sequence_builder=seq.to_abstract_repr()),\n", - " input_params={\"shots\": 100}, # Number of shots\n", - ")\n", - "\n", + "# use 100 repetitions (simulated runs).\n", "target = workspace.get_targets(name=\"pasqal.sim.emu-tn\")\n", "job = target.submit(\n", - " **input_args, \n", + " input_data=dict(sequence_builder=seq.to_abstract_repr()),\n", + " input_params={\"shots\": 100}, # Number of shots\n", " input_data_format=\"pasqal.pulser.v1\", \n", " output_data_format=\"pasqal.pulser-results.v1\",\n", " name=\"Pasqal sequence\",\n", From 18b1c34062c0f6e27b7422258a8216df39e0be56 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 17:34:40 +0200 Subject: [PATCH 13/18] Depend on full pulser + Remove some redundant imports --- azure-quantum/requirements-pulser.txt | 2 +- ...oa-for-quadratic-unconstrained-binary-optimization.ipynb | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/azure-quantum/requirements-pulser.txt b/azure-quantum/requirements-pulser.txt index 769e0e87..01a9065c 100644 --- a/azure-quantum/requirements-pulser.txt +++ b/azure-quantum/requirements-pulser.txt @@ -1 +1 @@ -pulser-core>=0.18,<0.19 \ No newline at end of file +pulser>=0.18,<0.19 \ No newline at end of file diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index dc3a1d6f..a182e78f 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -589,9 +589,6 @@ "cell_type": "code", "metadata": {}, "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", "def plot_cost(cost, title):\n", " plt.figure(figsize=(12, 6))\n", " plt.plot(range(1, 11), np.array(cost), \"--o\")\n", @@ -738,9 +735,6 @@ "cell_type": "code", "metadata": {}, "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", "def plot_costs(title, costs):\n", " plt.figure(figsize=(12, 6))\n", " for label, cost in costs.items():\n", From 9fff8e3c29db24bf4f2788ac57d30fc5772e2f24 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 17:37:31 +0200 Subject: [PATCH 14/18] Move introduction up --- ...ic-unconstrained-binary-optimization.ipynb | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index a182e78f..a8b41eae 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -8,22 +8,6 @@ "# QAOA and QAA to solve a QUBO problem" ] }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from pulser import Pulse, Sequence, Register\n", - "from pulser_simulation import QutipEmulator\n", - "from pulser.devices import DigitalAnalogDevice\n", - "from pulser.waveforms import InterpolatedWaveform\n", - "from scipy.optimize import minimize\n", - "from scipy.spatial.distance import pdist, squareform" - ], - "outputs": [], - "execution_count": null - }, { "cell_type": "markdown", "metadata": {}, @@ -46,6 +30,22 @@ "In this tutorial, we will demonstrate how a QUBO instance can be mapped and solved using neutral atoms." ] }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from pulser import Pulse, Sequence, Register\n", + "from pulser_simulation import QutipEmulator\n", + "from pulser.devices import DigitalAnalogDevice\n", + "from pulser.waveforms import InterpolatedWaveform\n", + "from scipy.optimize import minimize\n", + "from scipy.spatial.distance import pdist, squareform" + ] + }, { "cell_type": "markdown", "metadata": {}, From 8355bbcd9c1ac7c92253abdb6d8703a1bd79ab51 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 17:38:49 +0200 Subject: [PATCH 15/18] Put shots param on a single line + increase it to 100 --- ...qaoa-for-quadratic-unconstrained-binary-optimization.ipynb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index a8b41eae..0c667d6a 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -638,9 +638,7 @@ " input_data_format=\"pasqal.pulser.v1\", \n", " output_data_format=\"pasqal.pulser-results.v1\",\n", " name=\"Pasqal sequence\",\n", - " input_params={\n", - " \"shots\": 10\n", - " }\n", + " input_params={\"shots\": 100}\n", " )\n", "\n", " print(f\"Queued job: {job.id}\")\n", From 673792258e05936003237d94a038b4824e8b2acf Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 17:40:00 +0200 Subject: [PATCH 16/18] Remove assignment to target.latest_session --- .../qaoa-for-quadratic-unconstrained-binary-optimization.ipynb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index 0c667d6a..4da7ae27 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -674,8 +674,7 @@ "\n", "# Open a session on the backend to group all jobs together\n", "from azure.quantum.job.session import Session, SessionJobFailurePolicy \n", - "session = target.open_session(name=\"QUBO Problem\", job_failure_policy=SessionJobFailurePolicy.CONTINUE)\n", - "target.latest_session = session" + "session = target.open_session(name=\"QUBO Problem\", job_failure_policy=SessionJobFailurePolicy.CONTINUE)" ], "outputs": [], "execution_count": null From 332e85df58bad01e99a93e1adc171fe735ae0dd2 Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 17:55:00 +0200 Subject: [PATCH 17/18] Use context manager to close session --- ...ic-unconstrained-binary-optimization.ipynb | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index 4da7ae27..d666a04e 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -670,11 +670,7 @@ "if isinstance(target, list):\n", " if len(target) == 0:\n", " raise RuntimeError(\"No emulators found for that name\")\n", - " raise RuntimeError(target)\n", - "\n", - "# Open a session on the backend to group all jobs together\n", - "from azure.quantum.job.session import Session, SessionJobFailurePolicy \n", - "session = target.open_session(name=\"QUBO Problem\", job_failure_policy=SessionJobFailurePolicy.CONTINUE)" + " raise RuntimeError(target)" ], "outputs": [], "execution_count": null @@ -688,28 +684,31 @@ "cell_type": "code", "metadata": {}, "source": [ - "# Compute the time evolution on PASQAL emulator\n", - "cost_emutn = []\n", - "try:\n", - " for T in 1000 * np.linspace(1, 10, 10):\n", - " seq = Sequence(reg, DigitalAnalogDevice)\n", - " seq.declare_channel(\"ising\", \"rydberg_global\")\n", - " adiabatic_pulse = Pulse(\n", - " InterpolatedWaveform(T, [1e-9, Omega, 1e-9]),\n", - " InterpolatedWaveform(T, [delta_0, 0, delta_f]),\n", - " 0,\n", - " )\n", - " seq.add(adiabatic_pulse, \"ising\")\n", - "\n", - " from collections import Counter\n", - " histogram_results = submit_job(target, seq)\n", - " count_dict = Counter(histogram_results)\n", - "\n", - " cost_emutn.append(get_cost(count_dict, Q) / 3)\n", + "# Open a session on the backend to group all jobs together\n", + "from azure.quantum.job.session import SessionJobFailurePolicy\n", "\n", - "except Exception as e:\n", - " print(e)\n", - "session.close()" + "with target.open_session(name=\"QUBO Problem\", job_failure_policy=SessionJobFailurePolicy.CONTINUE) as session:\n", + " # Compute the time evolution on PASQAL emulator\n", + " cost_emutn = []\n", + " try:\n", + " for T in 1000 * np.linspace(1, 10, 10):\n", + " seq = Sequence(reg, DigitalAnalogDevice)\n", + " seq.declare_channel(\"ising\", \"rydberg_global\")\n", + " adiabatic_pulse = Pulse(\n", + " InterpolatedWaveform(T, [1e-9, Omega, 1e-9]),\n", + " InterpolatedWaveform(T, [delta_0, 0, delta_f]),\n", + " 0,\n", + " )\n", + " seq.add(adiabatic_pulse, \"ising\")\n", + " \n", + " from collections import Counter\n", + " histogram_results = submit_job(target, seq)\n", + " count_dict = Counter(histogram_results)\n", + " \n", + " cost_emutn.append(get_cost(count_dict, Q) / 3)\n", + " \n", + " except Exception as e:\n", + " print(e)" ], "outputs": [], "execution_count": null From 3d481f5fb216d12d602021fcb0820f7569d41f7b Mon Sep 17 00:00:00 2001 From: Arnaud Peloquin Date: Mon, 24 Jun 2024 18:27:58 +0200 Subject: [PATCH 18/18] Really inline number of shots --- samples/hello-world/HW-pasqal-pulser.ipynb | 2 +- .../qaoa-for-quadratic-unconstrained-binary-optimization.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/hello-world/HW-pasqal-pulser.ipynb b/samples/hello-world/HW-pasqal-pulser.ipynb index 0587718e..854d8b38 100644 --- a/samples/hello-world/HW-pasqal-pulser.ipynb +++ b/samples/hello-world/HW-pasqal-pulser.ipynb @@ -201,7 +201,7 @@ "target = workspace.get_targets(name=\"pasqal.sim.emu-tn\")\n", "job = target.submit(\n", " input_data=dict(sequence_builder=seq.to_abstract_repr()),\n", - " input_params={\"shots\": 100}, # Number of shots\n", + " shots=100,\n", " input_data_format=\"pasqal.pulser.v1\", \n", " output_data_format=\"pasqal.pulser-results.v1\",\n", " name=\"Pasqal sequence\",\n", diff --git a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb index d666a04e..188b149b 100644 --- a/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb +++ b/samples/quantum-approximation-optimization/qaoa-for-quadratic-unconstrained-binary-optimization.ipynb @@ -638,7 +638,7 @@ " input_data_format=\"pasqal.pulser.v1\", \n", " output_data_format=\"pasqal.pulser-results.v1\",\n", " name=\"Pasqal sequence\",\n", - " input_params={\"shots\": 100}\n", + " shots=100,\n", " )\n", "\n", " print(f\"Queued job: {job.id}\")\n",