Skip to content

Commit 9c845e5

Browse files
authored
Merge pull request #1325 from qiboteam/emulator-custom-sampling-rate
2 parents 2148944 + 1524f46 commit 9c845e5

File tree

6 files changed

+54
-22
lines changed

6 files changed

+54
-22
lines changed

src/qibolab/_core/instruments/emulator/emulator.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,27 @@
3939
class EmulatorController(Controller):
4040
"""Emulator controller."""
4141

42+
sampling_rate_: float = 1
43+
"""Sampling rate used during simulation."""
4244
engine: SimulationEngine = QutipEngine()
4345
"""SimulationEngine. Default is QutipEngine."""
4446
bounds: str = "emulator/bounds"
47+
"""Bounds for emulator."""
48+
49+
@property
50+
def sampling_rate(self) -> float:
51+
return self.sampling_rate_
52+
53+
@sampling_rate.setter
54+
def sampling_rate(self, value: float):
55+
self.sampling_rate_ = value
4556

4657
def connect(self):
4758
"""Dummy connect method."""
4859

4960
def disconnect(self):
5061
"""Dummy disconnect method."""
5162

52-
@property
53-
def sampling_rate(self):
54-
"""Sampling rate of emulator."""
55-
return 1
56-
5763
def play(
5864
self,
5965
configs: dict[str, Config],
@@ -127,7 +133,7 @@ def _play_sequence(
127133
the various measurements included in the sequence.
128134
"""
129135
sequence_ = update_sequence(sequence, updates)
130-
tlist_ = tlist(sequence_, self.sampling_rate)
136+
tlist_ = tlist(sequence_, self.sampling_rate, per_sample=2)
131137
configs_ = update_configs(configs, updates)
132138
config = cast(HamiltonianConfig, configs_["hamiltonian"])
133139
hamiltonian = config.hamiltonian(config=configs_, engine=self.engine)
@@ -151,8 +157,13 @@ def _pulse_hamiltonian(
151157
"""Construct Hamiltonian time dependent term for qutip simulation."""
152158

153159
channels = [
154-
[operator, channel_time(waveforms)]
155-
for operator, waveforms in hamiltonians(sequence, configs, self.engine)
160+
[
161+
operator,
162+
channel_time(waveforms, sampling_rate=self.sampling_rate),
163+
]
164+
for operator, waveforms in hamiltonians(
165+
sequence, configs, self.engine, self.sampling_rate
166+
)
156167
]
157168
return OperatorEvolution(channels) if len(channels) > 0 else None
158169

@@ -206,21 +217,25 @@ def hamiltonian(
206217
hamiltonian: HamiltonianConfig,
207218
hilbert_space_index: int,
208219
engine: SimulationEngine,
220+
sampling_rate: float,
209221
) -> tuple[Operator, list[Modulated]]:
210222
n = hamiltonian.transmon_levels
211223
op = engine.expand(
212224
config.operator(n=n, engine=engine), hamiltonian.dims, hilbert_space_index
213225
)
214226
waveforms = (
215-
waveform(pulse, config, hamiltonian.qubits[hilbert_space_index])
227+
waveform(pulse, config, hamiltonian.qubits[hilbert_space_index], sampling_rate)
216228
for pulse in pulses
217229
if isinstance(pulse, (Pulse, Delay, VirtualZ))
218230
)
219231
return (op, [w for w in waveforms if w is not None])
220232

221233

222234
def hamiltonians(
223-
sequence: PulseSequence, configs: dict[str, Config], engine: SimulationEngine
235+
sequence: PulseSequence,
236+
configs: dict[str, Config],
237+
engine: SimulationEngine,
238+
sampling_rate: float,
224239
) -> Iterable[tuple[Operator, list[Modulated]]]:
225240
hconfig = cast(HamiltonianConfig, configs["hamiltonian"])
226241
return (
@@ -230,14 +245,18 @@ def hamiltonians(
230245
hconfig,
231246
index(ch, hconfig),
232247
engine,
248+
sampling_rate,
233249
)
234250
for ch in sequence.channels
235251
# TODO: drop the following, and treat acquisitions just as empty channels
236252
if not isinstance(configs[ch], AcquisitionConfig)
237253
)
238254

239255

240-
def channel_time(waveforms: Iterable[Modulated]) -> Callable[[float], float]:
256+
def channel_time(
257+
waveforms: Iterable[Modulated],
258+
sampling_rate: int,
259+
) -> Callable[[float], float]:
241260
"""Wrap time function for specific channel.
242261
243262
Used to avoid late binding issues.
@@ -247,13 +266,12 @@ def time(t: float) -> float:
247266
cumulative_time = 0
248267
cumulative_phase = 0
249268
for pulse in waveforms:
250-
pulse_duration = pulse.duration # TODO: pass sampling rate
251269
pulse_phase = pulse.phase
252-
if cumulative_time <= t < cumulative_time + pulse_duration:
270+
if cumulative_time <= t < cumulative_time + pulse.duration:
253271
relative_time = t - cumulative_time
254-
index = int(relative_time) # TODO: pass sampling rate
272+
index = int(np.floor(relative_time * sampling_rate))
255273
return pulse(t, index, cumulative_phase)
256-
cumulative_time += pulse_duration
274+
cumulative_time += pulse.duration
257275
cumulative_phase += pulse_phase
258276
return 0
259277

src/qibolab/_core/instruments/emulator/hamiltonians.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,19 +361,23 @@ def waveform(
361361
pulse: PulseLike,
362362
config: Config,
363363
qubit: Qubit,
364+
sampling_rate: float,
364365
) -> Optional[ControlLine]:
365366
"""Convert pulse to hamiltonian."""
366367
if not isinstance(config, (DriveEmulatorConfig, FluxEmulatorConfig)):
367368
return None
368369

369370
if isinstance(pulse, Pulse):
370371
if isinstance(config, DriveEmulatorConfig):
371-
return ModulatedDrive(pulse=pulse, config=config)
372+
return ModulatedDrive(
373+
pulse=pulse, config=config, sampling_rate=sampling_rate
374+
)
372375
if isinstance(config, FluxEmulatorConfig):
373376
return FluxPulse(
374377
pulse=pulse,
375378
config=config,
376379
qubit=qubit,
380+
sampling_rate=sampling_rate,
377381
)
378382
if isinstance(pulse, Delay):
379383
return ModulatedDelay(duration=pulse.duration)

src/qibolab/_core/instruments/emulator/results.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def acquisitions(sequence: PulseSequence) -> dict[PulseId, float]:
7575
if isinstance(ev, (Acquisition, Readout)):
7676
acq[ev.id] = time
7777
time += ev.duration
78+
7879
return acq
7980

8081

@@ -121,7 +122,6 @@ def results(
121122
hamiltonian.nqubits,
122123
hamiltonian.transmon_levels,
123124
)
124-
125125
assert options.nshots is not None
126126
sampled = shots(np.moveaxis(probabilities, -2, 0), options.nshots)
127127
# move measurements dimension to the front, getting ready for extraction

tests/instruments/emulator/platforms/fixed-frequency-qutrits/platform.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ def create() -> Hardware:
2828
}
2929
# register the instruments
3030
instruments = {
31-
"emulator": EmulatorController(address="0.0.0.0", channels=channels),
31+
"emulator": EmulatorController(
32+
address="0.0.0.0", channels=channels, sampling_rate_=2
33+
),
3234
}
3335

3436
return Hardware(

tests/instruments/emulator/platforms/qubit/platform.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ def create() -> Platform:
2727

2828
# register the instruments
2929
instruments = {
30-
"dummy": EmulatorController(address="0.0.0.0", channels=channels),
30+
"dummy": EmulatorController(
31+
address="0.0.0.0", channels=channels, sampling_rate_=1
32+
),
3133
}
3234

3335
return Platform.load(

tests/instruments/emulator/test_hamiltonians.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ def test_dummy_waveform():
2020
acq_config = AcquisitionConfig(delay=0, smearing=0)
2121
dc_config = DcConfig(offset=0)
2222
qubit = Qubit()
23-
assert waveform(pulse=_PulseLike(), config=acq_config, qubit=qubit) is None
24-
assert waveform(pulse=_PulseLike(), config=dc_config, qubit=qubit) is None
23+
assert (
24+
waveform(pulse=_PulseLike(), config=acq_config, qubit=qubit, sampling_rate=1)
25+
is None
26+
)
27+
assert (
28+
waveform(pulse=_PulseLike(), config=dc_config, qubit=qubit, sampling_rate=1)
29+
is None
30+
)
2531

2632

2733
@pytest.mark.parametrize(
@@ -36,7 +42,7 @@ def test_dummy_waveform():
3642
def test_iq_waveform(pulse, level):
3743
iq_config = DriveEmulatorConfig(frequency=5e9)
3844
qubit = Qubit(frequency=5e9)
39-
modulated = waveform(pulse=pulse, config=iq_config, qubit=qubit)
45+
modulated = waveform(pulse=pulse, config=iq_config, qubit=qubit, sampling_rate=1)
4046
if isinstance(pulse, Pulse):
4147
assert isinstance(modulated, ModulatedDrive)
4248
assert pytest.approx(modulated.omega) == 2 * np.pi * 5

0 commit comments

Comments
 (0)