diff --git a/docs/source/hardware.ipynb b/docs/source/hardware.ipynb
new file mode 100644
index 000000000..5b2f5ce9e
--- /dev/null
+++ b/docs/source/hardware.ipynb
@@ -0,0 +1,500 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "# Hardware specifications"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "*What you will learn:*\n",
+ "- what is a Device and why is a Device needed;\n",
+ "- what are the available devices and where to find them;\n",
+ "- what do a Device and a Channel enforce; \n",
+ "- tips on how to pick a `Device`;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## The `Device`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "As presented in the [introduction to programming a neutral-atom QPU](programming.md#writing-a-pulser-program), the first step to writing a Pulser program is [the selection of a Device](programming.md#pick-a-device). \n",
+ "\n",
+ "The `Device` is an object that stores **all** the physical constraints a quantum program written with Pulser should verify. The `Device` enforces that each operation added to the quantum program (i.e. the `pulser.Sequence`) respects its constraints. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "
\n",
+ "\n",
+ "**Important note**:\n",
+ "\n",
+ "\n",
+ "The `Device` represents the physics of a neutral-atom QPU but is not the QPU itself. A QPU must be accessed via a `Backend`, which is presented [in this section](./tutorials/backends.nblink).\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Choosing a `Device`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "\n",
+ "
\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "To choose a `Device` for your `Sequence`, the first question you should ask yourself is:\n",
+ "\n",
+ "\"How close do I want the physical constraints I program with to be to the QPU's?\"\n",
+ "\n",
+ "**If you want to program with the physical constraints of a QPU**: Each QPU has an associated `Device`, which you can [get from the cloud provider you use to access this QPU](tutorials/backends.nblink#1.2.-Preparation-for-execution-on-QPUBackend).\n",
+ "\n",
+ "There are several reasons for which you might want to feel less constrained by the features currently supported by real QPUs. For instance, you might want to design an algorithm for a QPU having better performances (supporting more qubits, longer sequences, ...) or hardware components that have not been installed.\n",
+ "\n",
+ "Pulser enables you to define your own devices, but a `Device` object takes as input lots of parameters that have all to be defined. Therefore, for user convenience, `Pulser` provides:\n",
+ "\n",
+ "- **Examples of typical physical devices** in `pulser.devices`. Notably, `pulser.AnalogDevice` is an example of a QPU implementing an [Ising Hamiltonian](./programming.md#ising-hamiltonian).\n",
+ "\n",
+ "- **The possibility to define a device without some physical constraints** using the `VirtualDevice` class. An example of such a virtual device is the `MockDevice` provided in the `pulser.devices`, which gives full liberty to write a quantum program. `VirtualDevice` is detailed in [an advanced tutorial](./tutorials/virtual_devices.nblink)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "\n",
+ "\n",
+ "**Note**:\n",
+ "\n",
+ "The selection of a device in a Pulser program does not enforce any choice on the [backend](tutorials/backends.nblink). No matter the device you used to program your `Sequence`, you can always submit it to any QPU: if the values of the `Sequence` match the constraints of the `QPU`, it will be executed. \n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "\n",
+ "\n",
+ "\n",
+ "**Tip**:\n",
+ "\n",
+ "It is possible to change the device with which a `Sequence` was built, by using `Sequence.switch_device`. This is especially useful to check if the values of the `Sequence` match the constraints of the `QPU` prior to submitting to the `QPU`. For instance, you could have built your `Sequence` with an example of a `Device` like `AnalogDevice`, and now want to run it on a QPU, or the specifications of your QPU might have changed between your design and submission.\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Reading through the `Device`'s specifications"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "The second question you should ask yourself to choose your `Device` is: \n",
+ "\n",
+ "\"Do its constraints allow me to program my `Sequence` ?\" \n",
+ "\n",
+ "The device specifications are here to guide your choice. Here are all the parameters in a `Device`:"
+ ]
+ },
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "editable": true,
+ "raw_mimetype": "text/restructuredtext",
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [],
+ "vscode": {
+ "languageId": "raw"
+ }
+ },
+ "source": [
+ ".. autoclass:: pulser.devices._device_datacls.Device\n",
+ " :noindex:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**Note**:\n",
+ "\n",
+ "The `Device` object has many useful properties and methods, that you can check in the [API documentation](./apidoc/core.rst#devices). For instance, it is possible to display some of the specifications of the `Device` with `Device.specs`. See an example with `AnalogDevice.specs` [in the section below](#the-analogdevice).\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Tips on `Device` selection"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "The `Device` is going to constrain the next steps of your [quantum program](./programming.md#writing-a-pulser-program):\n",
+ "\n",
+ "1) Some parameters are going to constrain [the creation of your Register](./programming.md#create-the-register), and therefore, the [interaction strength in the interaction Hamiltonian](programming.md#interaction-hamiltonian). Some of these parameters are:\n",
+ " - `dimensions`\n",
+ " - `max_atom_num`\n",
+ " - `max_radial_distance`\n",
+ " - `min_atom_distance`\n",
+ "\n",
+ "2) The `rydberg_level` determines the [Ising interaction coefficient](./programming.md#ising-hamiltonian) $C_6$ of the Ising Hamiltonian. The quantity $\\frac{C_6}{\\hbar}$ is accessible via the `interaction_coeff` attribute of the `Device`.\n",
+ "\n",
+ "3) The `Channels` in the `channel_objects` parameter are going to determine what [Channels are available for the computation](programming.md#pick-the-channels). Knowing what states you want to use in your computation, you can first check that they are among the `Device.supported_states`, then find the bases and their associated channel that enable to use these states using [the conventions page](conventions.md#bases).\n",
+ "\n",
+ "4) The `max_sequence_duration` constrains the duration of the [Pulses you can add](programming.md#add-the-pulses), and therefore the Hamiltonian describing the system can at most be defined between 0 and this value.\n",
+ "\n",
+ "5) The `max_runs` limits the number of runs a quantum program can be executed on the QPU. See [the section on Backends](./tutorials/backends.nblink) to read more about this."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**Note**:\n",
+ "\n",
+ "If the Device associated with a QPU has `requires_layout=True`, then you have to define the `Register` from a layout. This adds more constraints to the creation of your `Register`, and is [presented in an advanced tutorial](./tutorials/reg_layouts.nblink).\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## The `Channels`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "The third step to writing a Pulser program is [the selection of Channels among the Device](programming.md#pick-a-device).\n",
+ "\n",
+ "As a reminder, the selection of a `Channel` defines the [interaction Hamiltonian](programming.md#interaction-hamiltonian) and [the driving Hamiltonian](programming.md#driving-hamiltonian) $H^D$.\n",
+ "\n",
+ "$$\n",
+ "H^D(t) / \\hbar = \\frac{\\Omega(t)}{2} e^{-i\\phi} |a\\rangle\\langle b| + \\frac{\\Omega(t)}{2} e^{i\\phi} |b\\rangle\\langle a| - \\delta(t) |b\\rangle\\langle b|\n",
+ "$$\n",
+ "\n",
+ "The `Channels` available for selection are stored in the `channels` property of the `Device`, a dictionnary associating a `channel_id` to each `Channel` in `channel_objects`. For instance, `AnalogDevice` only contains one channel, the `rydberg_global` channel, which can be accessed with `AnalogDevice.channels[\"rydberg_global\"]`. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Reading through the `Channel`'s specifications"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "The `Channel` is defined by:"
+ ]
+ },
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "editable": true,
+ "raw_mimetype": "text/restructuredtext",
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [],
+ "vscode": {
+ "languageId": "raw"
+ }
+ },
+ "source": [
+ ".. autoclass:: pulser.channels.base_channel.Channel\n",
+ " :noindex:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Tips on `Channel` selection"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "The `Channel` is going to determine the computational basis used in the driving Hamiltonian, and what is the Hamiltonian each atom sees:\n",
+ "\n",
+ "- The type of the `Channel` defines the [states](conventions.md#bases) that can be addressed by the [driving Hamiltonian](programming.md#driving-hamiltonian) if this channel is picked. All the child classes of `Channel` can be found [here](./apidoc/core.rst#module-pulser.channels.channels).\n",
+ "- The addressing of the `Channel` determines what atoms experience the driving Hamiltonian. In general, physical `Channels` have a `Global` addressability, which means that a Pulse added to this channel will implement the same driving Hamiltonian on all the atoms.\n",
+ "\n",
+ "The `Channel` also set constraints on the next stage of your quantum program, the addition of Pulses:\n",
+ "- the **duration** of the pulse is constrained by `min_duration` and `max_duration`, as well as `clock_period` (it has to be a multiple of the clock period).\n",
+ "- the **amplitude** is limited by the maximum amplitude `max_amp` and `min_avg_amp`.\n",
+ "- the **detuning** is limited by the maximum absolute detuning `max_abs_det`. It has to be between -`max_abs_det` and `max_abs_det`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "\n",
+ "\n",
+ "**Note**:\n",
+ "\n",
+ "The modulation bandwidth `mod_bandwidth` impacts the duration, the amplitude, the detuning and the phase of the Pulses. It is a more advanced feature explained [in this tutorial](./tutorials/output_mod_eom.nblink).\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## The `AnalogDevice`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "import pulser\n",
+ "\n",
+ "print(pulser.AnalogDevice.specs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "The `pulser.AnalogDevice` only supports the $\\left|r\\right>$ and $\\left|g\\right>$ states, because it only supports one channel of type \"Rydberg\" (that can be declared using its name \"rydberg_global\"). It implements the [Ising Hamiltonian](programming.md#ising-hamiltonian): \n",
+ "\n",
+ "$$\\frac{H}{\\hbar}(t) = \\sum_{k=1}^N \\left (\\frac{\\Omega(t)}{2} e^{-i\\phi(t)} |g\\rangle\\langle r|_k + \\frac{\\Omega(t)}{2} e^{i\\phi(t)} |r\\rangle\\langle g|_k - \\delta(t) |r\\rangle\\langle r|_k(t) + \\sum_{jInteraction strength and entangling operator
-- The interaction strength is $\frac{C_6}{R_{ij}^6}$, with $C_6$ a coefficient that depends on the principal quantum number of the Rydberg state.
+- The interaction strength is $\frac{C_6}{R_{ij}^6}$, with $C_6$ the Ising interaction coefficient that depends on the principal quantum number of the Rydberg state.
- The entangling operator between atom $i$ and $j$ is $\hat{n}_i\hat{n}_j = |r\rangle\langle r|_i |r\rangle\langle r|_j$.
@@ -169,7 +169,11 @@ As outlined above, Pulser lets you program an Hamiltonian ([the Hamiltonian $H$]
:width: 600
:::
-The `Device` you select will dictate some parameters and constrain others. For instance, the value of the $C_6$ and $C_3$ coefficients of the [interaction Hamiltonian](programming.md#22-interaction-hamiltonian) are defined by the device. Notably, the `Device` defines the list of `Channels` that can be used in the computation, which have a direct impact on the Hamiltonian that can be implemented. For a complete view of the constraints introduced by the device, [check its description](./apidoc/core.rst).
+The `Device` you select will dictate some parameters and constrain others. For instance, the value of the $C_6$ and $C_3$ coefficients of the [interaction Hamiltonian](programming.md#22-interaction-hamiltonian) are defined by the device. Notably, the `Device` defines the list of `Channels` that can be used in the computation, which have a direct impact on the Hamiltonian that can be implemented.
+
+:::{seealso}
+For a complete view of the constraints introduced by the device, [check its description](./hardware.ipynb).
+:::
### 2. Create the Register
@@ -214,6 +218,10 @@ The addressing of a `Channel` defines the number of atoms whose transition will
The most common addressing for a `Channel` is the `Global` one: all the atoms evolve under the same [driving Hamiltonian](programming.md#21-driving-hamiltonian).
:::
+:::{seealso}
+For a thorough description of what a Channel is and some tips on how to select a Channel, [check this page](./hardware.ipynb).
+:::
+
### 4. Add the Pulses
By adding pulses to a channel, we incrementally construct the [driving Hamiltonian](programming.md#21-driving-hamiltonian):
@@ -233,7 +241,7 @@ We have successfully defined the [Hamiltonian](programming.md#2-hamiltonian-evol
You can now simulate your first Hamiltonian by programming your first `Sequence`! [In this tutorial](tutorials/creating.nblink), you will simulate the evolution of the state of an atom initialized in $\left|g\right>$ under a Hamiltonian $H(t)=\frac{\Omega(t)}{2} |g\rangle \langle r|+\frac{\Omega(t)}{2} |r\rangle\langle g|$, with $\Omega$ chosen such that the final state of the atom is the excited state $\left|r\right>$.
Many concepts have been introduced here and you might want further explanations.
-- The `Device` object contains all the constraints and physical quantities that are defined in a QPU. [This section in the fundamentals](apidoc/core.rst) details these and provides examples of `Devices`. The `VirtualDevices` were also mentioned in this document ([here](programming.md#1-pick-a-device)), which is a more advanced feature described [here](tutorials/virtual_devices.nblink).
+- The `Device` object contains all the constraints and physical quantities that are defined in a QPU. [This section in the fundamentals](./hardware.ipynb) details these and provides examples of `Devices`. The `VirtualDevices` were also mentioned in this document ([here](programming.md#1-pick-a-device)), which is a more advanced feature described [here](tutorials/virtual_devices.nblink).
- There are multiple ways of defining a `Register`, as is further detailed [in this section](tutorials/reg_layouts.nblink).
-- The energy levels associated with each `Channel` and the interaction Hamiltonian they implement are summed up in [the conventions page](conventions.md). The channels contain lots of constraints and physical informations, they are detailed in [the same section as the `Device`](apidoc/core.rst).
+- The energy levels associated with each `Channel` and the interaction Hamiltonian they implement are summed up in [the conventions page](conventions.md). The channels contain lots of constraints and physical informations, they are detailed in [the same section as the `Device`](./hardware.ipynb).
- The quantities in a `Pulse` are defined using `Waveform`s, you can read more about these [on this page](tutorials/composite_wfs.nblink).
\ No newline at end of file
diff --git a/pulser-core/pulser/channels/base_channel.py b/pulser-core/pulser/channels/base_channel.py
index f3fb11433..a90219d6e 100644
--- a/pulser-core/pulser/channels/base_channel.py
+++ b/pulser-core/pulser/channels/base_channel.py
@@ -663,6 +663,7 @@ def __str__(self) -> str:
config += f", Maximum pulse duration: {self.max_duration} ns"
if self.mod_bandwidth:
config += f", Modulation Bandwidth: {self.mod_bandwidth} MHz"
+ config += f", Supports EOM: {self.supports_eom()}"
config += f", Basis: '{self.basis}')"
return self.name + config
diff --git a/pulser-core/pulser/devices/_device_datacls.py b/pulser-core/pulser/devices/_device_datacls.py
index 01b0181d0..1103d72bf 100644
--- a/pulser-core/pulser/devices/_device_datacls.py
+++ b/pulser-core/pulser/devices/_device_datacls.py
@@ -67,34 +67,36 @@ class BaseDevice(ABC):
Attributes:
name: The name of the device.
dimensions: Whether it supports 2D or 3D arrays.
- channel_objects: The Channel subclass instances specifying each
- channel in the device.
- channel_ids: Custom IDs for each channel object. When defined,
- an ID must be given for each channel. If not defined, the IDs are
- generated internally based on the channels' names and addressing.
- dmm_objects: The DMM subclass instances specifying each channel in the
- device. They are referenced by their order in the list, with the ID
- "dmm_[index in dmm_objects]".
- rydberg_level: The value of the principal quantum number :math:`n`
- when the Rydberg level used is of the form
- :math:`|nS_{1/2}, m_j = +1/2\rangle`.
max_atom_num: Maximum number of atoms supported in an array.
max_radial_distance: The furthest away an atom can be from the center
of the array (in μm).
min_atom_distance: The closest together two atoms can be (in μm).
+ requires_layout: Whether the register used in the sequence must be
+ created from a register layout. Only enforced in QPU execution.
+ min_layout_traps: The minimum number of traps a layout can have.
+ max_layout_traps: An optional value for the maximum number of traps a
+ layout can have.
+ max_layout_filling: The largest fraction of a layout that can be filled
+ with atoms.
+ optimal_layout_filling: An optional value for the fraction of a layout
+ that should be filled with atoms.
+ rydberg_level: The value of the principal quantum number :math:`n`
+ when the Rydberg level used is of the form
+ :math:`|nS_{1/2}, m_j = +1/2\rangle`.
interaction_coeff_xy: :math:`C_3/\hbar`
(in :math:`rad \cdot \mu s^{-1} \cdot \mu m^3`),
which sets the van der Waals interaction strength between atoms in
different Rydberg states. Needed only if there is a Microwave
channel in the device. If unsure, 3700.0 is a good default value.
+ channel_objects: The Channel subclass instances specifying each
+ channel in the device.
+ channel_ids: Custom IDs for each channel object. When defined,
+ an ID must be given for each channel. If not defined, the IDs are
+ generated internally based on the channels' names and addressing.
+ dmm_objects: The DMM subclass instances specifying each channel in the
+ device. They are referenced by their order in the list, with the ID
+ "dmm_[index in dmm_objects]".
supports_slm_mask: Whether the device supports the SLM mask feature.
- max_layout_filling: The largest fraction of a layout that can be filled
- with atoms.
- optimal_layout_filling: An optional value for the fraction of a layout
- that should be filled with atoms.
- min_layout_traps: The minimum number of traps a layout can have.
- max_layout_traps: An optional value for the maximum number of traps a
- layout can have.
max_sequence_duration: The maximum allowed duration for a sequence
(in ns).
max_runs: The maximum number of runs allowed on the device. Only used
@@ -102,8 +104,6 @@ class BaseDevice(ABC):
default_noise_model: An optional noise model characterizing the default
noise of the device. Can be used by emulator backends that support
noise.
- requires_layout: Whether the register used in the sequence must be
- created from a register layout. Only enforced in QPU execution.
"""
name: str
@@ -615,7 +615,6 @@ def _register_lines(self) -> list[str]:
register_lines = [
"\nRegister parameters:",
f" - Dimensions: {self.dimensions}D",
- f" - Rydberg level: {self.rydberg_level}",
self._param_check_none(self.max_atom_num)(
" - Maximum number of atoms: {}"
),
@@ -624,7 +623,6 @@ def _register_lines(self) -> list[str]:
),
" - Minimum distance between neighbouring atoms: "
+ f"{self.min_atom_distance} μm",
- f" - SLM Mask: {self._param_yes_no(self.supports_slm_mask)}",
]
return [line for line in register_lines if line != ""]
@@ -647,21 +645,23 @@ def _device_lines(self) -> list[str]:
device_lines = [
"\nDevice parameters:",
- self._param_check_none(self.max_runs)(
- " - Maximum number of runs: {}"
+ f" - Rydberg level: {self.rydberg_level}",
+ self._param_check_none(self.interaction_coeff)(
+ " - Ising interaction coefficient: {}",
),
- self._param_check_none(self.max_sequence_duration)(
- " - Maximum sequence duration: {} ns",
+ self._param_check_none(self.interaction_coeff_xy)(
+ " - XY interaction coefficient: {}",
),
" - Channels can be reused: "
+ self._param_yes_no(self.reusable_channels),
f" - Supported bases: {', '.join(self.supported_bases)}",
f" - Supported states: {', '.join(self.supported_states)}",
- self._param_check_none(self.interaction_coeff)(
- " - Ising interaction coefficient: {}",
+ f" - SLM Mask: {self._param_yes_no(self.supports_slm_mask)}",
+ self._param_check_none(self.max_sequence_duration)(
+ " - Maximum sequence duration: {} ns",
),
- self._param_check_none(self.interaction_coeff_xy)(
- " - XY interaction coefficient: {}",
+ self._param_check_none(self.max_runs)(
+ " - Maximum number of runs: {}"
),
self._param_check_none(self.default_noise_model)(
" - Default noise model: {}",
@@ -743,30 +743,18 @@ class Device(BaseDevice):
For usage in emulations, it can be converted to a VirtualDevice through the
`Device.to_virtual()` method.
- Attributes:
+ Args:
name: The name of the device.
dimensions: Whether it supports 2D or 3D arrays.
- channel_objects: The Channel subclass instances specifying each
- channel in the device.
- channel_ids: Custom IDs for each channel object. When defined,
- an ID must be given for each channel. If not defined, the IDs are
- generated internally based on the channels' names and addressing.
- dmm_objects: The DMM subclass instances specifying each channel in the
- device. They are referenced by their order in the list, with the ID
- "dmm_[index in dmm_objects]".
- rydberg_level: The value of the principal quantum number :math:`n`
- when the Rydberg level used is of the form
- :math:`|nS_{1/2}, m_j = +1/2\rangle`.
max_atom_num: Maximum number of atoms supported in an array.
max_radial_distance: The furthest away an atom can be from the center
of the array (in μm).
min_atom_distance: The closest together two atoms can be (in μm).
- interaction_coeff_xy: :math:`C_3/\hbar`
- (in :math:`rad \cdot \mu s^{-1} \cdot \mu m^3`),
- which sets the van der Waals interaction strength between atoms in
- different Rydberg states. Needed only if there is a Microwave
- channel in the device. If unsure, 3700.0 is a good default value.
- supports_slm_mask: Whether the device supports the SLM mask feature.
+ requires_layout: Whether the register used in the sequence must be
+ created from a register layout. Only enforced in QPU execution.
+ accepts_new_layouts: Whether registers built from register layouts
+ that are not already calibrated are accepted. Only enforced in
+ QPU execution.
max_layout_filling: The largest fraction of a layout that can be filled
with atoms.
optimal_layout_filling: An optional value for the fraction of a layout
@@ -774,6 +762,25 @@ class Device(BaseDevice):
min_layout_traps: The minimum number of traps a layout can have.
max_layout_traps: An optional value for the maximum number of traps a
layout can have.
+ pre_calibrated_layouts: RegisterLayout instances that are already
+ available on the Device.
+ rydberg_level: The value of the principal quantum number :math:`n`
+ when the Rydberg level used is of the form
+ :math:`|nS_{1/2}, m_j = +1/2\rangle`.
+ interaction_coeff_xy: :math:`C_3/\hbar`
+ (in :math:`rad \cdot \mu s^{-1} \cdot \mu m^3`),
+ which sets the van der Waals interaction strength between atoms in
+ different Rydberg states. Needed only if there is a Microwave
+ channel in the device. If unsure, 3700.0 is a good default value.
+ channel_objects: The Channel subclass instances specifying each
+ channel in the device.
+ channel_ids: Custom IDs for each channel object. When defined,
+ an ID must be given for each channel. If not defined, the IDs are
+ generated internally based on the channels' names and addressing.
+ dmm_objects: The DMM subclass instances specifying each channel in the
+ device. They are referenced by their order in the list, with the ID
+ "dmm_[index in dmm_objects]".
+ supports_slm_mask: Whether the device supports the SLM mask feature.
max_sequence_duration: The maximum allowed duration for a sequence
(in ns).
max_runs: The maximum number of runs allowed on the device. Only used
@@ -781,13 +788,6 @@ class Device(BaseDevice):
default_noise_model: An optional noise model characterizing the default
noise of the device. Can be used by emulator backends that support
noise.
- requires_layout: Whether the register used in the sequence must be
- created from a register layout. Only enforced in QPU execution.
- pre_calibrated_layouts: RegisterLayout instances that are already
- available on the Device.
- accepts_new_layouts: Whether registers built from register layouts
- that are not already calibrated are accepted. Only enforced in
- QPU execution.
"""
max_atom_num: int
@@ -933,27 +933,12 @@ class VirtualDevice(BaseDevice):
Attributes:
name: The name of the device.
dimensions: Whether it supports 2D or 3D arrays.
- channel_objects: The Channel subclass instances specifying each
- channel in the device.
- channel_ids: Custom IDs for each channel object. When defined,
- an ID must be given for each channel. If not defined, the IDs are
- generated internally based on the channels' names and addressing.
- dmm_objects: The DMM subclass instances specifying each channel in the
- device. They are referenced by their order in the list, with the ID
- "dmm_[index in dmm_objects]".
- rydberg_level: The value of the principal quantum number :math:`n`
- when the Rydberg level used is of the form
- :math:`|nS_{1/2}, m_j = +1/2\rangle`.
max_atom_num: Maximum number of atoms supported in an array.
max_radial_distance: The furthest away an atom can be from the center
of the array (in μm).
min_atom_distance: The closest together two atoms can be (in μm).
- interaction_coeff_xy: :math:`C_3/\hbar`
- (in :math:`rad \cdot \mu s^{-1} \cdot \mu m^3`),
- which sets the van der Waals interaction strength between atoms in
- different Rydberg states. Needed only if there is a Microwave
- channel in the device. If unsure, 3700.0 is a good default value.
- supports_slm_mask: Whether the device supports the SLM mask feature.
+ requires_layout: Whether the register used in the sequence must be
+ created from a register layout. Only enforced in QPU execution.
max_layout_filling: The largest fraction of a layout that can be filled
with atoms.
optimal_layout_filling: An optional value for the fraction of a layout
@@ -961,6 +946,25 @@ class VirtualDevice(BaseDevice):
min_layout_traps: The minimum number of traps a layout can have.
max_layout_traps: An optional value for the maximum number of traps a
layout can have.
+ rydberg_level: The value of the principal quantum number :math:`n`
+ when the Rydberg level used is of the form
+ :math:`|nS_{1/2}, m_j = +1/2\rangle`.
+ interaction_coeff_xy: :math:`C_3/\hbar`
+ (in :math:`rad \cdot \mu s^{-1} \cdot \mu m^3`),
+ which sets the van der Waals interaction strength between atoms in
+ different Rydberg states. Needed only if there is a Microwave
+ channel in the device. If unsure, 3700.0 is a good default value.
+ reusable_channels: Whether each channel can be declared multiple times
+ on the same pulse sequence.
+ channel_objects: The Channel subclass instances specifying each
+ channel in the device.
+ channel_ids: Custom IDs for each channel object. When defined,
+ an ID must be given for each channel. If not defined, the IDs are
+ generated internally based on the channels' names and addressing.
+ dmm_objects: The DMM subclass instances specifying each channel in the
+ device. They are referenced by their order in the list, with the ID
+ "dmm_[index in dmm_objects]".
+ supports_slm_mask: Whether the device supports the SLM mask feature.
max_sequence_duration: The maximum allowed duration for a sequence
(in ns).
max_runs: The maximum number of runs allowed on the device. Only used
@@ -968,10 +972,6 @@ class VirtualDevice(BaseDevice):
default_noise_model: An optional noise model characterizing the default
noise of the device. Can be used by emulator backends that support
noise.
- requires_layout: Whether the register used in the sequence must be
- created from a register layout. Only enforced in QPU execution.
- reusable_channels: Whether each channel can be declared multiple times
- on the same pulse sequence.
"""
min_atom_distance: float = 0
diff --git a/tests/test_channels.py b/tests/test_channels.py
index 582deb23b..21908a006 100644
--- a/tests/test_channels.py
+++ b/tests/test_channels.py
@@ -190,7 +190,7 @@ def test_repr():
"Raman.Local(Max Absolute Detuning: None, Max Amplitude: "
"2 rad/µs, Minimum retarget time: 1000 ns, "
"Fixed retarget time: 200 ns, Max targets: 4, Clock period: 4 ns, "
- "Minimum pulse duration: 16 ns, Basis: 'digital')"
+ "Minimum pulse duration: 16 ns, Supports EOM: False, Basis: 'digital')"
)
assert raman.__str__() == r1
@@ -200,7 +200,8 @@ def test_repr():
"Max Amplitude: None, Clock period: 1 ns, "
"Minimum pulse duration: 1 ns, "
"Maximum pulse duration: 100000000 ns, "
- "Modulation Bandwidth: 4 MHz, Basis: 'ground-rydberg')"
+ "Modulation Bandwidth: 4 MHz, Supports EOM: False, "
+ "Basis: 'ground-rydberg')"
)
assert ryd.__str__() == r2
diff --git a/tests/test_devices.py b/tests/test_devices.py
index df473a895..388a397f6 100644
--- a/tests/test_devices.py
+++ b/tests/test_devices.py
@@ -281,7 +281,6 @@ def specs(dev):
register_str = (
"\nRegister parameters:\n"
+ f" - Dimensions: {dev.dimensions}D\n"
- + f" - Rydberg level: {dev.rydberg_level}\n"
+ check_none_fn(dev, "max_atom_num", "Maximum number of atoms: {}")
+ check_none_fn(
dev,
@@ -290,7 +289,6 @@ def specs(dev):
)
+ " - Minimum distance between neighbouring atoms: "
+ f"{dev.min_atom_distance} μm\n"
- + yes_no_fn(dev, "supports_slm_mask", "SLM Mask")
)
layout_str = (
@@ -312,19 +310,21 @@ def specs(dev):
device_str = (
"\nDevice parameters:\n"
- + check_none_fn(dev, "max_runs", "Maximum number of runs: {}")
+ + f" - Rydberg level: {dev.rydberg_level}\n"
+ + f" - Ising interaction coefficient: {dev.interaction_coeff}\n"
+ check_none_fn(
- dev,
- "max_sequence_duration",
- "Maximum sequence duration: {} ns",
+ dev, "interaction_coeff_xy", "XY interaction coefficient: {}"
)
+ yes_no_fn(dev, "reusable_channels", "Channels can be reused")
+ f" - Supported bases: {', '.join(dev.supported_bases)}\n"
+ f" - Supported states: {', '.join(dev.supported_states)}\n"
- + f" - Ising interaction coefficient: {dev.interaction_coeff}\n"
+ + yes_no_fn(dev, "supports_slm_mask", "SLM Mask")
+ check_none_fn(
- dev, "interaction_coeff_xy", "XY interaction coefficient: {}"
+ dev,
+ "max_sequence_duration",
+ "Maximum sequence duration: {} ns",
)
+ + check_none_fn(dev, "max_runs", "Maximum number of runs: {}")
)
channel_str = "\nChannels:\n" + "\n".join(