From 67d9242e937fa787f83375bee11869de8a6d8045 Mon Sep 17 00:00:00 2001 From: a_corni Date: Fri, 24 Nov 2023 12:10:14 +0100 Subject: [PATCH] Taking into account review comments --- pulser-core/pulser/channels/dmm.py | 23 +++++++--- pulser-core/pulser/devices/_devices.py | 2 +- pulser-core/pulser/sequence/sequence.py | 42 ++++++++++--------- .../Local addressability with DMM.ipynb | 2 +- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/pulser-core/pulser/channels/dmm.py b/pulser-core/pulser/channels/dmm.py index 273ef2602..7c9cddbb8 100644 --- a/pulser-core/pulser/channels/dmm.py +++ b/pulser-core/pulser/channels/dmm.py @@ -13,7 +13,7 @@ # limitations under the License. """Defines the detuning map modulator.""" from __future__ import annotations - +import warnings from dataclasses import dataclass, field from typing import Literal, Optional @@ -33,7 +33,7 @@ class DMM(Channel): 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`, and the sum of the weights multiplied by - that detuning has to be blow `total_bottom_detuning`. Channel targeting + 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. @@ -56,8 +56,8 @@ class DMM(Channel): MHz. """ - bottom_detuning: Optional[float] = field(default=None, init=True) - total_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) @@ -89,11 +89,22 @@ def basis(self) -> Literal["ground-rydberg"]: def _undefined_fields(self) -> list[str]: optional = [ "bottom_detuning", - "total_bottom_detuning", "max_duration", + # TODO: "total_bottom_detuning" ] return [field for field in optional if getattr(self, field) is None] + 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( + "`total_bottom_detuning` should be defined to define a" + " physical DMM.", + DeprecationWarning, + ) + return virtual_dmm + def validate_pulse( self, pulse: Pulse, @@ -131,7 +142,7 @@ def validate_pulse( < self.total_bottom_detuning ): raise ValueError( - "The applied detuning goes below the global bottom detuning " + "The applied detuning goes below the total bottom detuning " f"of the DMM ({self.total_bottom_detuning} rad/µs)." ) diff --git a/pulser-core/pulser/devices/_devices.py b/pulser-core/pulser/devices/_devices.py index a93958b17..d39acec13 100644 --- a/pulser-core/pulser/devices/_devices.py +++ b/pulser-core/pulser/devices/_devices.py @@ -62,7 +62,7 @@ min_duration=16, max_duration=2**26, bottom_detuning=-2 * np.pi * 20, - total_bottom_detuning=-2 * np.pi * 2000, + total_bottom_detuning=None, # -2 * np.pi * 2000 ), ), ) diff --git a/pulser-core/pulser/sequence/sequence.py b/pulser-core/pulser/sequence/sequence.py index 1452740ea..fcff74d88 100644 --- a/pulser-core/pulser/sequence/sequence.py +++ b/pulser-core/pulser/sequence/sequence.py @@ -613,7 +613,7 @@ def config_detuning_map( ``MockDevice`` DMM can be repeatedly declared if needed. Args: - detuning_map: A DetuningMap defining the amont of detuning each + detuning_map: A DetuningMap defining the amount of detuning each atom receives. dmm_id: How the channel is identified in the device. See in ``Sequence.available_channels`` which DMM IDs are still @@ -647,8 +647,8 @@ def _config_detuning_map( dmm_name = dmm_id if dmm_id in self.declared_channels: assert self._device.reusable_channels - dmm_name += ( - f"_{''.join(self.declared_channels.keys()).count(dmm_id)}" + dmm_name = _get_dmm_name( + dmm_id, list(self.declared_channels.keys()) ) self._schedule[dmm_name] = _DMMSchedule( @@ -2195,42 +2195,46 @@ def _validate_channel( def _validate_and_adjust_pulse( self, pulse: Pulse, channel: str, phase_ref: Optional[float] = None ) -> Pulse: + # Get the channel object and its detuning map if the channel is a DMM channel_obj: Channel + # Detuning map is None if channel is not DMM detuning_map: DetuningMap | None = None if channel in self._schedule: + # channel name can refer to a Channel or a DMM object + # Get channel object channel_obj = self._schedule[channel].channel_obj + # Get its associated detuning map if channel is a DMM if isinstance(channel_obj, DMM): + # stored in _DMMSchedule with channel object detuning_map = cast( _DMMSchedule, self._schedule[channel] ).detuning_map else: + # If channel name can't be found among _schedule keys, the # Sequence is parametrized and channel is a dmm_name dmm_id = _dmm_id_from_name(channel) + # Get channel object channel_obj = self.device.dmm_channels[dmm_id] - dmm_idx = -1 + # Go over the calls to find the associated detuning map + declared_dmms: list[str] = [] for call in self._calls[1:] + self._to_build_calls: if ( call.name == "config_detuning_map" or call.name == "config_slm_mask" ): - # Check whether dmm_name matches with channel - ( - current_dmm, - current_det_map, - ) = self._get_dmm_id_detuning_map(call) - if current_dmm == dmm_id: - dmm_idx += 1 - current_dmm_name = ( - current_dmm - if dmm_idx == 0 - else current_dmm + f"_{dmm_idx}" - ) - if current_dmm_name == channel: - detuning_map = current_det_map - break + # Extract dmm_id, detuning map of call + call_id, call_det_map = self._get_dmm_id_detuning_map(call) + # Quit if dmm_name of call matches with channel + call_name = _get_dmm_name(call_id, declared_dmms) + declared_dmms.append(call_name) + if call_name == channel: + detuning_map = call_det_map + break if detuning_map is None: + # channel points to a Channel object channel_obj.validate_pulse(pulse) else: + # channel points to a DMM object cast(DMM, channel_obj).validate_pulse(pulse, detuning_map) _duration = channel_obj.validate_duration(pulse.duration) new_phase = pulse.phase + (phase_ref if phase_ref else 0) diff --git a/tutorials/advanced_features/Local addressability with DMM.ipynb b/tutorials/advanced_features/Local addressability with DMM.ipynb index 2fc5a1fcc..21305ec73 100644 --- a/tutorials/advanced_features/Local addressability with DMM.ipynb +++ b/tutorials/advanced_features/Local addressability with DMM.ipynb @@ -143,7 +143,7 @@ "source": [ "A `DMM` Channel is a `Channel` that accepts pulses of zero amplitude and detuning below 0 and above:\n", "- `bottom_detuning` for each site.\n", - "- `global_bottom_detuning` for the total detuning distributed among the atoms:" + "- `total_bottom_detuning` for the total detuning distributed among the atoms:" ] }, {