Skip to content

Commit

Permalink
Merge pull request #848 from qiboteam/flux-pulse-hotfix
Browse files Browse the repository at this point in the history
Hotfix: Exclude flux pulses from subsection splitting logic if they overlap (in time) with readout
  • Loading branch information
hay-k authored Mar 26, 2024
2 parents 031f178 + 2b79dd0 commit c109c51
Showing 1 changed file with 46 additions and 11 deletions.
57 changes: 46 additions & 11 deletions src/qibolab/instruments/zhinst/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from qibolab.couplers import Coupler
from qibolab.instruments.abstract import Controller
from qibolab.instruments.port import Port
from qibolab.pulses import PulseSequence, PulseType
from qibolab.pulses import FluxPulse, PulseSequence, PulseType
from qibolab.qubits import Qubit
from qibolab.sweeper import Parameter, Sweeper
from qibolab.unrolling import Bounds
Expand Down Expand Up @@ -114,6 +114,8 @@ def __init__(self, name, device_setup, time_of_flight=0.0, smearing=0.0):
"Zurich pulse sequence"
self.sub_sequences: list[SubSequence] = []
"Sub sequences between each measurement"
self.unsplit_channels: set[str] = set()
"Names of channels that were not split into sub-sequences"

self.processed_sweeps: Optional[ProcessedSweeps] = None
self.nt_sweeps: list[Sweeper] = []
Expand Down Expand Up @@ -307,8 +309,14 @@ def frequency_from_pulses(qubits, sequence):
if pulse.type is PulseType.DRIVE:
qubit.drive_frequency = pulse.frequency

def create_sub_sequences(self, qubits: list[Qubit]) -> list[SubSequence]:
"""Create subsequences based on locations of measurements."""
def create_sub_sequences(
self, qubits: list[Qubit]
) -> tuple[list[SubSequence], set[str]]:
"""Create subsequences based on locations of measurements.
Returns list of subsequences and a set of channel names that
were not split
"""
measure_channels = {measure_channel_name(qb) for qb in qubits}
other_channels = set(self.sequence.keys()) - measure_channels

Expand All @@ -317,17 +325,36 @@ def create_sub_sequences(self, qubits: list[Qubit]) -> list[SubSequence]:
for i, pulse in enumerate(self.sequence[ch]):
measurement_groups[i].append((ch, pulse))

measurement_starts = {}
measurement_start_end = {}
for i, group in measurement_groups.items():
starts = np.array([meas.pulse.start for _, meas in group])
measurement_starts[i] = max(starts)

# split all non-measurement channels according to the locations of the measurements
ends = np.array([meas.pulse.finish for _, meas in group])
measurement_start_end[i] = (
max(starts),
max(ends),
) # max is intended for float arithmetic errors only

# FIXME: this is a hotfix specifically made for any flux experiments in flux pulse mode, where the flux
# pulses extend through the entire duration of the experiment. This should be removed once the sub-sequence
# splitting logic is removed from the driver.
channels_overlapping_measurement = set()
if len(measurement_groups) == 1:
for ch in other_channels:
for pulse in self.sequence[ch]:
if not isinstance(pulse.pulse, FluxPulse):
break
start, end = measurement_start_end[0]
if pulse.pulse.start < end and pulse.pulse.finish > start:
channels_overlapping_measurement.add(ch)
break

# split non-measurement channels according to the locations of the measurements
sub_sequences = defaultdict(lambda: defaultdict(list))
for ch in other_channels:
for ch in other_channels - channels_overlapping_measurement:
measurement_index = 0
for pulse in self.sequence[ch]:
if pulse.pulse.finish > measurement_starts[measurement_index]:
start, _ = measurement_start_end[measurement_index]
if pulse.pulse.finish > start:
measurement_index += 1
sub_sequences[measurement_index][ch].append(pulse)
if len(sub_sequences) > len(measurement_groups):
Expand All @@ -336,7 +363,7 @@ def create_sub_sequences(self, qubits: list[Qubit]) -> list[SubSequence]:
return [
SubSequence(measurement_groups[i], sub_sequences[i])
for i in range(len(measurement_groups))
]
], channels_overlapping_measurement

def experiment_flow(
self,
Expand All @@ -356,7 +383,9 @@ def experiment_flow(
sequence (PulseSequence): sequence of pulses to be played in the experiment.
"""
self.sequence = self.sequence_zh(sequence, qubits)
self.sub_sequences = self.create_sub_sequences(list(qubits.values()))
self.sub_sequences, self.unsplit_channels = self.create_sub_sequences(
list(qubits.values())
)
self.calibration_step(qubits, couplers, options)
self.create_exp(qubits, options)

Expand Down Expand Up @@ -532,6 +561,12 @@ def get_channel_node_path(self, channel_name: str) -> str:

def select_exp(self, exp, qubits, exp_options):
"""Build Zurich Experiment selecting the relevant sections."""
# channels that were not split are just applied in parallel to the rest of the experiment
for ch in self.unsplit_channels:
with exp.section(uid="unsplit_channels"):
for pulse in self.sequence[ch]:
self.play_sweep(exp, ch, pulse)

weights = {}
previous_section = None
for i, seq in enumerate(self.sub_sequences):
Expand Down

0 comments on commit c109c51

Please sign in to comment.