Skip to content

Commit 100ea7c

Browse files
authored
Release v0.20.1
- Unpins `pulser-pasqal` in the `pulser` metapackage - Restores backwards compatibility with `pulser-core<0.20`
2 parents 499d961 + 4d59653 commit 100ea7c

File tree

12 files changed

+83
-43
lines changed

12 files changed

+83
-43
lines changed

VERSION.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.20.0
1+
0.20.1

packages.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
pulser-core
2-
pulser-simulation
3-
pulser-pasqal
2+
pulser-simulation

pulser-core/pulser/devices/_device_datacls.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -684,19 +684,21 @@ def _specs(self, for_docs: bool = False) -> str:
684684
(
685685
"\t"
686686
+ r"- Maximum :math:`\Omega`:"
687-
+ f" {ch.max_amp:.4g} rad/µs"
687+
+ f" {float(cast(float,ch.max_amp)):.4g} rad/µs"
688688
),
689689
(
690690
(
691691
"\t"
692692
+ r"- Maximum :math:`|\delta|`:"
693-
+ f" {ch.max_abs_detuning:.4g} rad/µs"
693+
+ f" {float(cast(float, ch.max_abs_detuning)):.4g}"
694+
+ " rad/µs"
694695
)
695696
if not isinstance(ch, DMM)
696697
else (
697698
"\t"
698699
+ r"- Bottom :math:`|\delta|`:"
699-
+ f" {ch.bottom_detuning:.4g} rad/µs"
700+
+ f" {float(cast(float,ch.bottom_detuning)):.4g}"
701+
+ " rad/µs"
700702
)
701703
),
702704
f"\t- Minimum average amplitude: {ch.min_avg_amp} rad/µs",

pulser-core/pulser/json/abstract_repr/deserializer.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,18 @@ def convert_complex(obj: Any) -> Any:
436436

437437
noise_types = noise_model_obj.pop("noise_types")
438438
with_leakage = "leakage" in noise_types
439+
relevant_params = pulser.NoiseModel._find_relevant_params(
440+
noise_types,
441+
noise_model_obj["state_prep_error"],
442+
noise_model_obj["amp_sigma"],
443+
noise_model_obj["laser_waist"],
444+
) - { # Handled separately
445+
"eff_noise_rates",
446+
"eff_noise_opers",
447+
"with_leakage",
448+
}
439449
noise_model = pulser.NoiseModel(
440-
**noise_model_obj,
450+
**{param: noise_model_obj[param] for param in relevant_params},
441451
eff_noise_rates=tuple(eff_noise_rates),
442452
eff_noise_opers=tuple(eff_noise_opers),
443453
with_leakage=with_leakage,

pulser-pasqal/VERSION.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.20.1

pulser-pasqal/pulser_pasqal/_version.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
# limitations under the License.
1414
from pathlib import PurePath
1515

16-
# Sets the version to the same as 'pulser'.
17-
version_file_path = PurePath(__file__).parent.parent.parent / "VERSION.txt"
16+
version_file_path = PurePath(__file__).parent.parent / "VERSION.txt"
1817

1918
with open(version_file_path, "r", encoding="utf-8") as f:
2019
__version__ = f.read().strip()

pulser-pasqal/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pulser-core >= 0.20
12
pasqal-cloud ~= 0.12
23
backoff ~= 2.2

pulser-pasqal/setup.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,14 @@
2525
current_directory = Path(__file__).parent
2626

2727
# Reads the version from the VERSION.txt file
28-
with open(
29-
current_directory.parent / "VERSION.txt", "r", encoding="utf-8"
30-
) as f:
28+
with open(current_directory / "VERSION.txt", "r", encoding="utf-8") as f:
3129
__version__ = f.read().strip()
3230

3331
# Changes to the directory where setup.py is
3432
os.chdir(current_directory)
3533

3634
with open("requirements.txt", encoding="utf-8") as f:
3735
requirements = f.read().splitlines()
38-
requirements.append(f"pulser-core=={__version__}")
3936

4037
# Stashes the source code for the local version file
4138
local_version_fpath = Path(package_name) / "_version.py"

setup.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@
2525
"`make dev-install` instead."
2626
)
2727

28+
# Pulser packages not pinned to __version__
29+
requirements = [
30+
"pulser-pasqal",
31+
]
32+
# Adding packages pinned to __version__
2833
with open("packages.txt", "r", encoding="utf-8") as f:
29-
requirements = [f"{pkg.strip()}=={__version__}" for pkg in f.readlines()]
34+
requirements += [f"{pkg.strip()}=={__version__}" for pkg in f.readlines()]
3035

3136
# Just a meta-package that requires all pulser packages
3237
setup(

tests/test_abstract_repr.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
)
5050
from pulser.json.abstract_repr.validation import validate_abstract_repr
5151
from pulser.json.exceptions import AbstractReprError, DeserializeDeviceError
52-
from pulser.noise_model import NoiseModel
52+
from pulser.noise_model import _LEGACY_DEFAULTS, NoiseModel
5353
from pulser.parametrized.decorators import parametrize
5454
from pulser.parametrized.paramobj import ParamObj
5555
from pulser.parametrized.variable import Variable, VariableItem
@@ -194,7 +194,24 @@ def test_noise_model(noise_model: NoiseModel):
194194
re_noise_model = NoiseModel.from_abstract_repr(ser_noise_model_str)
195195
assert noise_model == re_noise_model
196196

