From 95e58d0686338c33bdd8cc323245679e77d28234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Silv=C3=A9rio?= Date: Wed, 11 Jan 2023 16:30:28 +0100 Subject: [PATCH] Including detuning in the EOM buffer (#444) * Include detuning on the EOM mode buffer * Update the samples' modulation * Mypy + isort * UTs * Bump to v0.8.1 * Fix detuning modulation when starting in EOM mode * Updating docs and tutorials * Replacing `is_eom_delay` with `is_detuned_delay` * Correct phase sampling * Improving documentation --- VERSION.txt | 2 +- pulser-core/pulser/sampler/samples.py | 119 +++++++++++++++--- pulser-core/pulser/sequence/_schedule.py | 87 ++++++++----- pulser-core/pulser/sequence/_seq_str.py | 6 +- pulser-core/pulser/sequence/sequence.py | 7 +- tests/test_sequence.py | 22 +++- tests/test_sequence_sampler.py | 75 +++++++++-- .../Output Modulation and EOM Mode.ipynb | 4 +- 8 files changed, 256 insertions(+), 66 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index a3df0a695..6f4eebdf6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.8.0 +0.8.1 diff --git a/pulser-core/pulser/sampler/samples.py b/pulser-core/pulser/sampler/samples.py index 4e21ac0bb..e39347039 100644 --- a/pulser-core/pulser/sampler/samples.py +++ b/pulser-core/pulser/sampler/samples.py @@ -3,13 +3,17 @@ from collections import defaultdict from dataclasses import dataclass, field, replace -from typing import Optional +from typing import TYPE_CHECKING, Optional, cast import numpy as np from pulser.channels.base_channel import Channel +from pulser.channels.eom import BaseEOM from pulser.register import QubitId +if TYPE_CHECKING: + from pulser.sequence._schedule import _EOMSettings + """Literal constants for addressing.""" _GLOBAL = "Global" _LOCAL = "Local" @@ -86,8 +90,7 @@ class ChannelSamples: det: np.ndarray phase: np.ndarray slots: list[_TargetSlot] = field(default_factory=list) - # (t_start, t_end) of each EOM mode block - eom_intervals: list[tuple[int, int]] = field(default_factory=list) + eom_blocks: list[_EOMSettings] = field(default_factory=list) def __post_init__(self) -> None: assert len(self.amp) == len(self.det) == len(self.phase) @@ -116,7 +119,17 @@ def extend_duration(self, new_duration: int) -> ChannelSamples: raise ValueError("Can't extend samples to a lower duration.") new_amp = np.pad(self.amp, (0, extension)) - new_detuning = np.pad(self.det, (0, extension)) + # When in EOM mode, we need to keep the detuning at detuning_off + if self.eom_blocks and self.eom_blocks[-1].tf is None: + final_detuning = self.eom_blocks[-1].detuning_off + else: + final_detuning = 0.0 + new_detuning = np.pad( + self.det, + (0, extension), + constant_values=(final_detuning,), + mode="constant", + ) new_phase = np.pad( self.phase, (0, extension), @@ -153,29 +166,103 @@ def modulate( def masked(samples: np.ndarray, mask: np.ndarray) -> np.ndarray: new_samples = samples.copy() + # Extend the mask to fit the size of the samples + mask = np.pad(mask, (0, len(new_samples) - len(mask)), mode="edge") new_samples[~mask] = 0 return new_samples new_samples: dict[str, np.ndarray] = {} - if self.eom_intervals: + std_samples = { + key: getattr(self, key).copy() for key in ("amp", "det") + } + eom_samples = { + key: getattr(self, key).copy() for key in ("amp", "det") + } + + if self.eom_blocks: + # Note: self.duration already includes the fall time eom_mask = np.zeros(self.duration, dtype=bool) - for start, end in self.eom_intervals: - end = min(end, self.duration) # This is defensive - eom_mask[np.arange(start, end)] = True + # Extension of the EOM mask outside of the EOM interval + eom_mask_ext = eom_mask.copy() + eom_fall_time = 2 * cast(BaseEOM, channel_obj.eom_config).rise_time + for block in self.eom_blocks: + # If block.tf is None, uses the full duration as the tf + end = block.tf or self.duration + eom_mask[block.ti : end] = True + std_samples["amp"][block.ti : end] = 0 + # For modulation purposes, the detuning on the standard + # samples is kept at 'detuning_off', which permits a smooth + # transition to/from the EOM modulated samples + std_samples["det"][block.ti : end] = block.detuning_off + # Extends EOM masks to include fall time + ext_end = end + eom_fall_time + eom_mask_ext[end:ext_end] = True + + # We need 'eom_mask_ext' on its own, but we can already add it + # to the 'eom_mask' + eom_mask = eom_mask + eom_mask_ext + + if block.tf is None: + # The sequence finishes in EOM mode, so 'end' was already + # including the fall time (unlike when it is disabled). + # For modulation, we make the detuning during the last + # fall time to be kept at 'detuning_off' + eom_samples["det"][-eom_fall_time:] = block.detuning_off for key in ("amp", "det"): - samples = getattr(self, key) - std = channel_obj.modulate(masked(samples, ~eom_mask)) - eom = channel_obj.modulate(masked(samples, eom_mask), eom=True) + # First, we modulated the pre-filtered standard samples, then + # we mask them to include only the parts outside the EOM mask + # This ensures smooth transitions between EOM and STD samples + modulated_std = channel_obj.modulate(std_samples[key]) + std = masked(modulated_std, ~eom_mask) + + # At the end of an EOM block, the EOM(s) are switched back + # to the OFF configuration, so the detuning should go quickly + # back to `detuning_off`. + # However, the applied detuning and the lightshift are + # simultaneously being ramped to zero, so the fast ramp doesn't + # reach `detuning_off` but rather a modified detuning value + # (closer to zero). Then, the detuning goes slowly + # to zero (as dictacted by the standard modulation bandwidth). + # To mimick this effect, we substitute the detuning at the end + # of each block by the standard modulated detuning during the + # transition period, so the EOM modulation is superimposed on + # the standard modulation + if key == "det": + samples_ = eom_samples[key] + samples_[eom_mask_ext] = modulated_std[ + : len(eom_mask_ext) + ][eom_mask_ext] + # Starts out in EOM mode, so we prepend 'detuning_off' + # such that the modulation starts off from that value + # We then remove the extra value after modulation + if eom_mask[0]: + samples_ = np.insert( + samples_, + 0, + self.eom_blocks[0].detuning_off, + ) + # Finally, the modified EOM samples are modulated + modulated_eom = channel_obj.modulate( + samples_, eom=True, keep_ends=True + )[(1 if eom_mask[0] else 0) :] + else: + modulated_eom = channel_obj.modulate( + eom_samples[key], eom=True + ) + + # filtered to include only the parts inside the EOM mask + eom = masked(modulated_eom, eom_mask) + + # 'std' and 'eom' are then summed, but before the shortest + # array is extended so that they are of the same length sample_arrs = [std, eom] sample_arrs.sort(key=len) # Extend shortest array to match the longest - sample_arrs[0] = np.concatenate( - ( - sample_arrs[0], - np.zeros(sample_arrs[1].size - sample_arrs[0].size), - ) + sample_arrs[0] = np.pad( + sample_arrs[0], + (0, sample_arrs[1].size - sample_arrs[0].size), ) new_samples[key] = sample_arrs[0] + sample_arrs[1] diff --git a/pulser-core/pulser/sequence/_schedule.py b/pulser-core/pulser/sequence/_schedule.py index b9ae04f5f..0a9d03f4d 100644 --- a/pulser-core/pulser/sequence/_schedule.py +++ b/pulser-core/pulser/sequence/_schedule.py @@ -62,10 +62,12 @@ def last_target(self) -> int: return slot.tf return 0 # pragma: no cover - def last_pulse_slot(self) -> _TimeSlot: + def last_pulse_slot(self, ignore_detuned_delay: bool = False) -> _TimeSlot: """The last slot with a Pulse.""" for slot in self.slots[::-1]: - if isinstance(slot.type, Pulse) and not self.is_eom_delay(slot): + if isinstance(slot.type, Pulse) and not ( + ignore_detuned_delay and self.is_detuned_delay(slot.type) + ): return slot raise RuntimeError("There is no slot with a pulse.") @@ -78,13 +80,14 @@ def in_eom_mode(self, time_slot: Optional[_TimeSlot] = None) -> bool: for start, end in self.get_eom_mode_intervals() ) - def is_eom_delay(self, slot: _TimeSlot) -> bool: - """Tells if a pulse slot is actually an EOM delay.""" + @staticmethod + def is_detuned_delay(pulse: Pulse) -> bool: + """Tells if a pulse is actually a delay with a constant detuning.""" return ( - self.in_eom_mode(time_slot=slot) - and isinstance(slot.type, Pulse) - and isinstance(slot.type.amplitude, ConstantWaveform) - and slot.type.amplitude[0] == 0.0 + isinstance(pulse, Pulse) + and isinstance(pulse.amplitude, ConstantWaveform) + and pulse.amplitude[0] == 0.0 + and isinstance(pulse.detuning, ConstantWaveform) ) def get_eom_mode_intervals(self) -> list[tuple[int, int]]: @@ -138,14 +141,7 @@ def get_samples(self) -> ChannelSamples: pulse = cast(Pulse, s.type) amp[s.ti : s.tf] += pulse.amplitude.samples det[s.ti : s.tf] += pulse.detuning.samples - ph_jump_t = self.channel_obj.phase_jump_time - t_start = s.ti - ph_jump_t if ind > 0 else 0 - t_end = ( - channel_slots[ind + 1].ti - ph_jump_t - if ind < len(channel_slots) - 1 - else dt - ) - phase[t_start:t_end] += pulse.phase + tf = s.tf # Account for the extended duration of the pulses # after modulation, which is at most fall_time @@ -157,14 +153,30 @@ def get_samples(self) -> ChannelSamples: if ind < len(channel_slots) - 1 else fall_time ) - slots.append(_TargetSlot(s.ti, tf, s.targets)) - ch_samples = ChannelSamples( - amp, det, phase, slots, self.get_eom_mode_intervals() - ) + # The phase of detuned delays is not considered + if self.is_detuned_delay(pulse): + continue - return ch_samples + ph_jump_t = self.channel_obj.phase_jump_time + for last_pulse_ind in range(ind - 1, -1, -1): # From ind-1 to 0 + last_pulse_slot = channel_slots[last_pulse_ind] + # Skips over detuned delay pulses + if not self.is_detuned_delay( + cast(Pulse, last_pulse_slot.type) + ): + # Accounts for when pulse is added with 'no-delay' + # i.e. there is no phase_jump_time in between a phase jump + t_start = max(s.ti - ph_jump_t, last_pulse_slot.tf) + break + else: + t_start = 0 + # Overrides all values from t_start on. The next pulses will do + # the same, so the last phase is automatically kept till the endm + phase[t_start:] = pulse.phase + + return ChannelSamples(amp, det, phase, slots, self.eom_blocks) @overload def __getitem__(self, key: int) -> _TimeSlot: @@ -233,7 +245,20 @@ def enable_eom( # Account for time needed to ramp to desired amplitude # By definition, rise_time goes from 10% to 90% # Roughly 2*rise_time is enough to go from 0% to 100% - self.add_delay(2 * channel_obj.rise_time, channel_id) + if detuning_off != 0: + self.add_pulse( + Pulse.ConstantPulse( + 2 * channel_obj.rise_time, + 0.0, + detuning_off, + self._get_last_pulse_phase(channel_id), + ), + channel_id, + phase_barrier_ts=[0], + protocol="no-delay", + ) + else: + self.add_delay(2 * channel_obj.rise_time, channel_id) # Set up the EOM eom_settings = _EOMSettings( @@ -268,7 +293,9 @@ def add_pulse( ) try: # Gets the last pulse on the channel - last_pulse_slot = self[channel].last_pulse_slot() + last_pulse_slot = self[channel].last_pulse_slot( + ignore_detuned_delay=True + ) last_pulse = cast(Pulse, last_pulse_slot.type) # Checks if the current pulse changes the phase if last_pulse.phase != pulse.phase: @@ -304,11 +331,7 @@ def add_delay(self, duration: int, channel: str) -> None: self[channel].in_eom_mode() and self[channel].eom_blocks[-1].detuning_off != 0 ): - try: - last_pulse = cast(Pulse, self[channel].last_pulse_slot().type) - phase = last_pulse.phase - except RuntimeError: - phase = 0.0 + phase = self._get_last_pulse_phase(channel) delay_pulse = Pulse.ConstantPulse( tf - ti, 0.0, self[channel].eom_blocks[-1].detuning_off, phase ) @@ -385,3 +408,11 @@ def _find_add_delay(self, t0: int, channel: str, protocol: str) -> int: break return current_max_t + + def _get_last_pulse_phase(self, channel: str) -> float: + try: + last_pulse = cast(Pulse, self[channel].last_pulse_slot().type) + phase = last_pulse.phase + except RuntimeError: + phase = 0.0 + return phase diff --git a/pulser-core/pulser/sequence/_seq_str.py b/pulser-core/pulser/sequence/_seq_str.py index 837ab59a8..f7ee048ba 100644 --- a/pulser-core/pulser/sequence/_seq_str.py +++ b/pulser-core/pulser/sequence/_seq_str.py @@ -28,7 +28,7 @@ def seq_to_str(sequence: Sequence) -> str: pulse_line = "t: {}->{} | {} | Targets: {}\n" target_line = "t: {}->{} | Target: {} | Phase Reference: {}\n" delay_line = "t: {}->{} | Delay \n" - eom_delay_line = "t: {}->{} | EOM Delay | Detuning: {:.3g} rad/µs\n" + det_delay_line = "t: {}->{} | Detuned Delay | Detuning: {:.3g} rad/µs\n" for ch, seq in sequence._schedule.items(): basis = sequence.declared_channels[ch].basis full += f"Channel: {ch}\n" @@ -46,9 +46,9 @@ def seq_to_str(sequence: Sequence) -> str: ) tgt_txt = ", ".join(map(str, tgts)) if isinstance(ts.type, Pulse): - if seq.is_eom_delay(ts): + if seq.is_detuned_delay(ts.type): det = ts.type.detuning[0] - full += eom_delay_line.format(ts.ti, ts.tf, det) + full += det_delay_line.format(ts.ti, ts.tf, det) else: full += pulse_line.format(ts.ti, ts.tf, ts.type, tgt_txt) elif ts.type == "target": diff --git a/pulser-core/pulser/sequence/sequence.py b/pulser-core/pulser/sequence/sequence.py index 05d25447f..6e6105f80 100644 --- a/pulser-core/pulser/sequence/sequence.py +++ b/pulser-core/pulser/sequence/sequence.py @@ -716,7 +716,8 @@ def enable_eom_mode( Note: Enabling the EOM mode will automatically enforce a buffer time from - the last pulse on the chose channel. + the last pulse on the chosen channel. The detuning will go to the + `detuning_off` value during this buffer. Args: channel: The name of the channel to put in EOM mode. @@ -781,8 +782,8 @@ def disable_eom_mode(self, channel: str) -> None: (through `Sequence.add_eom_pulse()`) or delays. Note: - Disable the EOM mode will automatically enforce a buffer time from - the moment it is turned off. + Disabling the EOM mode will automatically enforce a buffer time + from the moment it is turned off. Args: channel: The name of the channel to take out of EOM mode. diff --git a/tests/test_sequence.py b/tests/test_sequence.py index 2fe2e0b22..12302e275 100644 --- a/tests/test_sequence.py +++ b/tests/test_sequence.py @@ -669,7 +669,7 @@ def test_str(mod_device): "| Phase Reference: 0.0 " "\nt: 0->100 | Pulse(Amp=2 rad/µs, Detuning=0 rad/µs, Phase=0) " f"| Targets: {targets}" - "\nt: 100->600 | EOM Delay | Detuning: -1 rad/µs" + "\nt: 100->600 | Detuned Delay | Detuning: -1 rad/µs" ) measure_msg = "\n\nMeasured in basis: digital" @@ -1285,10 +1285,11 @@ def test_eom_mode(mod_device): with pytest.raises(RuntimeError, match="There is no slot with a pulse."): # The EOM delay slot (which is a pulse slot) is ignored - seq._schedule["ch0"].last_pulse_slot() + seq._schedule["ch0"].last_pulse_slot(ignore_detuned_delay=True) delay_slot = seq._schedule["ch0"][-1] - assert seq._schedule["ch0"].is_eom_delay(delay_slot) + assert seq._schedule["ch0"].in_eom_mode(delay_slot) + assert seq._schedule["ch0"].is_detuned_delay(delay_slot.type) assert delay_slot.ti == 0 assert delay_slot.tf == delay_duration assert delay_slot.type == Pulse.ConstantPulse( @@ -1302,11 +1303,11 @@ def test_eom_mode(mod_device): pulse_duration = 100 seq.add_eom_pulse("ch0", pulse_duration, phase=0.0) first_pulse_slot = seq._schedule["ch0"].last_pulse_slot() - assert not seq._schedule["ch0"].is_eom_delay(first_pulse_slot) assert first_pulse_slot.ti == delay_slot.tf assert first_pulse_slot.tf == first_pulse_slot.ti + pulse_duration eom_pulse = Pulse.ConstantPulse(pulse_duration, amp_on, detuning_on, 0.0) assert first_pulse_slot.type == eom_pulse + assert not seq._schedule["ch0"].is_detuned_delay(eom_pulse) # Check phase jump buffer seq.add_eom_pulse("ch0", pulse_duration, phase=np.pi) @@ -1343,3 +1344,16 @@ def test_eom_mode(mod_device): assert buffer_delay.ti == last_pulse_slot.tf assert buffer_delay.tf == buffer_delay.ti + eom_pulse.fall_time(ch0_obj) assert buffer_delay.type == "delay" + + # Check buffer when EOM is not enabled at the start of the sequence + seq.enable_eom_mode("ch0", amp_on, detuning_on, optimal_detuning_off=-100) + last_slot = seq._schedule["ch0"][-1] + assert len(seq._schedule["ch0"].eom_blocks) == 2 + new_eom_block = seq._schedule["ch0"].eom_blocks[1] + assert new_eom_block.detuning_off != 0 + assert last_slot.ti == buffer_delay.tf # Nothing else was added + duration = last_slot.tf - last_slot.ti + # The buffer is a Pulse at 'detuning_off' and zero amplitude + assert last_slot.type == Pulse.ConstantPulse( + duration, 0.0, new_eom_block.detuning_off, last_pulse_slot.type.phase + ) diff --git a/tests/test_sequence_sampler.py b/tests/test_sequence_sampler.py index afa8ae0fb..c3a43da1a 100644 --- a/tests/test_sequence_sampler.py +++ b/tests/test_sequence_sampler.py @@ -188,7 +188,8 @@ def test_modulation_local(mod_device): np.testing.assert_array_equal(getattr(out_ch_samples, qty), combined) -def test_eom_modulation(mod_device): +@pytest.mark.parametrize("disable_eom", [True, False]) +def test_eom_modulation(mod_device, disable_eom): seq = pulser.Sequence(pulser.Register.square(2), mod_device) seq.declare_channel("ch0", "rydberg_global") seq.enable_eom_mode("ch0", amp_on=1, detuning_on=0.0) @@ -196,12 +197,18 @@ def test_eom_modulation(mod_device): seq.delay(200, "ch0") seq.add_eom_pulse("ch0", 100, 0.0) end_of_eom = seq.get_duration() - seq.disable_eom_mode("ch0") - seq.add(Pulse.ConstantPulse(500, 1, 0, 0), "ch0") + if disable_eom: + seq.disable_eom_mode("ch0") + seq.add(Pulse.ConstantPulse(500, 1, 0, 0), "ch0") full_duration = seq.get_duration(include_fall_time=True) eom_mask = np.zeros(full_duration, dtype=bool) eom_mask[:end_of_eom] = True + ext_eom_mask = np.zeros_like(eom_mask) + eom_config = seq.declared_channels["ch0"].eom_config + ext_eom_mask[end_of_eom : end_of_eom + 2 * eom_config.rise_time] = True + + det_off = seq._schedule["ch0"].eom_blocks[-1].detuning_off input_samples = sample( seq, extended_duration=full_duration @@ -210,13 +217,23 @@ def test_eom_modulation(mod_device): chan = seq.declared_channels["ch0"] for qty in ("amp", "det"): samples = getattr(input_samples, qty) - eom_input = samples.copy() - eom_input[~eom_mask] = 0.0 - eom_output = chan.modulate(eom_input, eom=True)[:full_duration] aom_input = samples.copy() - aom_input[eom_mask] = 0.0 + aom_input[eom_mask] = det_off if qty == "det" else 0.0 aom_output = chan.modulate(aom_input, eom=False)[:full_duration] - np.testing.assert_array_equal(eom_input + aom_input, samples) + + eom_input = samples.copy() + eom_input[ext_eom_mask] = aom_output[ext_eom_mask] + if qty == "det": + if not disable_eom: + eom_input[end_of_eom:] = det_off + eom_input = np.insert(eom_input, 0, det_off) + eom_output = chan.modulate(eom_input, eom=True, keep_ends=True)[1:] + else: + eom_output = chan.modulate(eom_input, eom=True) + eom_output = eom_output[:full_duration] + + aom_output[eom_mask + ext_eom_mask] = 0.0 + eom_output[~(eom_mask + ext_eom_mask)] = 0.0 want = eom_output + aom_output @@ -225,7 +242,7 @@ def test_eom_modulation(mod_device): alt_got = getattr(input_samples.modulate(chan, full_duration), qty) np.testing.assert_array_equal(got, alt_got) - np.testing.assert_array_equal(want, got) + np.testing.assert_allclose(want, got, atol=1e-10) @pytest.fixture @@ -314,6 +331,46 @@ def test_extend_duration(seq_rydberg): assert extended_short.slots == short.slots +def test_phase_sampling(mod_device): + reg = pulser.Register.from_coordinates(np.array([[0.0, 0.0]]), prefix="q") + seq = pulser.Sequence(reg, mod_device) + seq.declare_channel("ch0", "rydberg_global") + + dt = 100 + seq.add(Pulse.ConstantPulse(dt, 1, 0, phase=1), "ch0") + # With 'no-delay', the jump should in between the two pulses + seq.add(Pulse.ConstantPulse(dt, 1, 0, phase=2), "ch0", protocol="no-delay") + # With the standard protocol, there shoud be a delay added and then + # phase jump time is accounted for + seq.add(Pulse.ConstantPulse(dt, 1, 0, phase=3), "ch0") + pulse3_start = seq.get_duration() - dt + # Detuned delay (its phase should be ignored) + seq.add( + Pulse.ConstantPulse(1000, 0, 1, phase=0), "ch0", protocol="no-delay" + ) + end_of_detuned_delay = seq.get_duration() + # phase jump time happens during the detuned delay + seq.add(Pulse.ConstantPulse(dt, 1, 0, phase=4), "ch0") + full_duration = seq.get_duration() + # Nothing was added between the detuned delay and pulse4 + assert end_of_detuned_delay == full_duration - dt + + ph_jump_time = seq.declared_channels["ch0"].phase_jump_time + assert ph_jump_time > 0 + expected_phase = np.zeros(full_duration) + expected_phase[:dt] = 1.0 + transition2_3 = pulse3_start - ph_jump_time + assert transition2_3 >= 2 * dt # = End of pulse2 + expected_phase[dt:transition2_3] = 2.0 + # The detuned delay is ignored + transition3_4 = full_duration - dt - ph_jump_time + expected_phase[transition2_3:transition3_4] = 3.0 + expected_phase[transition3_4:] = 4.0 + + got_phase = sample(seq).channel_samples["ch0"].phase + np.testing.assert_array_equal(expected_phase, got_phase) + + # Fixtures diff --git a/tutorials/advanced_features/Output Modulation and EOM Mode.ipynb b/tutorials/advanced_features/Output Modulation and EOM Mode.ipynb index 0a74f22c8..1498ec451 100644 --- a/tutorials/advanced_features/Output Modulation and EOM Mode.ipynb +++ b/tutorials/advanced_features/Output Modulation and EOM Mode.ipynb @@ -242,8 +242,8 @@ "To overcome these limitations, a channel can be equipped with an EOM that allows the execution of pulses with a higher modulation bandwidth. For now, EOM mode operation is reserved for `Rydberg` channels and works under very specific conditions:\n", "\n", " 1. EOM mode must be explicitly enabled (`Sequence.enable_eom_mode()`) and disabled (`Sequence.disable_eom_mode()`).\n", - " 2. A buffering time is automatically added before the EOM mode is enabled and after it is disabled, as it needs to be isolated from regular channel operation.\n", - " 3. When enabling the EOM mode, one must choose the amplitude and detuning value that all square pulses will have. These values will also determine a set of options for the detuning during delays, out of which one is value chosen.\n", + " 2. A buffering time is automatically added before the EOM mode is enabled and after it is disabled, as it needs to be isolated from regular channel operation. During the starting buffer, the detuning goes to the value it will assume between EOM pulses (_i.e._ during delays).\n", + " 3. When enabling the EOM mode, one must choose the amplitude and detuning value that all square pulses will have. These values will also determine a set of options for the detuning during delays, out of which one chosen.\n", " 4. While in EOM mode, one can only add delays or pulses of variable duration (through `Sequence.add_eom_pulse()`) – changing the phase between pulses is also allowed, but the necessary buffer time for a phase jump will still be enforced." ] },