Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete summing constraint in DetuningMap #610

Merged
merged 21 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 78 additions & 13 deletions pulser-core/pulser/channels/dmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
"""Defines the detuning map modulator."""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Literal, Optional
import warnings
from dataclasses import dataclass, field, fields
from typing import Any, Literal, Optional

import numpy as np

from pulser.channels.base_channel import Channel
from pulser.json.utils import get_dataclass_defaults
from pulser.pulse import Pulse
from pulser.register.weight_maps import DetuningMap

OPTIONAL_ABSTR_DMM_FIELDS = ["total_bottom_detuning"]


@dataclass(init=True, repr=False, frozen=True)
Expand All @@ -31,16 +36,20 @@ class DMM(Channel):
(of zero amplitude and phase). These Pulses are locally modulated by the
weights of a `DetuningMap`, thus providing a local control over the
detuning. The detuning of the pulses added to a DMM has to be negative,
between 0 and `bottom_detuning`. Channel targeting the transition between
the ground and rydberg states, thus encoding the 'ground-rydberg' basis.
between 0 and `bottom_detuning`, and the sum of the weights multiplied by
that detuning has to be below `total_bottom_detuning`. Channel targeting
the transition between the ground and rydberg states, thus encoding the
'ground-rydberg' basis.

Note:
The protocol to add pulses to the DMM Channel is by default
"no-delay".

Args:
bottom_detuning: Minimum possible detuning (in rad/µs), must be below
zero.
bottom_detuning: Minimum possible detuning per atom (in rad/µs),
must be below zero.
total_bottom_detuning: Minimum possible detuning distributed on all
atoms (in rad/µs), must be below zero.
clock_period: The duration of a clock cycle (in ns). The duration of a
pulse or delay instruction is enforced to be a multiple of the
clock cycle.
Expand All @@ -51,7 +60,8 @@ class DMM(Channel):
MHz.
"""

bottom_detuning: Optional[float] = field(default=None, init=True)
bottom_detuning: float | None = None
total_bottom_detuning: float | None = None
addressing: Literal["Global"] = field(default="Global", init=False)
max_abs_detuning: Optional[float] = field(default=None, init=False)
max_amp: float = field(default=0, init=False)
Expand All @@ -63,6 +73,17 @@ def __post_init__(self) -> None:
super().__post_init__()
if self.bottom_detuning and self.bottom_detuning > 0:
raise ValueError("bottom_detuning must be negative.")
if self.total_bottom_detuning:
if self.total_bottom_detuning > 0:
raise ValueError("total_bottom_detuning must be negative.")
if (
self.bottom_detuning
and self.bottom_detuning < self.total_bottom_detuning
):
raise ValueError(
"total_bottom_detuning must be lower than"
" bottom_detuning."
)

@property
def basis(self) -> Literal["ground-rydberg"]:
Expand All @@ -73,26 +94,70 @@ def _undefined_fields(self) -> list[str]:
optional = [
"bottom_detuning",
"max_duration",
# TODO: "total_bottom_detuning"
]
return [field for field in optional if getattr(self, field) is None]

def validate_pulse(self, pulse: Pulse) -> None:
"""Checks if a pulse can be executed in this DMM.
def is_virtual(self) -> bool:
"""Whether the channel is virtual (i.e. partially defined)."""
virtual_dmm = bool(self._undefined_fields())
if not virtual_dmm and self.total_bottom_detuning is None:
warnings.warn(
"From v0.17 and onwards, `total_bottom_detuning` must be"
" defined to define a physical DMM.",
DeprecationWarning,
)
return virtual_dmm