197+
# Define parameters with defaults, like it was done before
198+
# pulser-core < 0.20, and check deserialization still works
197199
ser_noise_model_obj = json.loads(ser_noise_model_str)
200+
for param in ser_noise_model_obj:
201+
if param in _LEGACY_DEFAULTS and (
202+
# Case where only laser_waist is defined and adding non-zero
203+
# amp_sigma adds requirement for "runs" and "samples_per_run"
204+
param != "amp_sigma"
205+
or "amplitude" not in noise_model.noise_types
206+
):
207+
ser_noise_model_obj[param] = (
208+
ser_noise_model_obj[param] or _LEGACY_DEFAULTS[param]
209+
)
210+
assert (
211+
NoiseModel.from_abstract_repr(json.dumps(ser_noise_model_obj))
212+
== re_noise_model
213+
)
214+
198215
with pytest.raises(TypeError, match="must be given as a string"):
199216
NoiseModel.from_abstract_repr(ser_noise_model_obj)
200217

tests/test_pasqal.py

-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from pasqal_cloud.device.configuration import EmuFreeConfig, EmuTNConfig
2525

2626
import pulser
27-
import pulser_pasqal
2827
from pulser.backend.config import EmulatorConfig
2928
from pulser.backend.remote import (
3029
BatchStatus,
@@ -44,10 +43,6 @@
4443
root = Path(__file__).parent.parent
4544

4645

47-
def test_version():
48-
assert pulser_pasqal.__version__ == pulser.__version__
49-
50-
5146
@dataclasses.dataclass
5247
class CloudFixture:
5348
pasqal_cloud: PasqalCloud

tutorials/applications/QAOA and QAA to solve a QUBO problem.ipynb

+37-23
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,7 @@
4040
"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",
4141
"\n",
4242
"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",
43-
"$$f(z) = z^{T}Qz$$ \n",
44-
"\n",
45-
"\n",
46-
"In this tutorial, we will demonstrate how a QUBO instance can be mapped and solved using neutral atoms."
43+
"$$f(z) = z^{T}Qz$$ "
4744
]
4845
},
4946
{
@@ -74,7 +71,17 @@
7471
"cell_type": "markdown",
7572
"metadata": {},
7673
"source": [
77-
"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."
74+
"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",
75+
"\n",
76+
"1. All the off-diagonal terms are positive.\n",
77+
"2. The diagonal terms are all equal."
78+
]
79+
},
80+
{
81+
"cell_type": "markdown",
82+
"metadata": {},
83+
"source": [
84+
"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."
7885
]
7986
},
8087
{
@@ -115,7 +122,16 @@
115122
"source": [
116123
"We now illustrate how to use Pulser to embbed the QUBO matrix $Q$ on a neutral-atom device.\n",
117124
"\n",
118-
"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$:"
125+
"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",
126+
"$$\n",
127+
"U_{ij}=C_6/r_{ij}^6,\n",
128+
"$$\n",
129+
"we note that \n",
130+
"\n",
131+
"1. The term is strictly positive, which is why it matters that our off-diagonal terms are too.\n",
132+
"2. Its magnitude depends on the pairwise distance between atoms $i$ and $j$, $r_{ij}$. \n",
133+
"\n",
134+
"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$:"
119135
]
120136
},
121137
{
@@ -124,11 +140,10 @@
124140
"metadata": {},
125141
"outputs": [],
126142
"source": [
127-
"def evaluate_mapping(new_coords, *args):\n",
128-
" \"\"\"Cost function to minimize. Ideally, the pairwise\n",
129-
" distances are conserved\"\"\"\n",
130-
" Q, shape = args\n",
131-
" new_coords = np.reshape(new_coords, shape)\n",
143+
"def evaluate_mapping(new_coords, Q):\n",
144+
" \"\"\"Cost function to minimize. Ideally, the pairwise distances are conserved.\"\"\"\n",
145+
" new_coords = np.reshape(new_coords, (len(Q), 2))\n",
146+
" # computing the matrix of the distances between all coordinate pairs\n",
132147
" new_Q = squareform(\n",
133148
" DigitalAnalogDevice.interaction_coeff / pdist(new_coords) ** 6\n",
134149
" )\n",
@@ -141,14 +156,13 @@
141156
"metadata": {},
142157
"outputs": [],
143158
"source": [
144-
"shape = (len(Q), 2)\n",
145159
"costs = []\n",
146160
"np.random.seed(0)\n",
147-
"x0 = np.random.random(shape).flatten()\n",
161+
"x0 = np.random.random(len(Q) * 2)\n",
148162
"res = minimize(\n",
149163
" evaluate_mapping,\n",
150164
" x0,\n",
151-
" args=(Q, shape),\n",
165+
" args=(Q,),\n",
152166
" method=\"Nelder-Mead\",\n",
153167
" tol=1e-6,\n",
154168
" options={\"maxiter\": 200000, \"maxfev\": None},\n",
@@ -178,6 +192,13 @@
178192
")"
179193
]
180194
},
195+
{
196+
"cell_type": "markdown",
197+
"metadata": {},
198+
"source": [
199+
"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."
200+
]
201+
},
181202
{
182203
"cell_type": "markdown",
183204
"metadata": {},
@@ -193,9 +214,9 @@
193214
"\n",
194215
"$$ 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",
195216
"\n",
196-
"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",
217+
"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",
197218
"\n",
198-
"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."
219+
"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."
199220
]
200221
},
201222
{
@@ -481,13 +502,6 @@
481502
"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$."
482503
]
483504
},
484-
{
485-
"cell_type": "markdown",
486-
"metadata": {},
487-
"source": [
488-
"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:"
489-
]
490-
},
491505
{
492506
"cell_type": "markdown",
493507
"metadata": {},

0 commit comments

Comments
 (0)