From 9786545de79fcbdc40fc1d3341468eaec6e4d1ee Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Wed, 15 May 2024 23:41:32 +0400 Subject: [PATCH 01/23] qblox support for couplers bias --- .../instruments/qblox/cluster_qcm_bb.py | 42 +++++++++++++++---- .../instruments/qblox/cluster_qcm_rf.py | 1 + .../instruments/qblox/cluster_qrm_rf.py | 1 + src/qibolab/instruments/qblox/controller.py | 16 +++++-- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index e95cf69ef9..8aca5e382a 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -201,7 +201,7 @@ def setup(self, **settings): """ pass - def _get_next_sequencer(self, port, frequency, qubits: dict): + def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {}): """Retrieves and configures the next avaliable sequencer. The parameters of the new sequencer are copied from those of the default sequencer, except for the @@ -209,17 +209,33 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): Args: port (str): frequency (): - qubit (): + qubits (): + couplers (): Raises: Exception = If attempting to set a parameter without a connection to the instrument. """ - # select the qubit with flux line, if present, connected to the specific port + # check if this port is responsible for the flux of any qubit or coupler qubit = None for _qubit in qubits.values(): - name = _qubit.flux.port.name - module = _qubit.flux.port.module - if _qubit.flux is not None and (name, module) == (port, self): - qubit = _qubit + if _qubit.flux.port is not None: + if ( + _qubit.flux.port.name == port + and _qubit.flux.port.module.name == self.name + ): + qubit = _qubit + else: + log.warning(f"Qubit {_qubit.name} has no flux line connected") + + coupler = None + for _coupler in couplers.values(): + if _coupler.flux.port is not None: + if ( + _coupler.flux.port.name == port + and _coupler.flux.port.module.name == self.name + ): + coupler = _coupler + else: + log.warning(f"Coupler {_coupler.name} has no flux line connected") # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) @@ -245,6 +261,13 @@ def _get_next_sequencer(self, port, frequency, qubits: dict): # TODO: Throw error in that event or implement for non_overlapping_same_frequency_pulses # Even better, set the frequency before each pulse is played (would work with hardware modulation only) + if qubit: + self._ports[port].offset = qubit.sweetspot + elif coupler: + self._ports[port].offset = coupler.sweetspot + else: + self._ports[port].offset = 0 + # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) sequencer.qubit = qubit.name if qubit else None @@ -267,6 +290,7 @@ def get_if(self, pulse): def process_pulse_sequence( self, qubits: dict, + couplers: dict, instrument_pulses: PulseSequence, navgs: int, nshots: int, @@ -349,6 +373,7 @@ def process_pulse_sequence( port=port, frequency=self.get_if(non_overlapping_pulses[0]), qubits=qubits, + couplers=couplers, ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) @@ -383,12 +408,13 @@ def process_pulse_sequence( port=port, frequency=self.get_if(non_overlapping_pulses[0]), qubits=qubits, + couplers=couplers, ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) else: sequencer = self._get_next_sequencer( - port=port, frequency=0, qubits=qubits + port=port, frequency=0, qubits=qubits, couplers=couplers ) # add the sequencer to the list of sequencers required by the port self._sequencers[port].append(sequencer) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index cd91832577..0d5aa64abc 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -289,6 +289,7 @@ def get_if(self, pulse): def process_pulse_sequence( self, qubits: dict, + couplers: dict, instrument_pulses: PulseSequence, navgs: int, nshots: int, diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index a70d26c428..7c93242faf 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -337,6 +337,7 @@ def get_if(self, pulse: Pulse): def process_pulse_sequence( self, qubits: dict, + couplers: dict, instrument_pulses: PulseSequence, navgs: int, nshots: int, diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index f125677583..b85719c066 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -120,6 +120,7 @@ def _set_module_channel_map(self, module: QrmRf, qubits: dict): def _execute_pulse_sequence( self, qubits: dict, + couplers: dict, sequence: PulseSequence, options: ExecutionParameters, sweepers: list() = [], # list(Sweeper) = [] @@ -178,6 +179,7 @@ def _execute_pulse_sequence( # ask each module to generate waveforms & program and upload them to the device module.process_pulse_sequence( qubits, + couplers, module_pulses[name], navgs, nshots, @@ -228,7 +230,7 @@ def _execute_pulse_sequence( return data def play(self, qubits, couplers, sequence, options): - return self._execute_pulse_sequence(qubits, sequence, options) + return self._execute_pulse_sequence(qubits, couplers, sequence, options) def sweep( self, @@ -292,6 +294,7 @@ def sweep( # execute the each sweeper recursively self._sweep_recursion( qubits, + couplers, sequence_copy, options, *tuple(sweepers_copy), @@ -308,6 +311,7 @@ def sweep( def _sweep_recursion( self, qubits, + couplers, sequence, options: ExecutionParameters, *sweepers, @@ -386,6 +390,7 @@ def _sweep_recursion( if len(sweepers) > 1: self._sweep_recursion( qubits, + couplers, sequence, options, *sweepers[1:], @@ -393,7 +398,10 @@ def _sweep_recursion( ) else: result = self._execute_pulse_sequence( - qubits=qubits, sequence=sequence, options=options + qubits=qubits, + couplers=couplers, + sequence=sequence, + options=options, ) for pulse in sequence.ro_pulses: if results[pulse.id]: @@ -440,6 +448,7 @@ def _sweep_recursion( ) self._sweep_recursion( qubits, + couplers, sequence, options, *((split_sweeper,) + sweepers[1:]), @@ -486,7 +495,7 @@ def _sweep_recursion( # qubits[pulse.qubit].drive.gain = 1 result = self._execute_pulse_sequence( - qubits, sequence, options, sweepers + qubits, couplers, sequence, options, sweepers ) self._add_to_results(sequence, results, result) else: @@ -509,6 +518,7 @@ def _sweep_recursion( res = self._execute_pulse_sequence( qubits, + couplers, sequence, replace(options, nshots=_nshots), sweepers, From db433330b5a4e17839bd96aad10dedf8a1ae3d3b Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Sat, 18 May 2024 11:22:54 +0400 Subject: [PATCH 02/23] coupler support for qblox drivers --- src/qibolab/instruments/qblox/cluster_qcm_bb.py | 10 +++++++++- src/qibolab/instruments/qblox/controller.py | 10 ++++++++-- src/qibolab/instruments/qblox/sequencer.py | 1 + src/qibolab/pulses.py | 10 ++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 8aca5e382a..5156e4d9df 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -271,6 +271,7 @@ def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {} # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) sequencer.qubit = qubit.name if qubit else None + sequencer.coupler = coupler.name if coupler else None return sequencer def get_if(self, pulse): @@ -519,7 +520,14 @@ def process_pulse_sequence( ) else: # qubit_sweeper_parameters - if sequencer.qubit in [qubit.name for qubit in sweeper.qubits]: + + if ( + sweeper.qubits + and sequencer.qubit in [q.name for q in sweeper.qubits] + ) or ( + sweeper.couplers + and sequencer.coupler in [c.name for c in sweeper.couplers] + ): # plays an active role if sweeper.parameter == Parameter.bias: reference_value = self._ports[port].offset diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index b85719c066..05604d821c 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -102,7 +102,7 @@ def _termination_handler(self, signum, frame): log.warning("QbloxController: all modules are disconnected.") exit(0) - def _set_module_channel_map(self, module: QrmRf, qubits: dict): + def _set_module_channel_map(self, module: QrmRf, qubits: dict, couplers: dict): """Retrieve all the channels connected to a specific Qblox module. This method updates the `channel_port_map` attribute of the @@ -115,6 +115,10 @@ def _set_module_channel_map(self, module: QrmRf, qubits: dict): for channel in qubit.channels: if channel.port and channel.port.module.name == module.name: module.channel_map[channel.name] = channel + for coupler in couplers.values(): + for channel in coupler.channels: + if channel.port and channel.port.module.name == module.name: + module.channel_map[channel.name] = channel return list(module.channel_map) def _execute_pulse_sequence( @@ -173,7 +177,7 @@ def _execute_pulse_sequence( data = {} for name, module in self.modules.items(): # from the pulse sequence, select those pulses to be synthesised by the module - module_channels = self._set_module_channel_map(module, qubits) + module_channels = self._set_module_channel_map(module, qubits, couplers) module_pulses[name] = sequence.get_channel_pulses(*module_channels) # ask each module to generate waveforms & program and upload them to the device @@ -271,6 +275,7 @@ def sweep( values=sweeper.values, pulses=ps, qubits=sweeper.qubits, + couplers=sweeper.couplers, type=sweeper.type, ) ) @@ -445,6 +450,7 @@ def _sweep_recursion( values=_values, pulses=sweeper.pulses, qubits=sweeper.qubits, + couplers=sweeper.couplers, ) self._sweep_recursion( qubits, diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 185375185d..c55a7df904 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -232,3 +232,4 @@ def __init__(self, number: int): self.weights: dict = {} self.program: Program = Program() self.qubit = None # self.qubit: int | str = None + self.coupler = None # self.coupler: int | str = None diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 8da469bb9e..001c963934 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -929,6 +929,15 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.channel, self.qubit, ) + elif type(self) == CouplerFluxPulse: + return CouplerFluxPulse( + self.start, + self.duration, + self.amplitude, + self._shape, + self.channel, + self.qubit, + ) else: # return eval(self.serial) return Pulse( @@ -1221,6 +1230,7 @@ class PulseConstructor(Enum): READOUT = ReadoutPulse DRIVE = DrivePulse FLUX = FluxPulse + COUPLERFLUX = CouplerFluxPulse class PulseSequence: From a751d725a63976053f62d5da689be93f0a6ea6c9 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Sat, 18 May 2024 11:22:54 +0400 Subject: [PATCH 03/23] coupler support for qblox drivers --- src/qibolab/instruments/qblox/cluster_qcm_bb.py | 10 ++++++---- src/qibolab/instruments/qblox/cluster_qcm_rf.py | 5 ++++- src/qibolab/instruments/qblox/cluster_qrm_rf.py | 10 ++++++++-- src/qibolab/instruments/qblox/sequencer.py | 2 +- src/qibolab/pulses.py | 9 +++++++++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 5156e4d9df..9e65262319 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -520,13 +520,12 @@ def process_pulse_sequence( ) else: # qubit_sweeper_parameters - if ( sweeper.qubits - and sequencer.qubit in [q.name for q in sweeper.qubits] + and sequencer.qubit in [_.name for _ in sweeper.qubits] ) or ( sweeper.couplers - and sequencer.coupler in [c.name for c in sweeper.couplers] + and sequencer.coupler in [_.name for _ in sweeper.couplers] ): # plays an active role if sweeper.parameter == Parameter.bias: @@ -650,7 +649,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index 0d5aa64abc..f587e609b0 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -611,7 +611,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 7c93242faf..4705821d51 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -741,7 +741,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register @@ -788,7 +791,10 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if pulses[n].type == PulseType.FLUX: + if ( + pulses[n].type == PulseType.FLUX + or pulses[n].type == PulseType.COUPLERFLUX + ): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index c55a7df904..922e33f9be 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -127,7 +127,7 @@ def bake_pulse_waveforms( # there may be other waveforms stored already, set first index as the next available first_idx = len(self.unique_waveforms) - if pulse.type == PulseType.FLUX: + if pulses[n].type == PulseType.FLUX or pulses[n].type == PulseType.COUPLERFLUX: # for flux pulses, store i waveforms idx_range = np.arange(first_idx, first_idx + len(values), 1) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 001c963934..3468d856dd 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -938,6 +938,15 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.channel, self.qubit, ) + elif type(self) == CouplerFluxPulse: + return CouplerFluxPulse( + self.start, + self.duration, + self.amplitude, + self._shape, + self.channel, + self.qubit, + ) else: # return eval(self.serial) return Pulse( From 40c53fc2a1cfc89eb3d353181c9fdc6b353aecf1 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Sat, 18 May 2024 19:47:56 +0400 Subject: [PATCH 04/23] coupler support for qblox drivers --- src/qibolab/instruments/qblox/sequencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 922e33f9be..304a6b716c 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -127,7 +127,7 @@ def bake_pulse_waveforms( # there may be other waveforms stored already, set first index as the next available first_idx = len(self.unique_waveforms) - if pulses[n].type == PulseType.FLUX or pulses[n].type == PulseType.COUPLERFLUX: + if pulse.type == PulseType.FLUX or pulse.type == PulseType.COUPLERFLUX: # for flux pulses, store i waveforms idx_range = np.arange(first_idx, first_idx + len(values), 1) From e6e7d2f1e81331bc9322cd25a12caac2502a3dff Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Mon, 20 May 2024 10:05:36 +0400 Subject: [PATCH 05/23] support for baking Custom pulses and enhancements in their evaluation from a string --- src/qibolab/pulses.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 3468d856dd..0598495b79 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -705,11 +705,13 @@ class Custom(PulseShape): """Arbitrary shape.""" def __init__(self, envelope_i, envelope_q=None): + from ast import literal_eval + self.name = "Custom" self.pulse: Pulse = None - self.envelope_i: np.ndarray = np.array(envelope_i) + self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(envelope_q) + self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) else: self.envelope_q = self.envelope_i @@ -717,11 +719,13 @@ def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - if self.pulse.duration != len(self.envelope_i): + if self.pulse.duration > len(self.envelope_i): raise ValueError("Length of envelope_i must be equal to pulse duration") num_samples = int(np.rint(self.pulse.duration * sampling_rate)) - waveform = Waveform(self.envelope_i * self.pulse.amplitude) + waveform = Waveform( + np.clip(self.envelope_i[:num_samples] * self.pulse.amplitude, -1, 1) + ) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError @@ -730,11 +734,13 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - if self.pulse.duration != len(self.envelope_q): + if self.pulse.duration > len(self.envelope_q): raise ValueError("Length of envelope_q must be equal to pulse duration") num_samples = int(np.rint(self.pulse.duration * sampling_rate)) - waveform = Waveform(self.envelope_q * self.pulse.amplitude) + waveform = Waveform( + np.clip(self.envelope_q[:num_samples] * self.pulse.amplitude, -1, 1) + ) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError From 1d97650e72f262737b453785d3e2dc57c98b6a18 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Mon, 20 May 2024 23:08:24 +0400 Subject: [PATCH 06/23] add possibility to define shape in create_qubit_flux_pulse --- src/qibolab/platform/platform.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 7477405caf..94f2b06440 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -385,13 +385,15 @@ def create_qubit_readout_pulse(self, qubit, start): qubit = self.get_qubit(qubit) return self.create_MZ_pulse(qubit, start) - def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1): + def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=None): qubit = self.get_qubit(qubit) + if shape is None: + shape = "Rectangular()" pulse = FluxPulse( start=start, duration=duration, amplitude=amplitude, - shape="Rectangular", + shape=shape, channel=self.qubits[qubit].flux.name, qubit=qubit, ) From f174b6f115b115c398ff5fd3080984b73f06e614 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Fri, 31 May 2024 06:12:06 +0400 Subject: [PATCH 07/23] support the creation of coupler flux pulses with a particular shape --- src/qibolab/platform/platform.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 94f2b06440..269ec67fa4 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -400,8 +400,10 @@ def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=Non pulse.duration = duration return pulse - def create_coupler_pulse(self, coupler, start, duration=None, amplitude=None): + def create_coupler_pulse(self, coupler, start, duration=None, amplitude=None, shape=None): coupler = self.get_coupler(coupler) + if shape is None: + shape = "Rectangular()" pulse = self.couplers[coupler].native_pulse.CP.pulse(start) if duration is not None: pulse.duration = duration From 9ac640084b1b5ff66e1e6d0e478f9c1d3a062db0 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Fri, 31 May 2024 06:12:47 +0400 Subject: [PATCH 08/23] fix custom pulse initialisation so that it works both with strings and np arrays --- src/qibolab/pulses.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 0598495b79..f70dec8bd9 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -709,11 +709,24 @@ def __init__(self, envelope_i, envelope_q=None): self.name = "Custom" self.pulse: Pulse = None - self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) - if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) - else: - self.envelope_q = self.envelope_i + if isinstance(envelope_i, str): + self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) + if envelope_q is not None: + self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) + else: + self.envelope_q = self.envelope_i + elif isinstance(envelope_i, list): + self.envelope_i: np.ndarray = np.array(envelope_i) + if envelope_q is not None: + self.envelope_q: np.ndarray = np.array(envelope_q) + else: + self.envelope_q = self.envelope_i + elif isinstance(envelope_i, np.ndarray): + self.envelope_i: np.ndarray = envelope_i + if envelope_q is not None: + self.envelope_q: np.ndarray = envelope_q + else: + self.envelope_q = self.envelope_i def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" From 93559ad5e1ace00a0c787e49580daddc40359e0c Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Mon, 3 Jun 2024 09:27:23 +0400 Subject: [PATCH 09/23] expose i & q offsets properties in qblox port, for mixer calibration --- .../instruments/qblox/cluster_qcm_rf.py | 4 ++ .../instruments/qblox/cluster_qrm_rf.py | 4 ++ src/qibolab/instruments/qblox/port.py | 41 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index f587e609b0..507ac78693 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -197,6 +197,10 @@ def connect(self, cluster: Cluster = None): self._ports[port].lo_frequency = self.settings[port][ "lo_frequency" ] + if "mixer_calibration" in self.settings[port]: + self._ports[port].mixer_calibration = self.settings[port][ + "mixer_calibration" + ] self._ports[port].attenuation = self.settings[port]["attenuation"] self._ports[port].hardware_mod_en = True self._ports[port].nco_freq = 0 diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 4705821d51..98c0f8fe26 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -213,6 +213,10 @@ def connect(self, cluster: Cluster = None): self._ports["o1"].lo_frequency = self.settings["o1"][ "lo_frequency" ] + if "mixer_calibration" in self.settings["o1"]: + self._ports["o1"].mixer_calibration = self.settings["o1"][ + "mixer_calibration" + ] self._ports["o1"].hardware_mod_en = True self._ports["o1"].nco_freq = 0 self._ports["o1"].nco_phase_offs = 0 diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index c83cf391e3..b087b0d345 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -19,6 +19,8 @@ class QbloxOutputPort_Settings: nco_phase_offs: float = 0 lo_enabled: bool = True lo_frequency: int = 2_000_000_000 + i_offset: float = 0 + q_offset: float = 0 @dataclass @@ -221,6 +223,45 @@ def lo_frequency(self, value): elif self.module.device.is_qcm_type: self.module.device.set(f"out{self.port_number}_lo_freq", value=value) + @property + def mixer_calibration(self): + """Parameters for calibrating mixer output. + + i and q offsets are supported. + """ + if self.module.device: + self._settings.i_offset = self.module.device.get( + f"out{self.port_number}_offset_path0" + ) + self._settings.q_offset = self.module.device.get( + f"out{self.port_number}_offset_path1" + ) + return [self._settings.i_offset, self._settings.q_offset] + + @mixer_calibration.setter + def mixer_calibration(self, value): + if not isinstance(value, list) or len(value) != 2: + raise_error( + ValueError, + f"Invalid mixer calibration parameters {value}. A list [i_offset, q_offset] is required.", + ) + self._settings.i_offset, self._settings.q_offset = value + + if self.module.device: + self.module._set_device_parameter( + self.module.device, + f"out{self.port_number}_offset_path0", + value=self._settings.i_offset, + ) + self.module._set_device_parameter( + self.module.device, + f"out{self.port_number}_offset_path1", + value=self._settings.q_offset, + ) + else: + pass + # TODO: This case regards a connection error of the module + class QbloxInputPort: def __init__(self, module, port_number: int, port_name: str = None): From 84c61ccf229de1b127f9c21725bc106ec119f838 Mon Sep 17 00:00:00 2001 From: Alvaro Orgaz Date: Tue, 16 Jul 2024 11:03:11 +0400 Subject: [PATCH 10/23] cz_sweep & standard_rb --- src/qibolab/instruments/qblox/sequencer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index 304a6b716c..d061d38ebc 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -121,6 +121,7 @@ def bake_pulse_waveforms( NotEnoughMemory: If the memory needed to store the waveforms in more than the memory avalible. """ # In order to generate waveforms for each duration value, the pulse will need to be modified. + values = np.round(values).astype(int) # To avoid any conflicts, make a copy of the pulse first. pulse_copy = pulse.copy() From a18a6f1fa5b53b169f14302bbf30fdd92e19d6ea Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:14:57 +0400 Subject: [PATCH 11/23] cleanup the cherry-picked commits --- .../instruments/qblox/cluster_qcm_bb.py | 41 ++++++++++--------- .../instruments/qblox/cluster_qcm_rf.py | 5 +-- .../instruments/qblox/cluster_qrm_rf.py | 12 +++--- src/qibolab/instruments/qblox/port.py | 19 +++------ src/qibolab/instruments/qblox/sequencer.py | 10 +++-- src/qibolab/platform/platform.py | 36 ++++++++++++---- src/qibolab/pulses.py | 20 ++++----- 7 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/qibolab/instruments/qblox/cluster_qcm_bb.py b/src/qibolab/instruments/qblox/cluster_qcm_bb.py index 9e65262319..4693777022 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_bb.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_bb.py @@ -201,16 +201,18 @@ def setup(self, **settings): """ pass - def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {}): - """Retrieves and configures the next avaliable sequencer. + def _get_next_sequencer( + self, port: str, frequency: float, qubits: dict, couplers: dict + ): + """Retrieves and configures the next available sequencer. The parameters of the new sequencer are copied from those of the default sequencer, except for the intermediate frequency and classification parameters. Args: - port (str): - frequency (): - qubits (): - couplers (): + port: name of the output port + frequency: NCO frequency + qubits: qubits associated with this sequencer + couplers: couplers associated with this sequencer Raises: Exception = If attempting to set a parameter without a connection to the instrument. """ @@ -237,6 +239,17 @@ def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {} else: log.warning(f"Coupler {_coupler.name} has no flux line connected") + if qubit and coupler: + raise ValueError( + f"Port {port} of device {self.name} is configured for more than one line (flux lines of qubit {qubit.name} and coupler {coupler.name}" + ) + if qubit: + self._ports[port].offset = qubit.sweetspot + elif coupler: + self._ports[port].offset = coupler.sweetspot + else: + self._ports[port].offset = 0 + # select a new sequencer and configure it as required next_sequencer_number = self._free_sequencers_numbers.pop(0) if next_sequencer_number != self.DEFAULT_SEQUENCERS[port]: @@ -261,13 +274,6 @@ def _get_next_sequencer(self, port, frequency, qubits: dict, couplers: dict = {} # TODO: Throw error in that event or implement for non_overlapping_same_frequency_pulses # Even better, set the frequency before each pulse is played (would work with hardware modulation only) - if qubit: - self._ports[port].offset = qubit.sweetspot - elif coupler: - self._ports[port].offset = coupler.sweetspot - else: - self._ports[port].offset = 0 - # create sequencer wrapper sequencer = Sequencer(next_sequencer_number) sequencer.qubit = qubit.name if qubit else None @@ -522,10 +528,10 @@ def process_pulse_sequence( else: # qubit_sweeper_parameters if ( sweeper.qubits - and sequencer.qubit in [_.name for _ in sweeper.qubits] + and sequencer.qubit in [q.name for q in sweeper.qubits] ) or ( sweeper.couplers - and sequencer.coupler in [_.name for _ in sweeper.couplers] + and sequencer.coupler in [c.name for c in sweeper.couplers] ): # plays an active role if sweeper.parameter == Parameter.bias: @@ -649,10 +655,7 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX - ): + if pulses[n].type in (PulseType.FLUX, PulseType.COUPLERFLUX): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qcm_rf.py b/src/qibolab/instruments/qblox/cluster_qcm_rf.py index 507ac78693..a8f2c62bd1 100644 --- a/src/qibolab/instruments/qblox/cluster_qcm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qcm_rf.py @@ -615,10 +615,7 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX - ): + if pulses[n].type in (PulseType.FLUX, PulseType.COUPLERFLUX): RQ = pulses[n].sweeper.register else: RQ = pulses[n].sweeper.aux_register diff --git a/src/qibolab/instruments/qblox/cluster_qrm_rf.py b/src/qibolab/instruments/qblox/cluster_qrm_rf.py index 98c0f8fe26..7d930a4e92 100644 --- a/src/qibolab/instruments/qblox/cluster_qrm_rf.py +++ b/src/qibolab/instruments/qblox/cluster_qrm_rf.py @@ -745,9 +745,9 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX + if pulses[n].type in ( + PulseType.FLUX, + PulseType.COUPLERFLUX, ): RQ = pulses[n].sweeper.register else: @@ -795,9 +795,9 @@ def process_pulse_sequence( and pulses[n].sweeper.type == QbloxSweeperType.duration ): RI = pulses[n].sweeper.register - if ( - pulses[n].type == PulseType.FLUX - or pulses[n].type == PulseType.COUPLERFLUX + if pulses[n].type in ( + PulseType.FLUX, + PulseType.COUPLERFLUX, ): RQ = pulses[n].sweeper.register else: diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index b087b0d345..9efb4e160f 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -248,19 +248,12 @@ def mixer_calibration(self, value): self._settings.i_offset, self._settings.q_offset = value if self.module.device: - self.module._set_device_parameter( - self.module.device, - f"out{self.port_number}_offset_path0", - value=self._settings.i_offset, - ) - self.module._set_device_parameter( - self.module.device, - f"out{self.port_number}_offset_path1", - value=self._settings.q_offset, - ) - else: - pass - # TODO: This case regards a connection error of the module + self.module.device.set( + f"out{self.port_number}_offset_path0", self._settings.i_offset + ), + self.module.device.set( + f"out{self.port_number}_offset_path1", self._settings.q_offset + ), class QbloxInputPort: diff --git a/src/qibolab/instruments/qblox/sequencer.py b/src/qibolab/instruments/qblox/sequencer.py index d061d38ebc..60a03fd33c 100644 --- a/src/qibolab/instruments/qblox/sequencer.py +++ b/src/qibolab/instruments/qblox/sequencer.py @@ -1,3 +1,5 @@ +from typing import Optional, Union + import numpy as np from qblox_instruments.qcodes_drivers.sequencer import Sequencer as QbloxSequencer @@ -120,15 +122,15 @@ def bake_pulse_waveforms( Raises: NotEnoughMemory: If the memory needed to store the waveforms in more than the memory avalible. """ - # In order to generate waveforms for each duration value, the pulse will need to be modified. values = np.round(values).astype(int) + # In order to generate waveforms for each duration value, the pulse will need to be modified. # To avoid any conflicts, make a copy of the pulse first. pulse_copy = pulse.copy() # there may be other waveforms stored already, set first index as the next available first_idx = len(self.unique_waveforms) - if pulse.type == PulseType.FLUX or pulse.type == PulseType.COUPLERFLUX: + if pulse.type in (PulseType.FLUX, PulseType.COUPLERFLUX): # for flux pulses, store i waveforms idx_range = np.arange(first_idx, first_idx + len(values), 1) @@ -232,5 +234,5 @@ def __init__(self, number: int): self.acquisitions: dict = {} self.weights: dict = {} self.program: Program = Program() - self.qubit = None # self.qubit: int | str = None - self.coupler = None # self.coupler: int | str = None + self.qubit: Optional[Union[int, str]] = None + self.coupler: Optional[Union[int, str]] = None diff --git a/src/qibolab/platform/platform.py b/src/qibolab/platform/platform.py index 269ec67fa4..5178978cbc 100644 --- a/src/qibolab/platform/platform.py +++ b/src/qibolab/platform/platform.py @@ -10,7 +10,14 @@ from qibolab.couplers import Coupler from qibolab.execution_parameters import ExecutionParameters from qibolab.instruments.abstract import Controller, Instrument, InstrumentId -from qibolab.pulses import Drag, FluxPulse, PulseSequence, ReadoutPulse +from qibolab.pulses import ( + CouplerFluxPulse, + Drag, + FluxPulse, + PulseSequence, + ReadoutPulse, + Rectangular, +) from qibolab.qubits import Qubit, QubitId, QubitPair, QubitPairId from qibolab.sweeper import Sweeper from qibolab.unrolling import batch @@ -388,7 +395,7 @@ def create_qubit_readout_pulse(self, qubit, start): def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=None): qubit = self.get_qubit(qubit) if shape is None: - shape = "Rectangular()" + shape = Rectangular() pulse = FluxPulse( start=start, duration=duration, @@ -400,15 +407,30 @@ def create_qubit_flux_pulse(self, qubit, start, duration, amplitude=1, shape=Non pulse.duration = duration return pulse - def create_coupler_pulse(self, coupler, start, duration=None, amplitude=None, shape=None): + def create_coupler_pulse( + self, coupler, start, duration=None, amplitude=None, shape=None + ): coupler = self.get_coupler(coupler) + native_pulse = self.couplers[coupler].native_pulse.CP.pulse(start) + + if duration is None: + duration = native_pulse.duration + if amplitude is None: + amplitude = native_pulse.amplitude + if shape is None: - shape = "Rectangular()" - pulse = self.couplers[coupler].native_pulse.CP.pulse(start) - if duration is not None: + pulse = native_pulse pulse.duration = duration - if amplitude is not None: pulse.amplitude = amplitude + else: + pulse = CouplerFluxPulse( + start=start, + duration=duration, + amplitude=amplitude, + shape=shape, + channel=self.qubits[coupler].flux.name, + qubit=coupler, + ) return pulse # TODO Remove RX90_drag_pulse and RX_drag_pulse, replace them with create_qubit_drive_pulse diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index f70dec8bd9..6e8213e33c 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -732,13 +732,13 @@ def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" if self.pulse: - if self.pulse.duration > len(self.envelope_i): - raise ValueError("Length of envelope_i must be equal to pulse duration") num_samples = int(np.rint(self.pulse.duration * sampling_rate)) + if len(self.envelope_i) != num_samples: + raise ValueError( + "Length of envelope_i must be equal to pulse duration in samples" + ) - waveform = Waveform( - np.clip(self.envelope_i[:num_samples] * self.pulse.amplitude, -1, 1) - ) + waveform = Waveform(self.envelope_i * self.pulse.amplitude) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError @@ -747,13 +747,13 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the q component of the pulse.""" if self.pulse: - if self.pulse.duration > len(self.envelope_q): - raise ValueError("Length of envelope_q must be equal to pulse duration") num_samples = int(np.rint(self.pulse.duration * sampling_rate)) + if len(self.envelope_q) != num_samples: + raise ValueError( + "Length of envelope_q must be equal to pulse duration in samples" + ) - waveform = Waveform( - np.clip(self.envelope_q[:num_samples] * self.pulse.amplitude, -1, 1) - ) + waveform = Waveform(self.envelope_q * self.pulse.amplitude) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError From 48e8d2683490b7b8097cb91ad49ba99f12b9c39b Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:58:34 +0400 Subject: [PATCH 12/23] revert a5013cbb9c0f9145b1889943b245e2b928e4989c --- src/qibolab/pulses.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 6e8213e33c..27f5b573f8 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -705,28 +705,14 @@ class Custom(PulseShape): """Arbitrary shape.""" def __init__(self, envelope_i, envelope_q=None): - from ast import literal_eval self.name = "Custom" self.pulse: Pulse = None - if isinstance(envelope_i, str): - self.envelope_i: np.ndarray = np.array(literal_eval(envelope_i)) - if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(literal_eval(envelope_q)) - else: - self.envelope_q = self.envelope_i - elif isinstance(envelope_i, list): - self.envelope_i: np.ndarray = np.array(envelope_i) - if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(envelope_q) - else: - self.envelope_q = self.envelope_i - elif isinstance(envelope_i, np.ndarray): - self.envelope_i: np.ndarray = envelope_i - if envelope_q is not None: - self.envelope_q: np.ndarray = envelope_q - else: - self.envelope_q = self.envelope_i + self.envelope_i: np.ndarray = np.array(envelope_i) + if envelope_q is not None: + self.envelope_q: np.ndarray = np.array(envelope_q) + else: + self.envelope_q = self.envelope_i def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: """The envelope waveform of the i component of the pulse.""" From 39f8fc1cfe36d363fe10c21300205717e43327ce Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:01:56 +0400 Subject: [PATCH 13/23] remove duplicate block --- src/qibolab/pulses.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 27f5b573f8..b28fc1869e 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -943,15 +943,6 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.channel, self.qubit, ) - elif type(self) == CouplerFluxPulse: - return CouplerFluxPulse( - self.start, - self.duration, - self.amplitude, - self._shape, - self.channel, - self.qubit, - ) else: # return eval(self.serial) return Pulse( From 169f39751a3179c7f6805e6480c19d53c72d6e41 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:45:29 +0400 Subject: [PATCH 14/23] fix reference to shape --- src/qibolab/pulses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index b28fc1869e..17c2917148 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -939,7 +939,7 @@ def copy(self): # -> Pulse|ReadoutPulse|DrivePulse|FluxPulse: self.start, self.duration, self.amplitude, - self._shape, + self.shape, self.channel, self.qubit, ) From 6110d392b7a1444fcec769f8641300d71268faef Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:24:58 +0400 Subject: [PATCH 15/23] fix pulse shape deserialization not working for Custom, IIR, and SNZ --- src/qibolab/pulses.py | 59 +++++++++++++++++++++++++++---------- tests/test_pulses.py | 67 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 17c2917148..62b58614f2 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -19,6 +19,22 @@ """ +def _np_array_from_string_or_array_like(x) -> np.ndarray: + """Convert input into numpy array. + + The input can be in one of these forms: + 1. string in form '[1, 2, 3, ...]' + 2. list, e.g. [1, 2, 3, ...] + 3. numpy array. This will be identity conversion. + """ + if isinstance(x, str): + return np.fromstring(x[1:-1], sep=",") + elif isinstance(x, (list, np.ndarray)): + return np.array(x) + else: + raise ValueError(f"Data in unrecognized format: {x}") + + class PulseType(Enum): """An enumeration to distinguish different types of pulses. @@ -215,12 +231,19 @@ def eval(value: str) -> "PulseShape": To be replaced by proper serialization. """ - shape_name = re.findall(r"(\w+)", value)[0] - if shape_name not in globals(): - raise ValueError(f"shape {value} not found") - shape_parameters = re.findall(r"[-\w+\d\.\d]+", value)[1:] - # TODO: create multiple tests to prove regex working correctly - return globals()[shape_name](*shape_parameters) + match = re.fullmatch(r"(\w+)\((.*)\)", value) + shape_name, params = None, None + if match is not None: + shape_name, params = match.groups() + if match is None or shape_name not in globals(): + raise ValueError(f"shape {value} not recognized") + + single_item_pattern = r"[^,\s\[\]\(\)]+" + csv_items_pattern = rf"(?:{single_item_pattern}(?:,\s*)?)*" + param_pattern = ( + rf"\[{csv_items_pattern}\]|\w+\({csv_items_pattern}\)|{single_item_pattern}" + ) + return globals()[shape_name](*re.findall(param_pattern, params)) class Rectangular(PulseShape): @@ -509,12 +532,14 @@ class IIR(PulseShape): # p = [b0 = 1−k +k ·α, b1 = −(1−k)·(1−α),a0 = 1 and a1 = −(1−α)] # p = [b0, b1, a0, a1] - def __init__(self, b, a, target: PulseShape): + def __init__(self, b, a, target): self.name = "IIR" - self.target: PulseShape = target + self.target: PulseShape = ( + PulseShape.eval(target) if isinstance(target, str) else target + ) self._pulse: Pulse = None - self.a: np.ndarray = np.array(a) - self.b: np.ndarray = np.array(b) + self.a: np.ndarray = _np_array_from_string_or_array_like(a) + self.b: np.ndarray = _np_array_from_string_or_array_like(b) # Check len(a) = len(b) = 2 def __eq__(self, item) -> bool: @@ -596,8 +621,10 @@ class SNZ(PulseShape): def __init__(self, t_idling, b_amplitude=None): self.name = "SNZ" self.pulse: Pulse = None - self.t_idling: float = t_idling - self.b_amplitude = b_amplitude + self.t_idling: float = float(t_idling) + self.b_amplitude = ( + float(b_amplitude) if b_amplitude is not None else b_amplitude + ) def __eq__(self, item) -> bool: """Overloads == operator.""" @@ -708,9 +735,11 @@ def __init__(self, envelope_i, envelope_q=None): self.name = "Custom" self.pulse: Pulse = None - self.envelope_i: np.ndarray = np.array(envelope_i) + self.envelope_i: np.ndarray = _np_array_from_string_or_array_like(envelope_i) if envelope_q is not None: - self.envelope_q: np.ndarray = np.array(envelope_q) + self.envelope_q: np.ndarray = _np_array_from_string_or_array_like( + envelope_q + ) else: self.envelope_q = self.envelope_i @@ -745,7 +774,7 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: raise ShapeInitError def __repr__(self): - return f"{self.name}({self.envelope_i[:3]}, ..., {self.envelope_q[:3]}, ...)" + return f"{self.name}({self.envelope_i[:3]}, {self.envelope_q[:3]})" @dataclass diff --git a/tests/test_pulses.py b/tests/test_pulses.py index 9939b8faec..62f7a64b63 100644 --- a/tests/test_pulses.py +++ b/tests/test_pulses.py @@ -12,6 +12,7 @@ Custom, Drag, DrivePulse, + Exponential, FluxPulse, Gaussian, GaussianSquare, @@ -275,8 +276,70 @@ def test_pulses_pulseshape_sampling_rate(shape): def test_pulseshape_eval(): shape = PulseShape.eval("Rectangular()") assert isinstance(shape, Rectangular) - with pytest.raises(ValueError): - shape = PulseShape.eval("Ciao()") + + shape = PulseShape.eval("Exponential(1, 2)") + assert isinstance(shape, Exponential) + assert shape.tau == 1 + assert shape.upsilon == 2 + + shape = PulseShape.eval("Exponential(4, 5, 6)") + assert isinstance(shape, Exponential) + assert shape.tau == 4 + assert shape.upsilon == 5 + assert shape.g == 6 + + shape = PulseShape.eval("Gaussian(3.1)") + assert isinstance(shape, Gaussian) + assert shape.rel_sigma == 3.1 + + shape = PulseShape.eval("GaussianSquare(5, 78)") + assert isinstance(shape, GaussianSquare) + assert shape.rel_sigma == 5 + assert shape.width == 78 + + shape = PulseShape.eval("Drag(4, 0.1)") + assert isinstance(shape, Drag) + assert shape.rel_sigma == 4 + assert shape.beta == 0.1 + + shape = PulseShape.eval("IIR([1, 2, 3], [5], Drag(3, 0.2))") + assert isinstance(shape, IIR) + assert np.array_equal(shape.b, np.array([1, 2, 3])) + assert np.array_equal(shape.a, np.array([5])) + assert isinstance(shape.target, Drag) + assert shape.target.rel_sigma == 3 + assert shape.target.beta == 0.2 + + shape = PulseShape.eval("SNZ(10, 20)") + assert isinstance(shape, SNZ) + assert shape.t_idling == 10 + assert shape.b_amplitude == 20 + + shape = PulseShape.eval("eCap(3.14)") + assert isinstance(shape, eCap) + assert shape.alpha == 3.14 + + shape = PulseShape.eval("Custom([1, 2, 3], [4, 5, 6])") + assert isinstance(shape, Custom) + assert np.array_equal(shape.envelope_i, np.array([1, 2, 3])) + assert np.array_equal(shape.envelope_q, np.array([4, 5, 6])) + + with pytest.raises(ValueError, match="shape .* not recognized"): + _ = PulseShape.eval("Ciao()") + + +@pytest.mark.parametrize( + "value_str", + ["-0.1", "+0.1", "1.", "-3.", "+1.", "-0.1e2", "1e-2", "+1e3", "-3e-1", "-.4"], +) +def test_pulse_shape_eval_numeric_varieties(value_str): + shape = PulseShape.eval(f"Drag(1, {value_str})") + assert isinstance(shape, Drag) + assert shape.beta == float(value_str) + + shape = PulseShape.eval(f"Custom([0.1, {value_str}])") + assert isinstance(shape, Custom) + assert np.array_equal(shape.envelope_i, np.array([0.1, float(value_str)])) @pytest.mark.parametrize("rel_sigma,beta", [(5, 1), (5, -1), (3, -0.03), (4, 0.02)]) From f34b3fb537dc295ecc2ea6436e4383bd3c3db60b Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:42:21 +0400 Subject: [PATCH 16/23] update qblox test setup --- tests/dummy_qrc/qblox/parameters.json | 88 ++++++++++++++++------ tests/dummy_qrc/qblox/platform.py | 22 +++++- tests/test_instruments_qblox_controller.py | 19 +++++ 3 files changed, 106 insertions(+), 23 deletions(-) diff --git a/tests/dummy_qrc/qblox/parameters.json b/tests/dummy_qrc/qblox/parameters.json index 7a5099b5fe..e8797d7266 100644 --- a/tests/dummy_qrc/qblox/parameters.json +++ b/tests/dummy_qrc/qblox/parameters.json @@ -11,24 +11,30 @@ 3, 4 ], - "topology": [ - [ + "couplers": [ + 0, + 1, + 3, + 4 + ], + "topology": { + "0": [ 0, 2 ], - [ + "1": [ 1, 2 ], - [ + "3": [ 2, 3 ], - [ + "4": [ 2, 4 ] - ], + }, "instruments": { "qblox_controller": { "bounds": { @@ -76,7 +82,8 @@ "o1": { "attenuation": 36, "lo_frequency": 7300000000, - "gain": 0.6 + "gain": 0.6, + "mixer_calibration": [-3.2, 1.4] }, "i1": { "acquisition_hold_off": 500, @@ -87,7 +94,8 @@ "o1": { "attenuation": 36, "lo_frequency": 7850000000, - "gain": 0.6 + "gain": 0.6, + "mixer_calibration": [0.3, -3.8] }, "i1": { "acquisition_hold_off": 500, @@ -243,6 +251,48 @@ } } }, + "coupler": { + "0": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 0, + "relative_start": 0 + } + }, + "1": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 1, + "relative_start": 0 + } + }, + "3": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 3, + "relative_start": 0 + } + }, + "4": { + "CP": { + "type": "coupler", + "duration": 0, + "amplitude": 0, + "shape": "Rectangular()", + "coupler": 4, + "relative_start": 0 + } + } + }, "two_qubit": { "2-3": { "CZ": [ @@ -401,22 +451,18 @@ "threshold": 0.002323 } }, - "two_qubit":{ - "0-2": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "coupler": { + "0": { + "sweetspot": 0.1 }, - "1-2": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "1": { + "sweetspot": 0.2 }, - "2-3": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "3": { + "sweetspot": -0.3 }, - "2-4": { - "gate_fidelity": [0.0, 0.0], - "cz_fidelity": [0.0, 0.0] + "4": { + "sweetspot": 0.0 } } } diff --git a/tests/dummy_qrc/qblox/platform.py b/tests/dummy_qrc/qblox/platform.py index a60a600d8f..6129c716d9 100644 --- a/tests/dummy_qrc/qblox/platform.py +++ b/tests/dummy_qrc/qblox/platform.py @@ -30,6 +30,7 @@ def create(): modules = { "qcm_bb0": QcmBb("qcm_bb0", f"{ADDRESS}:2"), "qcm_bb1": QcmBb("qcm_bb1", f"{ADDRESS}:4"), + "qcm_bb2": QcmBb("qcm_bb2", f"{ADDRESS}:3"), "qcm_rf0": QcmRf("qcm_rf0", f"{ADDRESS}:6"), "qcm_rf1": QcmRf("qcm_rf1", f"{ADDRESS}:8"), "qcm_rf2": QcmRf("qcm_rf2", f"{ADDRESS}:10"), @@ -61,12 +62,17 @@ def create(): channels |= Channel(name="L3-12", port=modules["qcm_rf1"].ports("o1")) channels |= Channel(name="L3-13", port=modules["qcm_rf1"].ports("o2")) channels |= Channel(name="L3-14", port=modules["qcm_rf2"].ports("o1")) - # Flux + # Qubit flux channels |= Channel(name="L4-5", port=modules["qcm_bb0"].ports("o1")) channels |= Channel(name="L4-1", port=modules["qcm_bb0"].ports("o2")) channels |= Channel(name="L4-2", port=modules["qcm_bb0"].ports("o3")) channels |= Channel(name="L4-3", port=modules["qcm_bb0"].ports("o4")) channels |= Channel(name="L4-4", port=modules["qcm_bb1"].ports("o1")) + # Coupler flux + channels |= Channel(name="L4-12", port=modules["qcm_bb1"].ports("o2")) + channels |= Channel(name="L4-13", port=modules["qcm_bb1"].ports("o3")) + channels |= Channel(name="L4-14", port=modules["qcm_bb1"].ports("o4")) + channels |= Channel(name="L4-5", port=modules["qcm_bb2"].ports("o1")) # TWPA channels |= Channel(name="L3-28", port=None) channels["L3-28"].local_oscillator = twpa_pump @@ -98,8 +104,20 @@ def create(): for q in range(5): qubits[q].flux.max_bias = 2.5 + for i, coupler in enumerate(couplers): + couplers[coupler].flux = ( + channels[f"L4-{11 + i}"] if i > 0 else channels[f"L4-5"] + ) + couplers[coupler].flux.max_bias = 2.5 + settings = load_settings(runcard) return Platform( - str(FOLDER), qubits, pairs, instruments, settings, resonator_type="2D" + str(FOLDER), + qubits, + pairs, + instruments, + settings, + resonator_type="2D", + couplers=couplers, ) diff --git a/tests/test_instruments_qblox_controller.py b/tests/test_instruments_qblox_controller.py index fde3682f07..c5c5fba003 100644 --- a/tests/test_instruments_qblox_controller.py +++ b/tests/test_instruments_qblox_controller.py @@ -61,6 +61,25 @@ def test_sweep_too_many_sweep_points(platform, controller): controller.sweep({0: qubit}, {}, PulseSequence(pulse), params, sweep) +def test_sweep_coupler(platform, controller): + """Test that coupler related sweep is accepted.""" + ro_pulse = platform.create_MZ_pulse(qubit=0, start=0) + sequence = PulseSequence(ro_pulse) + + sweeper = Sweeper(Parameter.bias, np.random.rand(4), couplers=[0]) + params = ExecutionParameters( + nshots=10, relaxation_time=1000, averaging_mode=AveragingMode.CYCLIC + ) + mock_data = np.array([1, 2, 3, 4]) + controller._execute_pulse_sequence = Mock( + return_value={ro_pulse.serial: IntegratedResults(mock_data)} + ) + res = controller.sweep( + platform.qubits, platform.couplers, sequence, params, sweeper + ) + assert np.array_equal(res[ro_pulse.serial].voltage, mock_data) + + @pytest.mark.qpu def connect(connected_controller: QbloxController): connected_controller.connect() From 4074c9d2f84573e849f6e591b18490f4faa39d03 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:07:25 +0400 Subject: [PATCH 17/23] workaround to make experiments with start sweeper on non RO lines move RO according to the sweep --- src/qibolab/instruments/qblox/controller.py | 41 ++++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index 05604d821c..d4623036f0 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -11,7 +11,7 @@ from qibolab.instruments.qblox.cluster_qcm_rf import QcmRf from qibolab.instruments.qblox.cluster_qrm_rf import QrmRf from qibolab.instruments.qblox.sequencer import SAMPLING_RATE -from qibolab.pulses import PulseSequence, PulseType +from qibolab.pulses import Custom, PulseSequence, PulseType, ReadoutPulse from qibolab.result import SampleResults from qibolab.sweeper import Parameter, Sweeper, SweeperType from qibolab.unrolling import Bounds @@ -280,6 +280,43 @@ def sweep( ) ) + serial_map = {p.serial: p.serial for p in sequence_copy.ro_pulses} + for sweeper in sweepers_copy: + if sweeper.parameter is Parameter.start and not any( + pulse.type is PulseType.READOUT for pulse in sweeper.pulses + ): + for pulse in sequence_copy: + if pulse.type is PulseType.READOUT: + idx = sequence_copy.index(pulse) + padded_pulse = ReadoutPulse( + start=0, + duration=pulse.start + pulse.duration, + amplitude=pulse.amplitude, + frequency=pulse.frequency, + relative_phase=pulse.relative_phase, + shape=Custom( + envelope_i=np.concatenate( + ( + np.zeros(pulse.start), + pulse.envelope_waveform_i().data + / pulse.amplitude, + ) + ), + envelope_q=np.concatenate( + ( + np.zeros(pulse.start), + pulse.envelope_waveform_q().data + / pulse.amplitude, + ) + ), + ), + channel=pulse.channel, + qubit=pulse.qubit, + ) + serial_map[padded_pulse.serial] = pulse.serial + sequence_copy[idx] = padded_pulse + sweeper.pulses.append(padded_pulse) + # reverse sweepers exept for res punchout att contains_attenuation_frequency = any( sweepers_copy[i].parameter == Parameter.attenuation @@ -309,7 +346,7 @@ def sweep( # return the results using the original serials serial_results = {} for pulse in sequence_copy.ro_pulses: - serial_results[map_id_serial[pulse.id]] = id_results[pulse.id] + serial_results[serial_map[map_id_serial[pulse.id]]] = id_results[pulse.id] serial_results[pulse.qubit] = id_results[pulse.id] return serial_results From 108c3c2ffaa208ee1ec959bff7b799e2430b35a3 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:30:28 +0400 Subject: [PATCH 18/23] trim waveforms of pulses with custom shapes for shorter pulse durations --- src/qibolab/pulses.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 62b58614f2..63fd1b314a 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -748,12 +748,12 @@ def envelope_waveform_i(self, sampling_rate=SAMPLING_RATE) -> Waveform: if self.pulse: num_samples = int(np.rint(self.pulse.duration * sampling_rate)) - if len(self.envelope_i) != num_samples: + if len(self.envelope_i) < num_samples: raise ValueError( - "Length of envelope_i must be equal to pulse duration in samples" + "Length of envelope_i must not be shorter than pulse duration in samples" ) - waveform = Waveform(self.envelope_i * self.pulse.amplitude) + waveform = Waveform(self.envelope_i[:num_samples] * self.pulse.amplitude) waveform.serial = f"Envelope_Waveform_I(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError @@ -763,12 +763,12 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform: if self.pulse: num_samples = int(np.rint(self.pulse.duration * sampling_rate)) - if len(self.envelope_q) != num_samples: + if len(self.envelope_q) < num_samples: raise ValueError( - "Length of envelope_q must be equal to pulse duration in samples" + "Length of envelope_q must not be shorter than pulse duration in samples" ) - waveform = Waveform(self.envelope_q * self.pulse.amplitude) + waveform = Waveform(self.envelope_q[:num_samples] * self.pulse.amplitude) waveform.serial = f"Envelope_Waveform_Q(num_samples = {num_samples}, amplitude = {format(self.pulse.amplitude, '.6f').rstrip('0').rstrip('.')}, shape = {repr(self)})" return waveform raise ShapeInitError From 9242c2afe31277b65d7c0693b2a3296261ecff38 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:06:36 +0400 Subject: [PATCH 19/23] workaround to make sure RO pulse does not overlap with duration-swept pulse --- src/qibolab/instruments/qblox/controller.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/qibolab/instruments/qblox/controller.py b/src/qibolab/instruments/qblox/controller.py index d4623036f0..a82ca6635f 100644 --- a/src/qibolab/instruments/qblox/controller.py +++ b/src/qibolab/instruments/qblox/controller.py @@ -282,11 +282,19 @@ def sweep( serial_map = {p.serial: p.serial for p in sequence_copy.ro_pulses} for sweeper in sweepers_copy: - if sweeper.parameter is Parameter.start and not any( + if sweeper.parameter in (Parameter.duration, Parameter.start) and not any( pulse.type is PulseType.READOUT for pulse in sweeper.pulses ): - for pulse in sequence_copy: - if pulse.type is PulseType.READOUT: + for pulse in sequence_copy.ro_pulses: + current_serial = pulse.serial + if sweeper.parameter is Parameter.duration: + sweep_values = sweeper.get_values(sweeper.pulses[0].duration) + if ( + max_finish := sweeper.pulses[0].start + np.max(sweep_values) + ) > pulse.start: + pulse.start = max_finish + serial_map[pulse.serial] = current_serial + if sweeper.parameter is Parameter.start: idx = sequence_copy.index(pulse) padded_pulse = ReadoutPulse( start=0, @@ -313,7 +321,7 @@ def sweep( channel=pulse.channel, qubit=pulse.qubit, ) - serial_map[padded_pulse.serial] = pulse.serial + serial_map[padded_pulse.serial] = current_serial sequence_copy[idx] = padded_pulse sweeper.pulses.append(padded_pulse) From 730e291af4c5c54a137417970e5f930dc920b232 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:04:01 +0400 Subject: [PATCH 20/23] change order of arctan2 arguments --- src/qibolab/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibolab/result.py b/src/qibolab/result.py index 837d7fba10..19ecb0c7fd 100644 --- a/src/qibolab/result.py +++ b/src/qibolab/result.py @@ -38,7 +38,7 @@ def magnitude(self): @cached_property def phase(self): """Signal phase in radians.""" - return np.unwrap(np.arctan2(self.voltage_i, self.voltage_q)) + return np.unwrap(np.arctan2(self.voltage_q, self.voltage_i)) @cached_property def phase_std(self): @@ -95,7 +95,7 @@ def phase_std(self): @cached_property def phase(self): """Phase not unwrapped because it is a single value.""" - return np.arctan2(self.voltage_i, self.voltage_q) + return np.arctan2(self.voltage_q, self.voltage_i) class RawWaveformResults(IntegratedResults): From 1e56825290f21d721e39c12a435135ced2ed92c1 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:48:48 +0400 Subject: [PATCH 21/23] take sequencer number from defaults --- src/qibolab/instruments/qblox/port.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibolab/instruments/qblox/port.py b/src/qibolab/instruments/qblox/port.py index 9efb4e160f..8827709d99 100644 --- a/src/qibolab/instruments/qblox/port.py +++ b/src/qibolab/instruments/qblox/port.py @@ -38,7 +38,7 @@ class QbloxOutputPort(Port): def __init__(self, module, port_number: int, port_name: str = None): self.name = port_name self.module = module - self.sequencer_number: int = port_number + self.sequencer_number: int = module.DEFAULT_SEQUENCERS[port_name] self.port_number: int = port_number self._settings = QbloxOutputPort_Settings() From 035b325929ebc1f0d1d503e1fdb9cd34f28f1196 Mon Sep 17 00:00:00 2001 From: jevillegasdatTII Date: Wed, 9 Oct 2024 11:45:11 +0400 Subject: [PATCH 22/23] Fix for frequencies not overlapping. With @aorgazf. --- src/qibolab/pulses.py | 55 +++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/qibolab/pulses.py b/src/qibolab/pulses.py index 63fd1b314a..45eae38c28 100644 --- a/src/qibolab/pulses.py +++ b/src/qibolab/pulses.py @@ -1587,30 +1587,39 @@ def separate_overlapping_pulses(self): # -> dict((int,int): PulseSequence): """Separates a sequence of overlapping pulses into a list of non- overlapping sequences.""" - # This routine separates the pulses of a sequence into non-overlapping sets - # but it does not check if the frequencies of the pulses within a set have the same frequency - - separated_pulses = [] - for new_pulse in self.pulses: - stored = False - for ps in separated_pulses: - overlaps = False - for existing_pulse in ps: - if ( - new_pulse.start < existing_pulse.finish - and new_pulse.finish > existing_pulse.start - ): - overlaps = True + # This routine separates the pulses of a sequence into sets of different frequecy, non-overlapping + # pulses + + freqs = set() + for pulse in self.pulses: + freqs |= {pulse.frequency} + PS_freq = {} + separated_pulses = {} + for freq in freqs: + PS_freq[freq] = PulseSequence() + separated_pulses[freq] = [] + for pulse in self.pulses: + if pulse.frequency == freq: + PS_freq[freq].add(pulse) + + for new_pulse in PS_freq[freq]: + stored = False + for ps in separated_pulses[freq]: + overlaps = False + for existing_pulse in ps: + if ( + new_pulse.start < existing_pulse.finish + and new_pulse.finish > existing_pulse.start + ): + overlaps = True + break + if not overlaps: + ps.add(new_pulse) + stored = True break - if not overlaps: - ps.add(new_pulse) - stored = True - break - if not stored: - separated_pulses.append(PulseSequence(new_pulse)) - return separated_pulses - - # TODO: Implement separate_different_frequency_pulses() + if not stored: + separated_pulses[freq].append(PulseSequence(new_pulse)) + return [ps for freq in freqs for ps in separated_pulses[freq]] @property def pulses_overlap(self) -> bool: From f85447aa9ff231432842e4e26fa1ab0069085e14 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:27:26 +0400 Subject: [PATCH 23/23] fix result tests --- tests/test_result.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_result.py b/tests/test_result.py index e861f56796..7ba339bbae 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -71,7 +71,7 @@ def test_integrated_result_properties(result): np.sqrt(results.voltage_i**2 + results.voltage_q**2), results.magnitude ) np.testing.assert_equal( - np.unwrap(np.arctan2(results.voltage_i, results.voltage_q)), results.phase + np.unwrap(np.arctan2(results.voltage_q, results.voltage_i)), results.phase ) @@ -119,7 +119,7 @@ def test_serialize(average, result): "MSR[V]": np.sqrt(avg.voltage_i**2 + avg.voltage_q**2), "i[V]": avg.voltage_i, "q[V]": avg.voltage_q, - "phase[rad]": np.unwrap(np.arctan2(avg.voltage_i, avg.voltage_q)), + "phase[rad]": np.unwrap(np.arctan2(avg.voltage_q, avg.voltage_i)), } assert avg.serialize.keys() == target_dict.keys() for key in output: @@ -155,7 +155,7 @@ def test_serialize_averaged_iq_results(result): "MSR[V]": np.sqrt(results.voltage_i**2 + results.voltage_q**2), "i[V]": results.voltage_i, "q[V]": results.voltage_q, - "phase[rad]": np.unwrap(np.arctan2(results.voltage_i, results.voltage_q)), + "phase[rad]": np.unwrap(np.arctan2(results.voltage_q, results.voltage_i)), } assert output.keys() == target_dict.keys() for key in output: