Skip to content

Commit

Permalink
Release v0.20.1
Browse files Browse the repository at this point in the history
- Unpins `pulser-pasqal` in the `pulser` metapackage
- Restores backwards compatibility with `pulser-core<0.20`
  • Loading branch information
HGSilveri authored Sep 25, 2024
2 parents 499d961 + 4d59653 commit 100ea7c
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 43 deletions.
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.20.0
0.20.1
3 changes: 1 addition & 2 deletions packages.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pulser-core
pulser-simulation
pulser-pasqal
pulser-simulation
8 changes: 5 additions & 3 deletions pulser-core/pulser/devices/_device_datacls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 11 additions & 1 deletion pulser-core/pulser/json/abstract_repr/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions pulser-pasqal/VERSION.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.20.1
3 changes: 1 addition & 2 deletions pulser-pasqal/pulser_pasqal/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
1 change: 1 addition & 0 deletions pulser-pasqal/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pulser-core >= 0.20
pasqal-cloud ~= 0.12
backoff ~= 2.2
5 changes: 1 addition & 4 deletions pulser-pasqal/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,14 @@
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
os.chdir(current_directory)

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"
Expand Down
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
19 changes: 18 additions & 1 deletion tests/test_abstract_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
5 changes: 0 additions & 5 deletions tests/test_pasqal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
60 changes: 37 additions & 23 deletions tutorials/applications/QAOA and QAA to solve a QUBO problem.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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$$ "
]
},
{
Expand Down Expand Up @@ -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."
]
},
{
Expand Down Expand Up @@ -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$:"
]
},
{
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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": {},
Expand All @@ -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."
]
},
{
Expand Down Expand Up @@ -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": {},
Expand Down

0 comments on commit 100ea7c

Please sign in to comment.