From a7c4765295d53c0b4804620f99b9837efbf8a3ed Mon Sep 17 00:00:00 2001 From: HGSilveri Date: Fri, 20 Sep 2024 12:45:20 +0200 Subject: [PATCH 1/5] Bump version to 0.21dev0 --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 5a03fb737..870603922 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.20.0 +0.21dev0 From a90dec02262be662b920449af4b0f6227fa58ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Silv=C3=A9rio?= <29920212+HGSilveri@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:02:12 +0200 Subject: [PATCH 2/5] Clarifications on the QUBO tutorial (#731) * Clarifications on the QUBO tutorial * Clarifying embedding section --- ...QAOA and QAA to solve a QUBO problem.ipynb | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/tutorials/applications/QAOA and QAA to solve a QUBO problem.ipynb b/tutorials/applications/QAOA and QAA to solve a QUBO problem.ipynb index bee1d5979..624fc8948 100644 --- a/tutorials/applications/QAOA and QAA to solve a QUBO problem.ipynb +++ b/tutorials/applications/QAOA and QAA to solve a QUBO problem.ipynb @@ -40,10 +40,7 @@ "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." + "$$f(z) = z^{T}Qz$$ " ] }, { @@ -74,7 +71,17 @@ "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." + "In this tutorial, we will demonstrate how this QUBO instance can be mapped and solved using neutral atoms. For reasons that will become apparent further along, this QUBO instance is particularly amenable to embedding on a neutral-atom device since:\n", + "\n", + "1. All the off-diagonal terms are positive.\n", + "2. The diagonal terms are all equal." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Furthermore, 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." ] }, { @@ -115,7 +122,16 @@ "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$:" + "The key idea is to encode the off-diagonal terms of $Q$ by using the Rydberg interaction between atoms. Recalling that the interaction between two atoms is given by \n", + "$$\n", + "U_{ij}=C_6/r_{ij}^6,\n", + "$$\n", + "we note that \n", + "\n", + "1. The term is strictly positive, which is why it matters that our off-diagonal terms are too.\n", + "2. Its magnitude depends on the pairwise distance between atoms $i$ and $j$, $r_{ij}$. \n", + "\n", + "As such, we attempt a simple minimization procedure to find the optimal positions of the atoms in the Register that replicate best the off-diagonal terms of $Q$:" ] }, { @@ -124,11 +140,10 @@ "metadata": {}, "outputs": [], "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", + "def evaluate_mapping(new_coords, Q):\n", + " \"\"\"Cost function to minimize. Ideally, the pairwise distances are conserved.\"\"\"\n", + " new_coords = np.reshape(new_coords, (len(Q), 2))\n", + " # computing the matrix of the distances between all coordinate pairs\n", " new_Q = squareform(\n", " DigitalAnalogDevice.interaction_coeff / pdist(new_coords) ** 6\n", " )\n", @@ -141,14 +156,13 @@ "metadata": {}, "outputs": [], "source": [ - "shape = (len(Q), 2)\n", "costs = []\n", "np.random.seed(0)\n", - "x0 = np.random.random(shape).flatten()\n", + "x0 = np.random.random(len(Q) * 2)\n", "res = minimize(\n", " evaluate_mapping,\n", " x0,\n", - " args=(Q, shape),\n", + " args=(Q,),\n", " method=\"Nelder-Mead\",\n", " tol=1e-6,\n", " options={\"maxiter\": 200000, \"maxfev\": None},\n", @@ -178,6 +192,13 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, this simple procedure was enough to give a good and valid embedding but it will not always be so. For QUBO instances that are not as easy to embbed as this one, more complex embedding strategies are required." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -193,9 +214,9 @@ "\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", + "In the case where our mapping of the atoms is perfect, the last sum replicates exactly the off-diagonal terms of $Q$. Additionally, since the diagonal terms are all the same, we can use a Rydberg global beam with an approriate detuning $\\delta$ (otherwise, some kind of local addressability capabilities would be necessary).\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." + "As such, the next step is to prepare the ground-state of $H_Q$ to output the optimal bitstrings. 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." ] }, { @@ -481,13 +502,6 @@ "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": {}, From cea1f7a170ee11cc9edcb55e69a61035489ebddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Silv=C3=A9rio?= <29920212+HGSilveri@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:27:18 +0200 Subject: [PATCH 3/5] Isolating pulser-pasqal version and release process (#733) * Isolating pulser-pasqal version and release process * Delete version test --- packages.txt | 3 +-- pulser-pasqal/VERSION.txt | 1 + pulser-pasqal/pulser_pasqal/_version.py | 3 +-- pulser-pasqal/requirements.txt | 1 + pulser-pasqal/setup.py | 5 +---- setup.py | 7 ++++++- tests/test_pasqal.py | 5 ----- 7 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 pulser-pasqal/VERSION.txt diff --git a/packages.txt b/packages.txt index c76983836..888e92d31 100644 --- a/packages.txt +++ b/packages.txt @@ -1,3 +1,2 @@ pulser-core -pulser-simulation -pulser-pasqal \ No newline at end of file +pulser-simulation \ No newline at end of file diff --git a/pulser-pasqal/VERSION.txt b/pulser-pasqal/VERSION.txt new file mode 100644 index 000000000..9d2632160 --- /dev/null +++ b/pulser-pasqal/VERSION.txt @@ -0,0 +1 @@ +0.20.1 \ No newline at end of file diff --git a/pulser-pasqal/pulser_pasqal/_version.py b/pulser-pasqal/pulser_pasqal/_version.py index 7b37d81be..0ad1c76b2 100644 --- a/pulser-pasqal/pulser_pasqal/_version.py +++ b/pulser-pasqal/pulser_pasqal/_version.py @@ -13,8 +13,7 @@ # limitations under the License. from pathlib import PurePath -# Sets the version to the same as 'pulser'. -version_file_path = PurePath(__file__).parent.parent.parent / "VERSION.txt" +version_file_path = PurePath(__file__).parent.parent / "VERSION.txt" with open(version_file_path, "r", encoding="utf-8") as f: __version__ = f.read().strip() diff --git a/pulser-pasqal/requirements.txt b/pulser-pasqal/requirements.txt index 7b3f97f80..12fb10e9b 100644 --- a/pulser-pasqal/requirements.txt +++ b/pulser-pasqal/requirements.txt @@ -1,2 +1,3 @@ +pulser-core >= 0.20 pasqal-cloud ~= 0.12 backoff ~= 2.2 \ No newline at end of file diff --git a/pulser-pasqal/setup.py b/pulser-pasqal/setup.py index a202df7c8..631cd4d29 100644 --- a/pulser-pasqal/setup.py +++ b/pulser-pasqal/setup.py @@ -25,9 +25,7 @@ current_directory = Path(__file__).parent # Reads the version from the VERSION.txt file -with open( - current_directory.parent / "VERSION.txt", "r", encoding="utf-8" -) as f: +with open(current_directory / "VERSION.txt", "r", encoding="utf-8") as f: __version__ = f.read().strip() # Changes to the directory where setup.py is @@ -35,7 +33,6 @@ with open("requirements.txt", encoding="utf-8") as f: requirements = f.read().splitlines() -requirements.append(f"pulser-core=={__version__}") # Stashes the source code for the local version file local_version_fpath = Path(package_name) / "_version.py" diff --git a/setup.py b/setup.py index 2e094929e..5a739adc1 100644 --- a/setup.py +++ b/setup.py @@ -25,8 +25,13 @@ "`make dev-install` instead." ) +# Pulser packages not pinned to __version__ +requirements = [ + "pulser-pasqal", +] +# Adding packages pinned to __version__ with open("packages.txt", "r", encoding="utf-8") as f: - requirements = [f"{pkg.strip()}=={__version__}" for pkg in f.readlines()] + requirements += [f"{pkg.strip()}=={__version__}" for pkg in f.readlines()] # Just a meta-package that requires all pulser packages setup( diff --git a/tests/test_pasqal.py b/tests/test_pasqal.py index 6382f5898..bf7ee6f15 100644 --- a/tests/test_pasqal.py +++ b/tests/test_pasqal.py @@ -24,7 +24,6 @@ from pasqal_cloud.device.configuration import EmuFreeConfig, EmuTNConfig import pulser -import pulser_pasqal from pulser.backend.config import EmulatorConfig from pulser.backend.remote import ( BatchStatus, @@ -44,10 +43,6 @@ root = Path(__file__).parent.parent -def test_version(): - assert pulser_pasqal.__version__ == pulser.__version__ - - @dataclasses.dataclass class CloudFixture: pasqal_cloud: PasqalCloud From 04ba5a55d58bb0eaa7ce888377f0c704822dad32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Silv=C3=A9rio?= <29920212+HGSilveri@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:00:35 +0200 Subject: [PATCH 4/5] Restore backwards compatibility with `pulser-core<0.20` (#740) --- pulser-core/pulser/devices/_device_datacls.py | 8 +++++--- .../pulser/json/abstract_repr/deserializer.py | 12 +++++++++++- tests/test_abstract_repr.py | 19 ++++++++++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pulser-core/pulser/devices/_device_datacls.py b/pulser-core/pulser/devices/_device_datacls.py index 61629040d..9a1d44bd4 100644 --- a/pulser-core/pulser/devices/_device_datacls.py +++ b/pulser-core/pulser/devices/_device_datacls.py @@ -684,19 +684,21 @@ def _specs(self, for_docs: bool = False) -> str: ( "\t" + r"- Maximum :math:`\Omega`:" - + f" {ch.max_amp:.4g} rad/µs" + + f" {float(cast(float,ch.max_amp)):.4g} rad/µs" ), ( ( "\t" + r"- Maximum :math:`|\delta|`:" - + f" {ch.max_abs_detuning:.4g} rad/µs" + + f" {float(cast(float, ch.max_abs_detuning)):.4g}" + + " rad/µs" ) if not isinstance(ch, DMM) else ( "\t" + r"- Bottom :math:`|\delta|`:" - + f" {ch.bottom_detuning:.4g} rad/µs" + + f" {float(cast(float,ch.bottom_detuning)):.4g}" + + " rad/µs" ) ), f"\t- Minimum average amplitude: {ch.min_avg_amp} rad/µs", diff --git a/pulser-core/pulser/json/abstract_repr/deserializer.py b/pulser-core/pulser/json/abstract_repr/deserializer.py index 0f16c6dd1..d4bcfd2a1 100644 --- a/pulser-core/pulser/json/abstract_repr/deserializer.py +++ b/pulser-core/pulser/json/abstract_repr/deserializer.py @@ -436,8 +436,18 @@ def convert_complex(obj: Any) -> Any: noise_types = noise_model_obj.pop("noise_types") with_leakage = "leakage" in noise_types + relevant_params = pulser.NoiseModel._find_relevant_params( + noise_types, + noise_model_obj["state_prep_error"], + noise_model_obj["amp_sigma"], + noise_model_obj["laser_waist"], + ) - { # Handled separately + "eff_noise_rates", + "eff_noise_opers", + "with_leakage", + } noise_model = pulser.NoiseModel( - **noise_model_obj, + **{param: noise_model_obj[param] for param in relevant_params}, eff_noise_rates=tuple(eff_noise_rates), eff_noise_opers=tuple(eff_noise_opers), with_leakage=with_leakage, diff --git a/tests/test_abstract_repr.py b/tests/test_abstract_repr.py index 4a69c0eb7..7ef0bd12e 100644 --- a/tests/test_abstract_repr.py +++ b/tests/test_abstract_repr.py @@ -49,7 +49,7 @@ ) from pulser.json.abstract_repr.validation import validate_abstract_repr from pulser.json.exceptions import AbstractReprError, DeserializeDeviceError -from pulser.noise_model import NoiseModel +from pulser.noise_model import _LEGACY_DEFAULTS, NoiseModel from pulser.parametrized.decorators import parametrize from pulser.parametrized.paramobj import ParamObj from pulser.parametrized.variable import Variable, VariableItem @@ -194,7 +194,24 @@ def test_noise_model(noise_model: NoiseModel): re_noise_model = NoiseModel.from_abstract_repr(ser_noise_model_str) assert noise_model == re_noise_model + # Define parameters with defaults, like it was done before + # pulser-core < 0.20, and check deserialization still works ser_noise_model_obj = json.loads(ser_noise_model_str) + for param in ser_noise_model_obj: + if param in _LEGACY_DEFAULTS and ( + # Case where only laser_waist is defined and adding non-zero + # amp_sigma adds requirement for "runs" and "samples_per_run" + param != "amp_sigma" + or "amplitude" not in noise_model.noise_types + ): + ser_noise_model_obj[param] = ( + ser_noise_model_obj[param] or _LEGACY_DEFAULTS[param] + ) + assert ( + NoiseModel.from_abstract_repr(json.dumps(ser_noise_model_obj)) + == re_noise_model + ) + with pytest.raises(TypeError, match="must be given as a string"): NoiseModel.from_abstract_repr(ser_noise_model_obj) From 4d596536892d6c0871b925543d70d0cc8e5dd09a Mon Sep 17 00:00:00 2001 From: HGSilveri Date: Wed, 25 Sep 2024 15:02:12 +0200 Subject: [PATCH 5/5] Bump version to v0.20.1 --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 870603922..847e9aef6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.21dev0 +0.20.1