From ba3f08ba24a3096cbc1b906d1c3764cbb0c752cb Mon Sep 17 00:00:00 2001 From: HGSilveri Date: Mon, 5 Jun 2023 12:36:21 +0200 Subject: [PATCH 1/2] Fix for empty sequences on QutipEmulator --- pulser-core/pulser/sampler/samples.py | 5 +++++ pulser-core/pulser/sequence/_schedule.py | 5 ++++- pulser-simulation/pulser_simulation/simulation.py | 14 +++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pulser-core/pulser/sampler/samples.py b/pulser-core/pulser/sampler/samples.py index 49072391f..8a8a05fc2 100644 --- a/pulser-core/pulser/sampler/samples.py +++ b/pulser-core/pulser/sampler/samples.py @@ -91,6 +91,7 @@ class ChannelSamples: phase: np.ndarray slots: list[_TargetSlot] = field(default_factory=list) eom_blocks: list[_EOMSettings] = field(default_factory=list) + initial_targets: set[QubitId] = field(default_factory=set) def __post_init__(self) -> None: assert len(self.amp) == len(self.det) == len(self.phase) @@ -373,6 +374,10 @@ def to_nested_dict(self, all_local: bool = False) -> dict: d[_LOCAL][basis][t][_DET][:start_t] += cs.det[:start_t] d[_LOCAL][basis][t][_PHASE][:start_t] += cs.phase[:start_t] else: + if not cs.slots: + # Fill the defaultdict entries to not return an empty dict + for t in cs.initial_targets: + d[_LOCAL][basis][t] for s in cs.slots: for t in s.targets: ti = s.ti diff --git a/pulser-core/pulser/sequence/_schedule.py b/pulser-core/pulser/sequence/_schedule.py index 775632f05..010e1dbe2 100644 --- a/pulser-core/pulser/sequence/_schedule.py +++ b/pulser-core/pulser/sequence/_schedule.py @@ -138,6 +138,7 @@ def get_samples( dt = self.get_duration() amp, det, phase = np.zeros(dt), np.zeros(dt), np.zeros(dt) slots: list[_TargetSlot] = [] + initial_targets = self.slots[0].targets if self.slots else set() for ind, s in enumerate(channel_slots): pulse = cast(Pulse, s.type) @@ -181,7 +182,9 @@ def get_samples( # the same, so the last phase is automatically kept till the end phase[t_start:] = pulse.phase - return ChannelSamples(amp, det, phase, slots, self.eom_blocks) + return ChannelSamples( + amp, det, phase, slots, self.eom_blocks, initial_targets + ) @overload def __getitem__(self, key: int) -> _TimeSlot: diff --git a/pulser-simulation/pulser_simulation/simulation.py b/pulser-simulation/pulser_simulation/simulation.py index ef62689ff..bd3a1a67c 100644 --- a/pulser-simulation/pulser_simulation/simulation.py +++ b/pulser-simulation/pulser_simulation/simulation.py @@ -894,10 +894,8 @@ def run( .. _docs: https://bit.ly/3il9A2u """ - if "max_step" in options.keys(): - solv_ops = qutip.Options(**options) - else: - min_pulse_duration = min( + if "max_step" not in options: + pulse_durations = [ slot.tf - slot.ti for ch_sample in self.samples_obj.samples_list for slot in ch_sample.slots @@ -905,9 +903,11 @@ def run( np.all(np.isclose(ch_sample.amp[slot.ti : slot.tf], 0)) and np.all(np.isclose(ch_sample.det[slot.ti : slot.tf], 0)) ) - ) - auto_max_step = 0.5 * (min_pulse_duration / 1000) - solv_ops = qutip.Options(max_step=auto_max_step, **options) + ] + if pulse_durations: + options["max_step"] = 0.5 * min(pulse_durations) / 1000 + + solv_ops = qutip.Options(**options) meas_errors: Optional[Mapping[str, float]] = None if "SPAM" in self.config.noise: From 4a47db1fe263e014edf8e7d897e6bb4cb4af3c13 Mon Sep 17 00:00:00 2001 From: HGSilveri Date: Mon, 5 Jun 2023 13:23:18 +0200 Subject: [PATCH 2/2] Add UTs --- tests/test_sequence_sampler.py | 26 ++++++++++++++++++++++++++ tests/test_simulation.py | 10 +++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/test_sequence_sampler.py b/tests/test_sequence_sampler.py index 14971fd6b..fb253fd0c 100644 --- a/tests/test_sequence_sampler.py +++ b/tests/test_sequence_sampler.py @@ -74,6 +74,32 @@ def test_init_error(seq_rydberg): sample(seq_rydberg) +@pytest.mark.parametrize("local_only", [True, False]) +def test_delay_only(local_only): + seq_ = pulser.Sequence(pulser.Register({"q0": (0, 0)}), MockDevice) + seq_.declare_channel("ch0", "rydberg_global") + seq_.delay(16, "ch0") + samples = sample(seq_) + assert samples.channel_samples["ch0"].initial_targets == {"q0"} + + qty_dict = { + "amp": np.zeros(16), + "det": np.zeros(16), + "phase": np.zeros(16), + } + if local_only: + expected = { + "Local": {"ground-rydberg": {"q0": qty_dict}}, + "Global": dict(), + } + else: + expected = {"Global": {"ground-rydberg": qty_dict}, "Local": dict()} + + assert_nested_dict_equality( + samples.to_nested_dict(all_local=local_only), expected + ) + + def test_one_pulse_sampling(): """Test the sample function on a one-pulse sequence.""" reg = pulser.Register.square(1, prefix="q") diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 3959376a3..e8d442650 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -328,11 +328,19 @@ def test_empty_sequences(reg): QutipEmulator(sampler.sample(seq), seq.register, seq.device) seq = Sequence(reg, MockDevice) - seq.declare_channel("test", "rydberg_local", "target") + seq.declare_channel("test", "raman_local", "target") seq.declare_channel("test2", "rydberg_global") with pytest.raises(ValueError, match="No instructions given"): Simulation(seq) + seq.delay(100, "test") + emu = QutipEmulator.from_sequence(seq, config=SimConfig(noise="SPAM")) + assert not emu.samples["Global"] + for basis in emu.samples["Local"]: + for q in emu.samples["Local"][basis]: + for qty_values in emu.samples["Local"][basis][q].values(): + np.testing.assert_equal(qty_values, 0) + def test_get_hamiltonian(): simple_reg = Register.from_coordinates([[10, 0], [0, 0]], prefix="atom")