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

Bring in changes from the v0.18.1 hotfix #692

Merged
merged 4 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.11"]
python-version: ["3.8", "3.12"]
steps:
- name: Check out Pulser
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- name: Check out Pulser
uses: actions/checkout@v4
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
# Python 3.8 and 3.9 does not run on macos-latest (14)
# Uses macos-13 for 3.8 and 3.9 and macos-latest for >=3.10
os: [ubuntu-latest, macos-13, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
exclude:
- os: macos-latest
python-version: "3.8"
Expand All @@ -28,6 +28,8 @@ jobs:
python-version: "3.10"
- os: macos-13
python-version: "3.11"
- os: macos-13
python-version: "3.12"
steps:
- name: Check out Pulser
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.19dev0
0.19dev1
4 changes: 3 additions & 1 deletion pulser-core/pulser/channels/eom.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@ def __post_init__(self) -> None:
"There must be at least one beam in 'controlled_beams'."
)
for beam in chain((self.limiting_beam,), self.controlled_beams):
if not (isinstance(beam, RydbergBeam) and beam in RydbergBeam):
if not (
isinstance(beam, RydbergBeam) and beam in tuple(RydbergBeam)
):
raise TypeError(
"Every beam must be one of options of the `RydbergBeam`"
f" enumeration, not {self.limiting_beam}."
Expand Down
22 changes: 22 additions & 0 deletions pulser-core/pulser/pulse.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

__all__ = ["Pulse"]

PHASE_PRECISION = 1e-6


@dataclass(init=False, repr=False, frozen=True)
class Pulse:
Expand Down Expand Up @@ -263,6 +265,26 @@ def __repr__(self) -> str:
+ f"post_phase_shift={self.post_phase_shift:.3g})"
)

def __eq__(self, other: Any) -> bool:
if type(other) is not type(self):
return False

def check_phase_eq(phase1: float, phase2: float) -> np.bool_:
# Comparing with an offset ensures we don't fail just because
# we are very close to the wraping point
return np.isclose(phase1, phase2, atol=1e-6) or np.isclose(
(phase1 + 1) % (2 * np.pi),
(phase2 + 1) % (2 * np.pi),
atol=PHASE_PRECISION,
)

return bool(
self.amplitude == other.amplitude
and self.detuning == other.detuning
and check_phase_eq(self.phase, other.phase)
and check_phase_eq(self.post_phase_shift, other.post_phase_shift)
)