def validate_pulse(
self,
pulse: Pulse,
detuning_map: DetuningMap = DetuningMap(
trap_coordinates=[(0, 0)], weights=[1.0]
),
) -> None:
"""Checks if a pulse can be executed via this DMM on a DetuningMap.

Args:
pulse: The pulse to validate.
detuning_map: The detuning map on which the pulse is applied
(defaults to a detuning map with weight 1.0).
"""
super().validate_pulse(pulse)
round_detuning = np.round(pulse.detuning.samples, decimals=6)
# Check that detuning is negative
if np.any(round_detuning > 0):
raise ValueError("The detuning in a DMM must not be positive.")
if self.bottom_detuning is not None and np.any(
round_detuning < self.bottom_detuning
# Check that detuning on each atom is above bottom_detuning
min_round_detuning = np.min(round_detuning)
if (
self.bottom_detuning is not None
and np.max(detuning_map.weights) * min_round_detuning
< self.bottom_detuning
):
raise ValueError(
"The detuning goes below the bottom detuning "
f"of the DMM ({self.bottom_detuning} rad/µs)."
"The detunings on some atoms go below the local bottom "
f"detuning of the DMM ({self.bottom_detuning} rad/µs)."
)
# Check that distributed detuning is above total_bottom_detuning
if (
self.total_bottom_detuning is not None
and np.sum(detuning_map.weights) * min_round_detuning
< self.total_bottom_detuning
):
raise ValueError(
"The applied detuning goes below the total bottom detuning "
f"of the DMM ({self.total_bottom_detuning} rad/µs)."
)

def _to_abstract_repr(self, id: str) -> dict[str, Any]:
all_fields = fields(self)
defaults = get_dataclass_defaults(all_fields)
params = super()._to_abstract_repr(id)
for p in OPTIONAL_ABSTR_DMM_FIELDS:
if params[p] == defaults[p]:
params.pop(p, None)
return params


def _dmm_id_from_name(dmm_name: str) -> str:
Expand Down
93 changes: 49 additions & 44 deletions pulser-core/pulser/devices/_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,64 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Definitions of real devices."""
import warnings

import numpy as np

from pulser.channels import DMM, Raman, Rydberg
from pulser.channels.eom import RydbergBeam, RydbergEOM
from pulser.devices._device_datacls import Device
from pulser.register.special_layouts import TriangularLatticeLayout

Chadoq2 = Device(
name="Chadoq2",
dimensions=2,
rydberg_level=70,
max_atom_num=100,
max_radial_distance=50,
min_atom_distance=4,
supports_slm_mask=True,
channel_objects=(
Rydberg.Global(
max_abs_detuning=2 * np.pi * 20,
max_amp=2 * np.pi * 2.5,
clock_period=4,
min_duration=16,
max_duration=2**26,
),
Rydberg.Local(
max_abs_detuning=2 * np.pi * 20,
max_amp=2 * np.pi * 10,
min_retarget_interval=220,
fixed_retarget_t=0,
max_targets=1,
clock_period=4,
min_duration=16,
max_duration=2**26,
),
Raman.Local(
max_abs_detuning=2 * np.pi * 20,
max_amp=2 * np.pi * 10,
min_retarget_interval=220,
fixed_retarget_t=0,
max_targets=1,
clock_period=4,
min_duration=16,
max_duration=2**26,
with warnings.catch_warnings():
warnings.simplefilter("ignore")
HGSilveri marked this conversation as resolved.
Show resolved Hide resolved
Chadoq2 = Device(
name="Chadoq2",
dimensions=2,
rydberg_level=70,
max_atom_num=100,
max_radial_distance=50,
min_atom_distance=4,
supports_slm_mask=True,
channel_objects=(
Rydberg.Global(
max_abs_detuning=2 * np.pi * 20,
max_amp=2 * np.pi * 2.5,
clock_period=4,
min_duration=16,
max_duration=2**26,
),
Rydberg.Local(
max_abs_detuning=2 * np.pi * 20,
max_amp=2 * np.pi * 10,
min_retarget_interval=220,
fixed_retarget_t=0,
max_targets=1,
clock_period=4,
min_duration=16,
max_duration=2**26,
),
Raman.Local(
max_abs_detuning=2 * np.pi * 20,
max_amp=2 * np.pi * 10,
min_retarget_interval=220,
fixed_retarget_t=0,
max_targets=1,
clock_period=4,
min_duration=16,
max_duration=2**26,
),
),
),
dmm_objects=(
DMM(
clock_period=4,
min_duration=16,
max_duration=2**26,
bottom_detuning=-20,
dmm_objects=(
DMM(
clock_period=4,
min_duration=16,
max_duration=2**26,
bottom_detuning=-2 * np.pi * 20,
# TODO: total_bottom_detuning=-2 * np.pi * 2000
),
),
),
)
)

IroiseMVP = Device(
name="IroiseMVP",
Expand Down
18 changes: 16 additions & 2 deletions pulser-core/pulser/json/abstract_repr/schemas/device-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"type": "string"
},
"bottom_detuning": {
"description": "Minimum possible detuning (in rad/µs), must be below zero.",
"description": "Minimum possible detuning per trap (in rad/µs), must be below zero.",
"type": [
"number",
"null"
Expand Down Expand Up @@ -81,6 +81,13 @@
"number",
"null"
]
},
"total_bottom_detuning": {
"description": "Minimum possible detuning of the whole DMM channel (in rad/µs), must be below zero.",
"type": [
"number",
"null"
]
}
},
"required": [
Expand Down Expand Up @@ -1435,7 +1442,7 @@
"type": "string"
},
"bottom_detuning": {
"description": "Minimum possible detuning (in rad/µs), must be below zero.",
"description": "Minimum possible detuning per trap (in rad/µs), must be below zero.",
madagra marked this conversation as resolved.
Show resolved Hide resolved
"type": "number"
},
"clock_period": {
Expand Down Expand Up @@ -1489,6 +1496,13 @@
"number",
"null"
]
},
"total_bottom_detuning": {
"description": "Minimum possible detuning of the whole DMM channel (in rad/µs), must be below zero.",
"type": [
"number",
"null"
]
}
},
"required": [
Expand Down
5 changes: 1 addition & 4 deletions pulser-core/pulser/register/_reg_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,7 @@ def _draw_2D(
):
raise ValueError("masked qubits and dmm qubits must be the same.")
elif masked_qubits:
dmm_qubits = {
masked_qubit: 1.0 / len(masked_qubits)
for masked_qubit in masked_qubits
}
dmm_qubits = {masked_qubit: 1.0 for masked_qubit in masked_qubits}

if dmm_qubits:
dmm_pos = []
Expand Down
3 changes: 1 addition & 2 deletions pulser-core/pulser/register/base_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,7 @@ def define_detuning_map(

Args:
detuning_weights: A mapping between the IDs of the targeted qubits
and detuning weights (between 0 and 1, their sum must be equal
to 1).
and detuning weights (between 0 and 1).
slug: An optional identifier for the detuning map.

Returns:
Expand Down
3 changes: 1 addition & 2 deletions pulser-core/pulser/register/mappable_reg.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ def define_detuning_map(

Args:
detuning_weights: A mapping between the IDs of the targeted traps
and detuning weights (between 0 and 1, their sum must be equal
to 1).
and detuning weights (between 0 and 1).
slug: An optional identifier for the detuning map.

Returns:
Expand Down
3 changes: 1 addition & 2 deletions pulser-core/pulser/register/register_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ def define_detuning_map(

Args:
detuning_weights: A mapping between the IDs of the targeted traps
and detuning weights (between 0 and 1, their sum must be equal
to 1).
and detuning weights (between 0 and 1).
slug: An optional identifier for the detuning map.

Returns:
Expand Down
19 changes: 9 additions & 10 deletions pulser-core/pulser/register/weight_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@
class WeightMap(Traps, RegDrawer):
"""Defines a generic map of weights on traps.

The sum of the provided weights must be equal to 1.

Args:
trap_coordinates: An array containing the coordinates of the traps.
weights: A list weights to associate to the traps.
weights: A list of weights (between 0 and 1) to associate to the traps.
"""

weights: tuple[float, ...]
Expand All @@ -56,10 +54,10 @@ def __init__(
super().__init__(trap_coordinates, slug)
if len(cast(list, trap_coordinates)) != len(weights):
raise ValueError("Number of traps and weights don't match.")
if not np.all(np.array(weights) >= 0):
raise ValueError("All weights must be non-negative.")
if not np.isclose(sum(weights), 1.0, atol=1e-16):
raise ValueError("The sum of the weights should be 1.")
if not (
np.all(np.array(weights) >= 0) and np.all(np.array(weights) <= 1)
):
raise ValueError("All weights must be between 0 and 1.")
object.__setattr__(self, "weights", tuple(weights))

@property
Expand Down Expand Up @@ -173,10 +171,11 @@ def _to_abstract_repr(self) -> dict[str, Any]:
class DetuningMap(WeightMap):
"""Defines a DetuningMap.

A DetuningMap associates a detuning weight to the coordinates of a trap.
The sum of the provided weights must be equal to 1.
A DetuningMap associates a detuning weight (a value between 0 and 1)
to the coordinates of a trap.

Args:
trap_coordinates: An array containing the coordinates of the traps.
weights: A list of detuning weights to associate to the traps.
weights: A list of detuning weights (between 0 and 1) to associate
to the traps.
"""
Loading