# Replicate __init__'s signature in __new__
functools.update_wrapper(Pulse.__new__, Pulse.__init__)
26 changes: 17 additions & 9 deletions pulser-core/pulser/sequence/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,10 @@ def enable_eom_mode(

if not self.is_parametrized():
phase_drift_params = _PhaseDriftParams(
drift_rate=-detuning_off, ti=self.get_duration(channel)
drift_rate=-detuning_off,
# enable_eom() calls wait for fall, so the block only
# starts after fall time
ti=self.get_duration(channel, include_fall_time=True),
)
self._schedule.enable_eom(
channel, amp_on, detuning_on, detuning_off, switching_beams
Expand Down Expand Up @@ -2073,16 +2076,21 @@ def _add(
phase_drift_params=phase_drift_params,
)

true_finish = self._last(channel).tf + pulse.fall_time(
channel_obj, in_eom_mode=self.is_in_eom_mode(channel)
)
new_pulse_slot = self._last(channel)
for qubit in last.targets:
self._basis_ref[basis][qubit].update_last_used(true_finish)

if pulse.post_phase_shift:
self._phase_shift(
pulse.post_phase_shift, *last.targets, basis=basis
self._basis_ref[basis][qubit].update_last_used(new_pulse_slot.tf)

total_phase_shift = pulse.post_phase_shift
if phase_drift_params:
# The phase correction done to the EOM pulse's phase must
# also be done to the phase shift, as the phase reference is
# effectively changed by -drift
total_phase_shift = (
total_phase_shift
- phase_drift_params.calc_phase_drift(new_pulse_slot.ti)
)
if total_phase_shift:
self._phase_shift(total_phase_shift, *last.targets, basis=basis)
if (
self._in_ising
and self._slm_mask_dmm
Expand Down
10 changes: 10 additions & 0 deletions tests/test_pulse.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,13 @@ def test_full_duration(eom_channel):
assert pls.get_full_duration(
eom_channel, in_eom_mode=True
) == pls.duration + pls.fall_time(eom_channel, in_eom_mode=True)


def test_eq():
assert (pls_ := Pulse.ConstantPulse(100, 1, -1, 0)) == Pulse(
ConstantWaveform(100, 1),
ConstantWaveform(100, -1),
1e-6,
post_phase_shift=-1e-6,
)
assert pls_ != repr(pls_)
45 changes: 34 additions & 11 deletions tests/test_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -2140,10 +2140,16 @@ def test_multiple_index_targets(reg):
assert built_seq._last("ch0").targets == {"q2", "q3"}


@pytest.mark.parametrize("check_wait_for_fall", (True, False))
@pytest.mark.parametrize("correct_phase_drift", (True, False))
@pytest.mark.parametrize("custom_buffer_time", (None, 400))
def test_eom_mode(
reg, mod_device, custom_buffer_time, correct_phase_drift, patch_plt_show
reg,
mod_device,
custom_buffer_time,
correct_phase_drift,
check_wait_for_fall,
patch_plt_show,
):
# Setting custom_buffer_time
channels = mod_device.channels
Expand Down Expand Up @@ -2199,8 +2205,14 @@ def test_eom_mode(
first_pulse_slot = seq._schedule["ch0"].last_pulse_slot()
assert first_pulse_slot.ti == delay_slot.tf
assert first_pulse_slot.tf == first_pulse_slot.ti + pulse_duration
phase = detuning_off * first_pulse_slot.ti * 1e-3 * correct_phase_drift
eom_pulse = Pulse.ConstantPulse(pulse_duration, amp_on, detuning_on, phase)
phase_ref = (
detuning_off * first_pulse_slot.ti * 1e-3 * correct_phase_drift
) % (2 * np.pi)
# The phase correction becomes the new phase reference point
assert seq.current_phase_ref("q0", basis="ground-rydberg") == phase_ref
eom_pulse = Pulse.ConstantPulse(
pulse_duration, amp_on, detuning_on, phase_ref
)
assert first_pulse_slot.type == eom_pulse
assert not seq._schedule["ch0"].is_detuned_delay(eom_pulse)

Expand All @@ -2219,9 +2231,9 @@ def test_eom_mode(
)
assert second_pulse_slot.ti == first_pulse_slot.tf + phase_buffer
# Corrects the phase acquired during the phase buffer
phase_ += detuning_off * phase_buffer * 1e-3 * correct_phase_drift
phase_ref += detuning_off * phase_buffer * 1e-3 * correct_phase_drift
assert second_pulse_slot.type == Pulse.ConstantPulse(
pulse_duration, amp_on, detuning_on, phase_
pulse_duration, amp_on, detuning_on, phase_ + phase_ref
)

# Check phase jump buffer is not enforced with "no-delay"
Expand Down Expand Up @@ -2253,8 +2265,15 @@ def test_eom_mode(
)
assert buffer_delay.type == "delay"

assert seq.current_phase_ref("q0", basis="ground-rydberg") == 0
assert seq.current_phase_ref("q0", basis="ground-rydberg") == phase_ref
# Check buffer when EOM is not enabled at the start of the sequence
interval_time = 0
if check_wait_for_fall:
cte_pulse = Pulse.ConstantPulse(100, 1, 0, 0)
seq.add(cte_pulse, "ch0")
interval_time = cte_pulse.duration + cte_pulse.fall_time(
seq.declared_channels["ch0"]
)
seq.enable_eom_mode(
"ch0",
amp_on,
Expand All @@ -2266,7 +2285,7 @@ def test_eom_mode(
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
assert last_slot.ti == buffer_delay.tf + interval_time
duration = last_slot.tf - last_slot.ti
assert (
duration == custom_buffer_time
Expand All @@ -2277,21 +2296,25 @@ def test_eom_mode(
duration, 0.0, new_eom_block.detuning_off, last_pulse_slot.type.phase
)
# Check the phase shift that corrects for the drift
phase_ref = (
phase_ref += (
(new_eom_block.detuning_off * duration * 1e-3)
% (2 * np.pi)
* correct_phase_drift
)
assert seq.current_phase_ref("q0", basis="ground-rydberg") == phase_ref
assert np.isclose(
seq.current_phase_ref("q0", basis="ground-rydberg"),
phase_ref % (2 * np.pi),
)

# Add delay to test the phase drift correction in disable_eom_mode
last_delay_time = 400
seq.delay(last_delay_time, "ch0")

seq.disable_eom_mode("ch0", correct_phase_drift=True)
phase_ref += new_eom_block.detuning_off * last_delay_time * 1e-3
assert seq.current_phase_ref("q0", basis="ground-rydberg") == phase_ref % (
2 * np.pi
assert np.isclose(
seq.current_phase_ref("q0", basis="ground-rydberg"),
phase_ref % (2 * np.pi),
)

# Test drawing in eom mode
Expand Down