diff --git a/.gitignore b/.gitignore index 49f02d865c63ee31a39470459192592247ba3a01..b2e235683b41025454d2a89f184267fd22e86d92 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,4 @@ dmypy.json .pyre/ .DS_Store +/pulse_lib/examples/q1 diff --git a/pulse_lib/base_pulse.py b/pulse_lib/base_pulse.py index 2f80ca23d730511f3ce15fac813054357502040e..450e887dcd9dfe93e9dd6f271886f436a058ed21 100644 --- a/pulse_lib/base_pulse.py +++ b/pulse_lib/base_pulse.py @@ -286,6 +286,18 @@ class pulselib: self.IQ_channels, self.qubit_channels, self.digitizers, self.digitizer_channels) + def _create_QbloxPulsar_uploader(self): + try: + from pulse_lib.qblox.pulsar_uploader import PulsarUploader + except ImportError: + logging.error('Import of QbloxPulsar uploader failed', exc_info=True) + raise + + self.uploader = PulsarUploader(self.awg_devices, self.awg_channels, + self.marker_channels, + self.IQ_channels, self.qubit_channels, + self.digitizers, self.digitizer_channels) + def finish_init(self): if self._backend in ["Keysight", "M3202A"]: self._create_M3202A_uploader() @@ -296,6 +308,9 @@ class pulselib: elif self._backend == "Keysight_QS": self._create_KeysightQS_uploader() + elif self._backend == "Qblox": + self._create_QbloxPulsar_uploader() + elif self._backend in ["Demo", "None", None]: logging.info('No backend defined') TODO('define demo backend') diff --git a/pulse_lib/configuration/physical_channels.py b/pulse_lib/configuration/physical_channels.py index 16942ed8b1ead024414676579fe14e7996b51789..93ba60c1f3fc8dd421e7226a8adcc63b8301c9bc 100644 --- a/pulse_lib/configuration/physical_channels.py +++ b/pulse_lib/configuration/physical_channels.py @@ -21,12 +21,17 @@ class marker_channel: ''' Keysight: 0 = trigger out channel, 1...4 = analogue channel Tektronix: tuple = (channel,marker number), int = analogue channel + Qblox: 0...3 = marker out. ''' setup_ns: float hold_ns: float amplitude: float = 1000 invert: bool = False delay: float = 0 # ns + sequencer_name : str = None + ''' + Qblox only: name of qubit, awg or digitizer channel to use for sequencing + ''' # NOTES on digitizer configuration options for M3102A FPGA # * Input: I/Q demodulated (external demodulation) with pairing in FPGA diff --git a/pulse_lib/examples/configuration/init_keysight.py b/pulse_lib/examples/configuration/init_keysight.py new file mode 100644 index 0000000000000000000000000000000000000000..4cf0b0887669c7b8ef486758671245eb9eca0b38 --- /dev/null +++ b/pulse_lib/examples/configuration/init_keysight.py @@ -0,0 +1,22 @@ +import qcodes as qc + +from pulse_lib.tests.mock_m3202a import MockM3202A_fpga + +if not qc.Station.default: + station = qc.Station() +else: + station = qc.Station.default + +_use_dummy=True + +def add_awg(name, slot_nr): + try: + awg = station[name] + except: + awg = MockM3202A_fpga(name, 0, slot_nr) + station.add_component(awg) + return awg + +awg1 = add_awg('AWG1', 2) +awg2 = add_awg('AWG2', 3) + diff --git a/pulse_lib/examples/configuration/init_keysight_qs.py b/pulse_lib/examples/configuration/init_keysight_qs.py new file mode 100644 index 0000000000000000000000000000000000000000..f3a396670643590726188888908c627ccbe8f8c4 --- /dev/null +++ b/pulse_lib/examples/configuration/init_keysight_qs.py @@ -0,0 +1,30 @@ +import qcodes as qc + +from pulse_lib.tests.mock_m3202a_qs import MockM3202A_QS +from pulse_lib.tests.mock_m3102a_qs import MockM3102A_QS + +if not qc.Station.default: + station = qc.Station() +else: + station = qc.Station.default + +_use_dummy=True + +def add_awg(name, slot_nr): + try: + awg = station[name] + except: + awg = MockM3202A_QS(name, 0, slot_nr) + station.add_component(awg) + return awg + +def add_dig(name, slot_nr): + try: + dig = station[name] + except: + dig = MockM3102A_QS(name, 0, slot_nr) + station.add_component(dig) + return dig + +awg1 = add_awg('AWG1', 2) +dig1 = add_dig('Dig1', 5) diff --git a/pulse_lib/examples/configuration/init_pulsars.py b/pulse_lib/examples/configuration/init_pulsars.py new file mode 100644 index 0000000000000000000000000000000000000000..1ceece659bdb91c523c362c2d9b26341f5d30c09 --- /dev/null +++ b/pulse_lib/examples/configuration/init_pulsars.py @@ -0,0 +1,54 @@ +import qcodes as qc + +from pulsar_qcm.pulsar_qcm import pulsar_qcm, pulsar_qcm_dummy +from pulsar_qrm.pulsar_qrm import pulsar_qrm, pulsar_qrm_dummy + +try: + from q1simulator import Q1Simulator + _q1simulator_found = True +except: + print('package q1simulator not found') + _q1simulator_found = False + +if not qc.Station.default: + station = qc.Station() +else: + station = qc.Station.default + +_use_dummy = True +_use_simulator = True + +def add_pulsar_module(module_type, name, ip_addr): + try: + pulsar = station[name] + except: + if _use_simulator: + if not _q1simulator_found: + raise Exception('q1simulator not found') + pulsar = Q1Simulator(name) + elif module_type == 'qrm': + if _use_dummy: + print(f'Starting QRM {name} dummy') + pulsar = pulsar_qrm_dummy(name) + else: + print(f'Connecting QRM {name} on {ip_addr}...') + pulsar = pulsar_qrm(name, ip_addr) + else: + if _use_dummy: + print(f'Starting QCM {name} dummy') + pulsar = pulsar_qcm_dummy(name) + else: + print(f'Connecting QCM {name} on {ip_addr}...') + pulsar = pulsar_qcm(name, ip_addr) + + pulsar.reset() + station.add_component(pulsar) + + return pulsar + +qcm0 = add_pulsar_module('qcm', 'qcm0', '192.168.0.2') +qrm1 = add_pulsar_module('qrm', 'qrm1', '192.168.0.3') + +qcm0.reference_source('internal') +qrm1.reference_source('external') + diff --git a/pulse_lib/examples/configuration/init_pulsars_3.py b/pulse_lib/examples/configuration/init_pulsars_3.py new file mode 100644 index 0000000000000000000000000000000000000000..adb1b31fc2c001e8e8697ad46bb13e858d57df3f --- /dev/null +++ b/pulse_lib/examples/configuration/init_pulsars_3.py @@ -0,0 +1,56 @@ +import qcodes as qc + +from pulsar_qcm.pulsar_qcm import pulsar_qcm, pulsar_qcm_dummy +from pulsar_qrm.pulsar_qrm import pulsar_qrm, pulsar_qrm_dummy + +try: + from q1simulator import Q1Simulator + _q1simulator_found = True +except: + print('package q1simulator not found') + _q1simulator_found = False + +if not qc.Station.default: + station = qc.Station() +else: + station = qc.Station.default + +_use_dummy = True +_use_simulator = True + +def add_pulsar_module(module_type, name, ip_addr): + try: + pulsar = station[name] + except: + if _use_simulator: + if not _q1simulator_found: + raise Exception('q1simulator not found') + pulsar = Q1Simulator(name) + elif module_type == 'qrm': + if _use_dummy: + print(f'Starting QRM {name} dummy') + pulsar = pulsar_qrm_dummy(name) + else: + print(f'Connecting QRM {name} on {ip_addr}...') + pulsar = pulsar_qrm(name, ip_addr) + else: + if _use_dummy: + print(f'Starting QCM {name} dummy') + pulsar = pulsar_qcm_dummy(name) + else: + print(f'Connecting QCM {name} on {ip_addr}...') + pulsar = pulsar_qcm(name, ip_addr) + + pulsar.reset() + station.add_component(pulsar) + + return pulsar + +qcm0 = add_pulsar_module('qcm', 'qcm0', '192.168.0.2') +qcm2 = add_pulsar_module('qcm', 'qcm2', '192.168.0.4') +qrm1 = add_pulsar_module('qrm', 'qrm1', '192.168.0.3') + +qcm0.reference_source('internal') +qcm2.reference_source('external') +qrm1.reference_source('external') + diff --git a/pulse_lib/examples/configuration/medium_iq.py b/pulse_lib/examples/configuration/medium_iq.py new file mode 100644 index 0000000000000000000000000000000000000000..2ef2fd8eb2679a04848af4b84dd146ced5bc0038 --- /dev/null +++ b/pulse_lib/examples/configuration/medium_iq.py @@ -0,0 +1,104 @@ +from pulse_lib.base_pulse import pulselib +from pulse_lib.virtual_channel_constructors import virtual_gates_constructor, IQ_channel_constructor +import numpy as np + +_backend = 'Qblox' +#_backend = 'Keysight' +#_backend = 'Keysight_QS' + +_ch_offset = 0 + +def init_hardware(): + global _ch_offset + + if _backend == 'Qblox': + _ch_offset = 0 + from .init_pulsars_3 import qcm0, qcm2, qrm1 + return [qcm0, qcm2], [qrm1] + if _backend == 'Keysight': + _ch_offset = 1 + from .init_keysight import awg1, awg2 + return [awg1,awg2], [] + if _backend == 'Keysight_QS': + _ch_offset = 1 + from .init_keysight_qs import awg1, dig1 + TODO() + return [awg1], [dig1] + + +def init_pulselib(awgs, digitizers, virtual_gates=False, bias_T_rc_time=None): + + pulse = pulselib(_backend) + + for awg in awgs: + pulse.add_awg(awg) + + for dig in digitizers: + pulse.add_digitizer(dig) + + awg1 = awgs[0].name + awg2 = awgs[1].name + # define channels + pulse.define_channel('P1', awg1, 0 + _ch_offset) + pulse.define_channel('P2', awg1, 1 + _ch_offset) + pulse.define_channel('I1', awg1, 2 + _ch_offset) + pulse.define_channel('Q1', awg1, 3 + _ch_offset) + + pulse.define_channel('P3', awg2, 0 + _ch_offset) + pulse.define_channel('P4', awg2, 1 + _ch_offset) + pulse.define_channel('I2', awg2, 2 + _ch_offset) + pulse.define_channel('Q2', awg2, 3 + _ch_offset) + + pulse.define_marker('M1', awg1, 0, setup_ns=40, hold_ns=20) + pulse.define_marker('M2', awg2, 2, setup_ns=40, hold_ns=20) + + dig_name = digitizers[0].name if len(digitizers) > 0 else 'Dig1' + pulse.define_digitizer_channel('SD1', dig_name, 1) + + # add limits on voltages for DC channel compensation (if no limit is specified, no compensation is performed). + pulse.add_channel_compensation_limit('P1', (-100, 100)) + pulse.add_channel_compensation_limit('P2', (-50, 50)) + pulse.add_channel_compensation_limit('P3', (-80, 80)) + + pulse.awg_channels['P1'].attenuation = 0.5 + + if bias_T_rc_time: + pulse.add_channel_bias_T_compensation('P1', bias_T_rc_time) + pulse.add_channel_bias_T_compensation('P2', bias_T_rc_time) + + if virtual_gates: + # set a virtual gate matrix + virtual_gate_set_1 = virtual_gates_constructor(pulse) + virtual_gate_set_1.add_real_gates('P1','P2', 'P3', 'P4') + virtual_gate_set_1.add_virtual_gates('vP1','vP2', 'vP3', 'vP4') + inv_matrix = 1.2*np.eye(4) - 0.05 + virtual_gate_set_1.add_virtual_gate_matrix(np.linalg.inv(inv_matrix)) + + # define IQ output pair + IQ_pair_1 = IQ_channel_constructor(pulse) + IQ_pair_1.add_IQ_chan("I1", "I") + IQ_pair_1.add_IQ_chan("Q1", "Q") + IQ_pair_1.add_marker("M1") + # frequency of the MW source + IQ_pair_1.set_LO(2.400e9) + + # add 2 qubits: q2 + IQ_pair_1.add_virtual_IQ_channel("q1", 2.435e9) + IQ_pair_1.add_virtual_IQ_channel("q2", 2.450e9) + + # define IQ output pair + IQ_pair_2 = IQ_channel_constructor(pulse) + IQ_pair_2.add_IQ_chan("I2", "I") + IQ_pair_2.add_IQ_chan("Q2", "Q") + IQ_pair_2.add_marker("M2") + # frequency of the MW source + IQ_pair_2.set_LO(2.800e9) + + # add qubits: + IQ_pair_2.add_virtual_IQ_channel("q3", 2.900e9) + IQ_pair_2.add_virtual_IQ_channel("q4", 2.700e9) + + pulse.finish_init() + + return pulse + diff --git a/pulse_lib/examples/configuration/small.py b/pulse_lib/examples/configuration/small.py index 802165c46296fc3f5cf401a5f76a77b65cad27d9..7a10b000db75a6449663ee9385d5c97a03fa07dc 100644 --- a/pulse_lib/examples/configuration/small.py +++ b/pulse_lib/examples/configuration/small.py @@ -1,32 +1,47 @@ -from pulse_lib.tests.mock_m3202a import MockM3202A_fpga from pulse_lib.base_pulse import pulselib from pulse_lib.virtual_channel_constructors import virtual_gates_constructor import numpy as np -import qcodes +_backend = 'Qblox' +#_backend = 'Keysight' +#_backend = 'Keysight_QS' -def init_hardware(): - # try to close all instruments of previous run - try: - qcodes.Instrument.close_all() - except: pass - - awg1 = MockM3202A_fpga("AWG1", 0, 2) - return [awg1] - +_ch_offset = 0 -def init_pulselib(awgs, virtual_gates=False, bias_T_rc_time=None): - - pulse = pulselib() +def init_hardware(): + global _ch_offset + + if _backend == 'Qblox': + _ch_offset = 0 + from .init_pulsars import qcm0, qrm1 + return [qcm0], [qrm1] + if _backend == 'Keysight': + _ch_offset = 1 + from .init_keysight import awg1 + return [awg1], [] + if _backend == 'Keysight_QS': + _ch_offset = 1 + from .init_keysight_qs import awg1 + return [awg1], [] + +pulse = None +def init_pulselib(awgs, digitizers, virtual_gates=False, bias_T_rc_time=None): + global pulse + + pulse = pulselib(_backend) for awg in awgs: - pulse.add_awgs(awg.name, awg) + pulse.add_awg(awg) + + for dig in digitizers: + pulse.add_digitizer(dig) + awg1 = awgs[0].name # define channels - pulse.define_channel('P1','AWG1', 1) - pulse.define_channel('P2','AWG1', 2) + pulse.define_channel('P1', awg1, 0 + _ch_offset) + pulse.define_channel('P2', awg1, 1 + _ch_offset) - # add limits on voltages for DC channel compenstation (if no limit is specified, no compensation is performed). + # add limits on voltages for DC channel compensation (if no limit is specified, no compensation is performed). pulse.add_channel_compensation_limit('P1', (-200, 500)) pulse.add_channel_compensation_limit('P2', (-200, 500)) diff --git a/pulse_lib/examples/configuration/small_iq.py b/pulse_lib/examples/configuration/small_iq.py index ebe8bc52c9d053b69352c83afed20cb2b5b4bd06..25f101d089a4d0f3d3206d1c5cbb73654510ec01 100644 --- a/pulse_lib/examples/configuration/small_iq.py +++ b/pulse_lib/examples/configuration/small_iq.py @@ -1,36 +1,52 @@ -from pulse_lib.tests.mock_m3202a import MockM3202A_fpga from pulse_lib.base_pulse import pulselib from pulse_lib.virtual_channel_constructors import virtual_gates_constructor, IQ_channel_constructor import numpy as np -import qcodes +#_backend = 'Qblox' +_backend = 'Keysight' +#_backend = 'Keysight_QS' + +_ch_offset = 0 def init_hardware(): - # try to close all instruments of previous run - try: - qcodes.Instrument.close_all() - except: pass + global _ch_offset - awg1 = MockM3202A_fpga("AWG1", 0, 2) - return [awg1] + if _backend == 'Qblox': + _ch_offset = 0 + from .init_pulsars import qcm0, qrm1 + return [qcm0], [qrm1] + if _backend == 'Keysight': + _ch_offset = 1 + from .init_keysight import awg1 + return [awg1], [] + if _backend == 'Keysight_QS': + _ch_offset = 1 + from .init_keysight_qs import awg1, dig1 + return [awg1], [dig1] -def init_pulselib(awgs, virtual_gates=False, bias_T_rc_time=None): +def init_pulselib(awgs, digitizers, virtual_gates=False, bias_T_rc_time=None, + lo_frequency=None): - pulse = pulselib() + pulse = pulselib(_backend) for awg in awgs: - pulse.add_awgs(awg.name, awg) + pulse.add_awg(awg) + + for dig in digitizers: + pulse.add_digitizer(dig) + awg1 = awgs[0].name # define channels - pulse.define_channel('P1','AWG1', 1) - pulse.define_channel('P2','AWG1', 2) - pulse.define_channel('I1','AWG1', 3) - pulse.define_channel('Q1','AWG1', 4) + pulse.define_channel('P1', awg1, 0 + _ch_offset) + pulse.define_channel('P2', awg1, 1 + _ch_offset) + pulse.define_channel('I1', awg1, 2 + _ch_offset) + pulse.define_channel('Q1', awg1, 3 + _ch_offset) - pulse.define_digitizer_channel('SD1', 'Digitizer', 1) + dig_name = digitizers[0].name if len(digitizers) > 0 else 'Dig1' + pulse.define_digitizer_channel('SD1', dig_name, 1) - # add limits on voltages for DC channel compenstation (if no limit is specified, no compensation is performed). + # add limits on voltages for DC channel compensation (if no limit is specified, no compensation is performed). pulse.add_channel_compensation_limit('P1', (-200, 500)) pulse.add_channel_compensation_limit('P2', (-200, 500)) @@ -51,10 +67,11 @@ def init_pulselib(awgs, virtual_gates=False, bias_T_rc_time=None): IQ_pair_1.add_IQ_chan("I1", "I") IQ_pair_1.add_IQ_chan("Q1", "Q") # frequency of the MW source - IQ_pair_1.set_LO(2.40e9) + lo_freq = lo_frequency if lo_frequency is not None else 2.400e9 + IQ_pair_1.set_LO(lo_freq) # add 1 qubit: q1 - IQ_pair_1.add_virtual_IQ_channel("q1") + IQ_pair_1.add_virtual_IQ_channel("q1", 2.415e9) pulse.finish_init() diff --git a/pulse_lib/examples/example_2Dscan.py b/pulse_lib/examples/example_2Dscan.py index e22509c21940d8f78c2ec285d4134e56080c10f0..7758598c5f54d75256de182e18687d84c3d7412f 100644 --- a/pulse_lib/examples/example_2Dscan.py +++ b/pulse_lib/examples/example_2Dscan.py @@ -46,17 +46,17 @@ def create_2D_scan(pulse_lib, gate1, sweep1_mv, gate2, sweep2_mv, n_steps, t_mea # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) my_seq = create_2D_scan( p, - 'P1', 100, - 'P2', 100, - 100, 50.0, + 'vP1', 100, + 'vP2', 100, + 200, 2.0, bias_T_corr=True ) diff --git a/pulse_lib/examples/example_baseband_pulses.py b/pulse_lib/examples/example_baseband_pulses.py index 6fc2bfee2df375299aad739fb9211a5da377572e..00218787a76c84d49f257844fd67dbc559dee61c 100644 --- a/pulse_lib/examples/example_baseband_pulses.py +++ b/pulse_lib/examples/example_baseband_pulses.py @@ -14,10 +14,10 @@ def tukey_pulse(duration, sample_rate, amplitude, alpha): # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs) +p = init_pulselib(awgs, digs) seg1 = p.mk_segment() seg2 = p.mk_segment() @@ -28,9 +28,9 @@ seg1.P1.add_ramp_ss(200, 300, 340, 120) seg1.P1.reset_time() # overlapping pulses are summed seg1.P1.add_block(0, 200, 120) -seg1.P1.add_ramp_ss(180, 190, 0, 50) -seg1.P1.add_ramp_ss(190, 200, 50, 0) -seg1.P1.add_sin(50, 150, 50, 100e6) +seg1.P1.add_ramp_ss(180, 192, 0, 50) +seg1.P1.add_ramp_ss(192, 200, 50, 0) +seg1.P1.add_sin(52, 152, 50, 100e6) # wait 100 ns after last pulse of segment seg1.P1.wait(100) @@ -39,7 +39,7 @@ seg2.P1 += -200 # seg2.P2.add_block(100, 180, 200) seg2.P2.add_ramp_ss(180, 200, 200, 100) -seg2.P2.add_ramp_ss(200, 250, 100, 0) +seg2.P2.add_ramp_ss(200, 240, 100, 0) seg2.P1.add_ramp_ss(100, 200, 0, -200) # reset time base for SEGMENT: aligns all segments seg2.reset_time() diff --git a/pulse_lib/examples/example_bias_T_compensation.py b/pulse_lib/examples/example_bias_T_compensation.py index d4dddd181db13fa737de85f9379d80e44d8d8d40..c625e12c39739231b73ea288b1115f0e1df5f106 100644 --- a/pulse_lib/examples/example_bias_T_compensation.py +++ b/pulse_lib/examples/example_bias_T_compensation.py @@ -7,14 +7,14 @@ from utils.plot import plot_awgs # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # RC time = 1 ms bias_T_rc_time = 0.001 compensate_bias_T = True # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True, +p = init_pulselib(awgs, digs, virtual_gates=True, bias_T_rc_time=bias_T_rc_time if compensate_bias_T else None) diff --git a/pulse_lib/examples/example_custom_pulses.py b/pulse_lib/examples/example_custom_pulses.py index d10b6d2ec9145d1c0f689ca6225c31883456793b..aa7f614d8ca79a06f887991de964b57148d940b6 100644 --- a/pulse_lib/examples/example_custom_pulses.py +++ b/pulse_lib/examples/example_custom_pulses.py @@ -51,34 +51,34 @@ def iswap_pulse(duration, sample_rate, amplitude, frequency, mw_amp): # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) seg = p.mk_segment() -seg.P1.wait(10) +seg.P1.wait(20) seg.P1.reset_time() seg.P1.add_custom_pulse(0, 60, 350.0, tukey_pulse, alpha=0.5) -seg.P1.add_sin(20, 40, 50.0, 2e8) -seg.P1.wait(10) +#seg.P1.add_sin(20, 40, 50.0, 2e8) +seg.P1.wait(20) seg.reset_time() # looping on arguments alpha_loop = lp.linspace(0.3, 0.5, n_steps = 2, name = 'alpha', axis = 0) amplitude_loop = lp.linspace(300, 500, n_steps = 3, name = 'amplitude', unit = 'mV', axis = 1) -seg.P2.wait(10) +seg.P2.wait(20) seg.P2.reset_time() seg.P2.add_custom_pulse(0, 60, amplitude_loop, tukey_pulse, alpha=alpha_loop) -seg.P2.add_sin(20, 40, 50.0, 2e8) -seg.P2.wait(10) +#seg.P2.add_sin(20, 40, 50.0, 2e8) +seg.P2.wait(20) seg.reset_time() # virtual gate: compensation is visible on P1 -seg.vP2.add_custom_pulse(10, 70, 150.0, iswap_pulse, frequency=2e8, mw_amp=2.5) -seg.vP2.wait(10) +seg.vP2.add_custom_pulse(20, 80, 150.0, iswap_pulse, frequency=2e8, mw_amp=2.5) +seg.vP2.wait(20) # create sequence seq = p.mk_sequence([seg]) diff --git a/pulse_lib/examples/example_dig_acquire.py b/pulse_lib/examples/example_dig_acquire.py index bdab0b3b61970714f0316becd09286d7ecb20294..4db2926c450f066229845f12d8669686fd420b5a 100644 --- a/pulse_lib/examples/example_dig_acquire.py +++ b/pulse_lib/examples/example_dig_acquire.py @@ -1,72 +1,18 @@ -import numpy as np -import time import logging from pprint import pprint import matplotlib.pyplot as pt -import qcodes import qcodes.logger as logger from qcodes.logger import start_all_logging -from pulse_lib.base_pulse import pulselib -from pulse_lib.tests.mock_m3202a import MockM3202A_fpga -from pulse_lib.tests.mock_m3202a_qs import MockM3202A_QS -from pulse_lib.tests.mock_m3102a_qs import MockM3102A_QS from pulse_lib.tests.hw_schedule_mock import HardwareScheduleMock -import pulse_lib.segments.utility.looping as lp -from pulse_lib.virtual_channel_constructors import IQ_channel_constructor, virtual_gates_constructor - -import scipy.signal.windows as windows +from configuration.medium_iq import init_hardware, init_pulselib start_all_logging() logger.get_file_handler().setLevel(logging.DEBUG) -try: - qcodes.Instrument.close_all() -except: pass - - -def set_channel_props(pulse, channel_name, compensation_limits=(0,0), attenuation=1.0, delay=0, bias_T_rc_time=None): - pulse.add_channel_compensation_limit(channel_name, compensation_limits) - pulse.add_channel_attenuation(channel_name, attenuation) - pulse.add_channel_delay(channel_name, delay) - pulse.add_channel_bias_T_compensation(channel_name, bias_T_rc_time) - - -def init_pulselib(awgs, digitizer): - p = pulselib(backend='Keysight_QS') - for awg in awgs: - p.add_awg(awg) - p.add_digitizer(digitizer) - - awg1, awg2 = awgs - - p.define_channel('P1', awg1.name, 1) - p.define_channel('P2', awg1.name, 2) - p.define_channel('P3', awg1.name, 3) - p.define_channel('P4', awg1.name, 4) - - set_channel_props(p, 'P1', compensation_limits=(-500,500), attenuation=2.0, delay=0, bias_T_rc_time=0.001) - set_channel_props(p, 'P2', compensation_limits=(-250,250), attenuation=1.0, delay=0, bias_T_rc_time=0.001) - set_channel_props(p, 'P3', compensation_limits=(-280,280), attenuation=1.0, delay=0, bias_T_rc_time=0.001) - set_channel_props(p, 'P4', bias_T_rc_time=0.001) - - p.define_digitizer_channel('SE1', digitizer.name, 1) - - p.finish_init() - - # add virtual channels. - - virtual_gate_set_1 = virtual_gates_constructor(p) - virtual_gate_set_1.add_real_gates('P1','P2','P3','P4') - virtual_gate_set_1.add_virtual_gates('vP1','vP2','vP3','vP4') - virtual_gate_set_1.add_virtual_gate_matrix(0.9*np.eye(4) + 0.1) - - return p - - def create_seq(pulse_lib): seg1 = pulse_lib.mk_segment(name='init') @@ -88,7 +34,7 @@ def create_seq(pulse_lib): s.vP4.add_ramp_ss(1e4, 3e4, 0, 50) s.vP4.add_block(3e4, 7e4, 50) s.vP4.add_ramp_ss(7e4, 9e4, 50, 0) - s.SE1.acquire(4e4, t_measure=2e4) + s.SD1.acquire(4e4, t_measure=2e4) # generate the sequence and upload it. my_seq = pulse_lib.mk_sequence([seg1, seg2, seg3]) @@ -123,11 +69,11 @@ def play_next(): my_seq.play([index], release=True) -awg1 = MockM3202A_fpga("A1", 0, 2) -awg2 = MockM3202A_QS("A2", 0, 4) -dig = MockM3102A_QS("Dig", 0, 9) +# create "AWG1","AWG2" +awgs, digs = init_hardware() -pulse = init_pulselib([awg1, awg2], dig) +# create channels +pulse = init_pulselib(awgs, digs, virtual_gates=True) my_seq = create_seq(pulse) @@ -137,7 +83,7 @@ job = my_seq.upload([0]) my_seq.play([0], release=False) -plot(my_seq, job, (awg1,dig) ) +plot(my_seq, job, awgs) pprint(job.upload_info) my_seq.play([0], release=True) diff --git a/pulse_lib/examples/example_param_sweep.py b/pulse_lib/examples/example_param_sweep.py index 0b6f5a85c67659cb5db0f789945fa4f8b1922bf3..6af659582d3b9bfc5d9e833fff85b5bb59f6aebe 100644 --- a/pulse_lib/examples/example_param_sweep.py +++ b/pulse_lib/examples/example_param_sweep.py @@ -8,10 +8,10 @@ from utils.plot import plot_awgs # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs) +p = init_pulselib(awgs, digs) v_param = lp.linspace(0, 200, 11, axis=0, unit = "mV", name = "vPulse") t_wait = lp.linspace(20, 100, 5, axis=1, unit = "mV", name = "t_wait") diff --git a/pulse_lib/examples/example_rabi.py b/pulse_lib/examples/example_rabi.py index 14a4fe6ea05068ab08712e933a8eae2aab67e68c..b99deb6d87ed732f14941b18b65d8d9bb5d413b0 100644 --- a/pulse_lib/examples/example_rabi.py +++ b/pulse_lib/examples/example_rabi.py @@ -8,10 +8,10 @@ from configuration.small_iq import init_hardware, init_pulselib from utils.plot import plot_awgs # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) gates = ['vP1','vP2'] v_init = [70, 20] @@ -19,7 +19,7 @@ v_manip = [0,0] v_read = [30, 25] t_measure = 100 # short time for visibility of other pulses -t_X90 = 50 +t_X90 = 60 amplitude = 50 t_pulse = lp.linspace(100, 1000, 10, axis=0) @@ -29,7 +29,7 @@ amplitude = 50 # init pulse init = p.mk_segment() init.add_block(0, 100, gates, v_init, reset_time=True) -init.add_ramp(0, 50, gates, v_init, v_manip) +init.add_ramp(0, 60, gates, v_init, v_manip) manip = p.mk_segment() manip.q1.add_MW_pulse(0, t_pulse, amplitude, f_drive) diff --git a/pulse_lib/examples/example_ramsey.py b/pulse_lib/examples/example_ramsey.py index f98b37f8b1e940b45e28738ba173d43405d22bd3..cad7c3fd316c712797c034360a13281b80714b14 100644 --- a/pulse_lib/examples/example_ramsey.py +++ b/pulse_lib/examples/example_ramsey.py @@ -1,4 +1,3 @@ -import numpy as np import matplotlib.pyplot as pt from pulse_lib.tests.hw_schedule_mock import HardwareScheduleMock @@ -9,11 +8,12 @@ from configuration.small_iq import init_hardware, init_pulselib from utils.plot import plot_awgs # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) + gates = ['vP1','vP2'] v_init = [70, 20] @@ -22,10 +22,12 @@ v_read = [30, 25] t_measure = 100 # short time for visibility of other pulses f_drive = 2.420e9 -t_X90 = 50 +t_X90 = 60 amplitude = 50 -t_wait = lp.linspace(0, 200, 21, axis=0) +p.qubit_channels['q1'].reference_frequency = 2.420e9 + +t_wait = lp.linspace(0, 200, 11, axis=0) # init pulse init = p.mk_segment() diff --git a/pulse_lib/examples/example_smooth_step.py b/pulse_lib/examples/example_smooth_step.py index ff71dc800e6709a09249b303c3eda65f07014897..f183b1239012c1f1e24ca0a59294a6e1f8394d2d 100644 --- a/pulse_lib/examples/example_smooth_step.py +++ b/pulse_lib/examples/example_smooth_step.py @@ -49,10 +49,10 @@ def gaussian_step(duration, sample_rate, amplitude, stddev): # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) seg = p.mk_segment() diff --git a/pulse_lib/examples/example_virtual_IQ_markers_plotting.py b/pulse_lib/examples/example_virtual_IQ_markers_plotting.py index 5811edcb8dd516e519896cba8930f8abad6b100a..1f8543716a76abfd617cee8e22351b8087916065 100644 --- a/pulse_lib/examples/example_virtual_IQ_markers_plotting.py +++ b/pulse_lib/examples/example_virtual_IQ_markers_plotting.py @@ -1,95 +1,19 @@ -import numpy as np -import time import logging from pprint import pprint import matplotlib.pyplot as pt +import scipy.signal.windows as windows -import qcodes import qcodes.logger as logger from qcodes.logger import start_all_logging -from pulse_lib.base_pulse import pulselib -from pulse_lib.tests.mock_m3202a import MockM3202A, MockM3202A_fpga from pulse_lib.tests.hw_schedule_mock import HardwareScheduleMock -import pulse_lib.segments.utility.looping as lp -from pulse_lib.virtual_channel_constructors import IQ_channel_constructor, virtual_gates_constructor +from configuration.medium_iq import init_hardware, init_pulselib -import scipy.signal.windows as windows start_all_logging() logger.get_file_handler().setLevel(logging.DEBUG) - -try: - qcodes.Instrument.close_all() -except: pass - - -def set_channel_props(pulse, channel_name, compensation_limits=(0,0), attenuation=1.0, delay=0): - pulse.add_channel_compensation_limit(channel_name, compensation_limits) - pulse.awg_channels[channel_name].attenuation = attenuation - pulse.add_channel_delay(channel_name, delay) - - - -def init_pulselib(awg1, awg2, awg3): - p = pulselib() - p.add_awg(awg1) - p.add_awg(awg2) - p.add_awg(awg3) - p.define_channel('P1', awg1.name, 1) - p.define_channel('P2', awg1.name, 2) - p.define_channel('P3', awg1.name, 3) - p.define_channel('P4', awg1.name, 4) - - p.define_marker('M1', awg3.name, 3, setup_ns=40, hold_ns=20) - p.define_marker('M2', awg3.name, 0, setup_ns=40, hold_ns=20) - - set_channel_props(p, 'P1', compensation_limits=(-100,100), attenuation=2.0, delay=0) - set_channel_props(p, 'P2', compensation_limits=(-50,50), attenuation=1.0, delay=0) - set_channel_props(p, 'P3', compensation_limits=(-80,80), attenuation=1.0, delay=0) - - p.define_channel('I1', awg2.name, 1) - p.define_channel('Q1', awg2.name, 2) - p.define_channel('I2', awg2.name, 3) - p.define_channel('Q2', awg2.name, 4) - - set_channel_props(p, 'I1', compensation_limits=(-80,80), attenuation=1.0, delay=0) - - p.finish_init() - - # add virtual channels. - - virtual_gate_set_1 = virtual_gates_constructor(p) - virtual_gate_set_1.add_real_gates('P1','P2','P3','P4') - virtual_gate_set_1.add_virtual_gates('vP1','vP2','vP3','vP4') - virtual_gate_set_1.add_virtual_gate_matrix(0.9*np.eye(4) + 0.1) - - #make virtual channels for IQ usage (also here, make one one of these object per MW source) - IQ_chan_set_1 = IQ_channel_constructor(p) - # set right association of the real channels with I/Q output. - IQ_chan_set_1.add_IQ_chan("I1", "I") - IQ_chan_set_1.add_IQ_chan("Q1", "Q") - IQ_chan_set_1.add_marker("M1") - # frequency of the MW source - IQ_chan_set_1.set_LO(2e9) - IQ_chan_set_1.add_virtual_IQ_channel("MW_qubit_1") - IQ_chan_set_1.add_virtual_IQ_channel("MW_qubit_2") - - IQ_chan_set_2 = IQ_channel_constructor(p) - # set right association of the real channels with I/Q output. - IQ_chan_set_2.add_IQ_chan("I2", "I") - IQ_chan_set_2.add_IQ_chan("Q2", "Q") - IQ_chan_set_2.add_marker("M2") - # frequency of the MW source - IQ_chan_set_2.set_LO(2e9) - IQ_chan_set_2.add_virtual_IQ_channel("MW_qubit_3") - IQ_chan_set_2.add_virtual_IQ_channel("MW_qubit_4") - - return p - - def tukey(duration, sample_rate): n_points = int(duration * sample_rate) return windows.tukey(n_points, alpha=0.5) @@ -110,22 +34,22 @@ def create_seq(pulse_lib): s.vP4.add_ramp_ss(0, 100, 50, 100) s.vP4.add_ramp_ss(100, 200, 100, 50) - s.MW_qubit_2.add_MW_pulse(50, 150, 50, 200e6, AM=tukey) + s.q2.add_MW_pulse(40, 140, 50, 2450e6, AM=tukey) - s.MW_qubit_1.add_MW_pulse(0, 300, 20, 35e6) + s.q1.add_MW_pulse(0, 300, 20, 2435e6) - s.MW_qubit_3.add_MW_pulse(250, 300, 60, 50e6) + s.q3.add_MW_pulse(240, 300, 60, 2900e6) seg2b = pulse_lib.mk_segment('manip2') s = seg2b s.vP4.add_ramp_ss(0, 100, 50, 100) s.vP4.add_ramp_ss(100, 200, 100, 50) - s.MW_qubit_2.add_MW_pulse(50, 150, 50, 200e6, AM=tukey) + s.q2.add_MW_pulse(40, 140, 50, 2450e6, AM=tukey) - s.MW_qubit_1.add_MW_pulse(0, 300, 60, 35e6) + s.q1.add_MW_pulse(0, 300, 40, 2435e6) - s.MW_qubit_3.add_MW_pulse(250, 300, 20, 20e6) + s.q3.add_MW_pulse(240, 300, 20, 2900e6) seg3 = pulse_lib.mk_segment('measure', 1e8) s = seg3 @@ -174,11 +98,11 @@ def play_next(): my_seq.play([index], release=True) -awg1 = MockM3202A("A1", 0, 2) -awg2 = MockM3202A("A2", 0, 3) -awg3 = MockM3202A_fpga("A3", 0, 4) +# create "AWG1","AWG2" +awgs, digs = init_hardware() -pulse = init_pulselib(awg1, awg2, awg3) +# create channels +pulse = init_pulselib(awgs, digs, virtual_gates=True) my_seq = create_seq(pulse) @@ -188,7 +112,7 @@ job = my_seq.upload() my_seq.play(release=False) -plot(my_seq, job, (awg1, awg2, awg3) ) +plot(my_seq, job, awgs) pprint(job.upload_info) my_seq.play(release=True) diff --git a/pulse_lib/examples/example_virtual_IQ_multiple_segments.py b/pulse_lib/examples/example_virtual_IQ_multiple_segments.py index 4744edcb2975d2bba706f3732f4072560effbe43..2f52c407f7175e4089ea82884d97d2f73bc9edb7 100644 --- a/pulse_lib/examples/example_virtual_IQ_multiple_segments.py +++ b/pulse_lib/examples/example_virtual_IQ_multiple_segments.py @@ -1,99 +1,19 @@ import numpy as np -import time import logging from pprint import pprint import matplotlib.pyplot as pt -import qcodes import qcodes.logger as logger from qcodes.logger import start_all_logging -from pulse_lib.base_pulse import pulselib -from pulse_lib.tests.mock_m3202a import MockM3202A, MockM3202A_fpga from pulse_lib.tests.hw_schedule_mock import HardwareScheduleMock -import pulse_lib.segments.utility.looping as lp -from pulse_lib.virtual_channel_constructors import IQ_channel_constructor, virtual_gates_constructor - -import scipy.signal.windows as windows +from configuration.medium_iq import init_hardware, init_pulselib start_all_logging() logger.get_file_handler().setLevel(logging.DEBUG) -try: - qcodes.Instrument.close_all() -except: pass - - -def set_channel_props(pulse, channel_name, compensation_limits=(0,0), attenuation=1.0, delay=0): - pulse.add_channel_compenstation_limit(channel_name, compensation_limits) - pulse.awg_channels[channel_name].attenuation = attenuation - pulse.add_channel_delay(channel_name, delay) - - - -def init_pulselib(awg1, awg2, awg3): - p = pulselib() - p.add_awg(awg1) - p.add_awg(awg2) - p.add_awg(awg3) - p.define_channel('P1', awg1.name, 1) - p.define_channel('P2', awg1.name, 2) - p.define_channel('P3', awg1.name, 3) - p.define_channel('P4', awg1.name, 4) - - p.define_marker('M1', awg3.name, 3, setup_ns=40, hold_ns=20) - p.define_marker('M2', awg3.name, 0, setup_ns=40, hold_ns=20) - - set_channel_props(p, 'P1', compensation_limits=(-100,100), attenuation=2.0, delay=0) - set_channel_props(p, 'P2', compensation_limits=(-50,50), attenuation=1.0, delay=0) - set_channel_props(p, 'P3', compensation_limits=(-80,80), attenuation=1.0, delay=0) - - p.define_channel('I1', awg2.name, 1) - p.define_channel('Q1', awg2.name, 2) - p.define_channel('I2', awg2.name, 3) - p.define_channel('Q2', awg2.name, 4) - - set_channel_props(p, 'I1', compensation_limits=(-80,80), attenuation=1.0, delay=0) - - p.finish_init() - - # add virtual channels. - - virtual_gate_set_1 = virtual_gates_constructor(p) - virtual_gate_set_1.add_real_gates('P1','P2','P3','P4') - virtual_gate_set_1.add_virtual_gates('vP1','vP2','vP3','vP4') - virtual_gate_set_1.add_virtual_gate_matrix(0.9*np.eye(4) + 0.1) - - #make virtual channels for IQ usage (also here, make one one of these object per MW source) - IQ_chan_set_1 = IQ_channel_constructor(p) - # set right association of the real channels with I/Q output. - IQ_chan_set_1.add_IQ_chan("I1", "I") - IQ_chan_set_1.add_IQ_chan("Q1", "Q") - IQ_chan_set_1.add_marker("M1") - # frequency of the MW source - IQ_chan_set_1.set_LO(2e9) - IQ_chan_set_1.add_virtual_IQ_channel("MW_qubit_1") - IQ_chan_set_1.add_virtual_IQ_channel("MW_qubit_2") - - IQ_chan_set_2 = IQ_channel_constructor(p) - # set right association of the real channels with I/Q output. - IQ_chan_set_2.add_IQ_chan("I2", "I") - IQ_chan_set_2.add_IQ_chan("Q2", "Q") - IQ_chan_set_2.add_marker("M2") - # frequency of the MW source - IQ_chan_set_2.set_LO(2e9) - IQ_chan_set_2.add_virtual_IQ_channel("MW_qubit_3") - IQ_chan_set_2.add_virtual_IQ_channel("MW_qubit_4") - - return p - - -def tukey(duration, sample_rate): - n_points = int(duration * sample_rate) - return windows.tukey(n_points, alpha=0.5) - def create_seq(pulse_lib): seg1 = pulse_lib.mk_segment(name='init', sample_rate=1e8) @@ -110,23 +30,15 @@ def create_seq(pulse_lib): s.vP4.add_ramp_ss(0, 100, 50, 100) s.vP4.add_ramp_ss(100, 200, 100, 50) -# s.MW_qubit_2.add_MW_pulse(50, 150, 50, 200e6, AM=tukey) - - s.MW_qubit_1.add_MW_pulse(0, 300, 20, 35e6) - s.MW_qubit_1.add_phase_shift(300, np.pi/2) - -# s.MW_qubit_3.add_MW_pulse(250, 300, 60, 50e6) + s.q1.add_MW_pulse(0, 300, 20, 2.435e9) + s.q1.add_phase_shift(300, np.pi/2) seg2b = pulse_lib.mk_segment('manip2') s = seg2b s.vP4.add_ramp_ss(0, 100, 50, 100) s.vP4.add_ramp_ss(100, 200, 100, 50) -# s.MW_qubit_2.add_MW_pulse(50, 150, 50, 200e6, AM=tukey) - - s.MW_qubit_1.add_MW_pulse(0, 300, 20, 35e6) - -# s.MW_qubit_3.add_MW_pulse(250, 300, 20, 20e6) + s.q1.add_MW_pulse(0, 300, 20, 2.435e9) seg3 = pulse_lib.mk_segment('measure', 1e8) s = seg3 @@ -136,17 +48,15 @@ def create_seq(pulse_lib): s.reset_time() s.add_HVI_marker('ping', 99) - # segment without data. Will be used for DC compensation with low sample rate seg4 = pulse_lib.mk_segment('dc compensation', 1e7) - # wait 10 ns (i.e. 1 sample at 1e8 MSa/s) + # wait 100 ns (i.e. 10 samples at 1e8 MSa/s) seg4.P1.wait(100) # generate the sequence and upload it. my_seq = pulse_lib.mk_sequence([seg1, seg2, seg2b, seg3, seg4]) my_seq.set_hw_schedule(HardwareScheduleMock()) my_seq.n_rep = 1 -# my_seq.sample_rate = 2e8 return my_seq @@ -175,11 +85,12 @@ def play_next(): my_seq.play([index], release=True) -awg1 = MockM3202A("A1", 0, 2) -awg2 = MockM3202A("A2", 0, 3) -awg3 = MockM3202A_fpga("A3", 0, 4) -pulse = init_pulselib(awg1, awg2, awg3) +# create "AWG1","AWG2" +awgs, digs = init_hardware() + +# create channels +pulse = init_pulselib(awgs, digs, virtual_gates=True) my_seq = create_seq(pulse) @@ -189,7 +100,7 @@ job = my_seq.upload([0]) my_seq.play([0], release=False) -plot(my_seq, job, (awg1, awg2, awg3) ) +plot(my_seq, job, awgs) pprint(job.upload_info) my_seq.play([0], release=True) diff --git a/pulse_lib/examples/example_virtual_matrix_plotting.py b/pulse_lib/examples/example_virtual_matrix_plotting.py index 9b1aa480d09f3b46adf8cc0f09ffef705ed62820..650c92f6ecb2d668a50b6d0497ee9157e6a7a2e6 100644 --- a/pulse_lib/examples/example_virtual_matrix_plotting.py +++ b/pulse_lib/examples/example_virtual_matrix_plotting.py @@ -7,10 +7,10 @@ from utils.plot import plot_awgs # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) seg1 = p.mk_segment() diff --git a/pulse_lib/examples/psb_example.py b/pulse_lib/examples/psb_example.py index 4c4b22a639cc339b7399b36656a5a5c96ddf4003..d36fe34718cec7077be860fe934e5fab58943a0f 100644 --- a/pulse_lib/examples/psb_example.py +++ b/pulse_lib/examples/psb_example.py @@ -9,10 +9,10 @@ from utils.plot import plot_awgs # create "AWG1" -awgs = init_hardware() +awgs, digs = init_hardware() # create channels P1, P2 -p = init_pulselib(awgs, virtual_gates=True) +p = init_pulselib(awgs, digs, virtual_gates=True) seg = p.mk_segment() diff --git a/pulse_lib/examples/utils/plot.py b/pulse_lib/examples/utils/plot.py index c931cdb1a4f22fc518f9e743052ca57e91d0062b..e4f0c6d04c81717ed1f334ed8c5e8c7d00cf926e 100644 --- a/pulse_lib/examples/utils/plot.py +++ b/pulse_lib/examples/utils/plot.py @@ -2,10 +2,19 @@ import matplotlib.pyplot as pt def plot_awgs(awgs, bias_T_rc_time=None): + do_plot = False + for awg in awgs: + if hasattr(awg, 'plot'): + do_plot = True + + if not do_plot: + return + pt.figure() for awg in awgs: - awg.plot(bias_T_rc_time=bias_T_rc_time) + if hasattr(awg, 'plot'): + awg.plot(bias_T_rc_time=bias_T_rc_time) pt.legend() pt.ylabel('amplitude [V]') diff --git a/pulse_lib/qblox/pulsar_sequencers.py b/pulse_lib/qblox/pulsar_sequencers.py new file mode 100644 index 0000000000000000000000000000000000000000..02959cdca7365a3edcd2a51b5d0fb0bda04905a1 --- /dev/null +++ b/pulse_lib/qblox/pulsar_sequencers.py @@ -0,0 +1,208 @@ +from numbers import Number +from copy import copy +import numpy as np +from .rendering import render_custom_pulse + +class SequenceBuilderBase: + def __init__(self, name, sequencer): + self.name = name + self.seq = sequencer + self.t_end = 0 + self.sinewaves = [] + self.t_next_marker = None + self.imarker = 0 + self.max_output_voltage = sequencer.max_output_voltage + + def register_sinewave(self, waveform): + try: + index = self.sinewaves.index(waveform) + waveid = f'sine{index}' + except: + index = len(self.sinewaves) + self.sinewaves.append(waveform) + waveid = f'sine{index}' + data = waveform.render() + self.seq.add_wave(waveid, data) + return waveid + + def register_sinewave_iq(self, waveform): + try: + index = self.sinewaves.index(waveform) + waveids = (f'iq{index}I', f'iq{index}Q') + except: + index = len(self.sinewaves) + self.sinewaves.append(waveform) + waveids = (f'iq{index}I', f'iq{index}Q') + data = waveform.render_iq() + self.seq.add_wave(waveids[0], data[0]) + self.seq.add_wave(waveids[1], data[1]) + return waveids + + def add_markers(self, markers): + self.markers = markers + self.imarker = -1 + self.set_next_marker() + + def set_next_marker(self): + self.imarker += 1 + if len(self.markers) > self.imarker: + self.t_next_marker = self.markers[self.imarker][0] + else: + self.t_next_marker = None + + def insert_markers(self, t): + while self.t_next_marker is not None and t > self.t_next_marker: + marker = self.markers[self.imarker] + self._set_markers(marker[0], marker[1]) + self.set_next_marker() + + def _set_markers(self, t, value): + self.seq.set_markers(value, t_offset=t) + + def _update_time(self, t, duration): + if t < self.t_end: + raise Exception(f'Overlapping pulses {t} > {self.t_end} ({self.name})') + self.insert_markers(t) + self.t_end = t + duration + + def add_comment(self, comment): + self.seq.add_comment(comment) + + def close(self): + while self.t_next_marker is not None: + marker = self.markers[self.imarker] + self._set_markers(marker[0], marker[1]) + self._update_time(self.t_next_marker, 0.0) + self.set_next_marker() + + +class VoltageSequenceBuilder(SequenceBuilderBase): + def __init__(self, name, sequencer, rc_time=None): + super().__init__(name, sequencer) + self.rc_time = rc_time + self.integral = 0.0 + self.custom_pulses = [] + if rc_time is not None: + self.compensation_factor = 1 / (1e9 * rc_time) + else: + self.compensation_factor = 0.0 + + def ramp(self, t, duration, v_start, v_end): + self._update_time(t, duration) + v_start_comp = self._compensate_bias_T(v_start) + v_end_comp = self._compensate_bias_T(v_end) + self.integral += duration * (v_start + v_end)/2 + self.seq.ramp(duration, v_start_comp, v_end_comp, t_offset=t, v_after=None) + + def set_offset(self, t, duration, v): + # sequencer only updates offset, no continuing action: duration=0 + self._update_time(t, 0) + v_comp = self._compensate_bias_T(v) + self.integral += duration * v + if self.rc_time and duration > 0.01 * 1e9 * self.rc_time: + self._update_time(t, duration) + v_end = self._compensate_bias_T(v) + self.seq.ramp(duration, v_comp, v_end, t_offset=t, v_after=None) + else: + self.seq.set_offset(v_comp, t_offset=t) + + def pulse(self, t, duration, amplitude, waveform): + self._update_time(t, duration) + # TODO @@@ add 2*np.pi*t*frequency*1e-9 to phase ?? + wave_id = self.register_sinewave(waveform) + self.seq.shaped_pulse(wave_id, amplitude, t_offset=t) + + def custom_pulse(self, t, duration, amplitude, custom_pulse): + self._update_time(t, duration) + wave_id = self.register_custom_pulse(custom_pulse, amplitude) + self.seq.shaped_pulse(wave_id, 1.0, t_offset=t) + + def register_custom_pulse(self, custom_pulse, scaling): + data = render_custom_pulse(custom_pulse, scaling) + for index,wave in enumerate(self.custom_pulses): + if np.all(wave == data): + return f'pulse{index}' + index = len(self.custom_pulses) + waveid = f'pulse{index}' + self.custom_pulses.append(data) + self.seq.add_wave(waveid, data) + return waveid + + + def _compensate_bias_T(self, v): + return v + self.integral * self.compensation_factor + + +class IQSequenceBuilder(SequenceBuilderBase): + def __init__(self, name, sequencer, nco_frequency): + super().__init__(name, sequencer) + self.nco_frequency = nco_frequency + self.add_comment(f'IQ: NCO={self.nco_frequency/1e6:7.2f} MHz') + + def pulse(self, t, duration, amplitude, waveform): + self._update_time(t, duration) + self.add_comment(f'MW pulse {waveform.frequency/1e6:6.2f} MHz {duration} ns') + waveform = copy(waveform) + waveform.frequency -= self.nco_frequency + + if abs(waveform.frequency) > 1: + # TODO @@@ Fix coherent pulses + print(f'Warning: incorrect phase for pulse at {t} ns') + wave_ids = self.register_sinewave_iq(waveform) + self.seq.shaped_pulse(wave_ids[0], amplitude, + wave_ids[1], amplitude, + t_offset=t) + elif not isinstance(waveform.phmod, Number): + wave_ids = self.register_sinewave_iq(waveform) + self.seq.shaped_pulse(wave_ids[0], amplitude, + wave_ids[1], amplitude, + t_offset=t) + else: + # frequency is less than 1 Hz make it 0. + waveform.frequency = 0 + # phase is constant + cycles = 2*np.pi*(waveform.phase + waveform.phmod) + if isinstance(waveform.amod, Number): + ampI = amplitude * waveform.amod * np.sin(cycles) + ampQ = amplitude * waveform.amod * np.cos(cycles) + # generate block pulse + self.seq.block_pulse(duration, ampI, ampQ, t_offset=t) + else: + # phase is accounted for in ampI, ampQ + waveform.phase = np.pi*0.5 + waveform.phmod = 0 + ampI = amplitude * np.sin(cycles) + ampQ = amplitude * np.cos(cycles) + # same wave for I and Q + wave_id = self.register_sinewave(waveform) + self.seq.shaped_pulse(wave_id, ampI, wave_id, ampQ, t_offset=t) + + def shift_phase(self, t, phase): + self._update_time(t, 0.0) + self.seq.shift_phase(phase, t_offset=t) + + + +class AcquisitionSequenceBuilder(SequenceBuilderBase): + def __init__(self, name, sequencer, n_repetitions): + super().__init__(name, sequencer) + self.n_repetitions = n_repetitions + self.n_triggers = 0 + # allocate minim size later adjust for number of triggers + self.seq.add_acquisition_bins('default', n_repetitions) + + def acquire(self, t, t_measure, n=1): + self.n_triggers += n + if n == 1: + self.seq.acquire('default', 'increment', t_offset=t) + else: + period = t_measure + self.seq.repeated_acquire(n, period, 'default', 'increment', ) + + def close(self): + super().close() + num_bins = self.n_triggers * self.n_repetitions + self.seq.add_acquisition_bins('default', num_bins) + + + diff --git a/pulse_lib/qblox/pulsar_uploader.py b/pulse_lib/qblox/pulsar_uploader.py new file mode 100644 index 0000000000000000000000000000000000000000..04a235eba6152b1cd4c6d593c560e99f7f79dffd --- /dev/null +++ b/pulse_lib/qblox/pulsar_uploader.py @@ -0,0 +1,720 @@ +import time +from datetime import datetime +import numpy as np +import logging +from dataclasses import dataclass, field +from typing import List, Dict, Optional, Union + +from .rendering import SineWaveform, get_modulation +from .pulsar_sequencers import ( + VoltageSequenceBuilder, + IQSequenceBuilder, + AcquisitionSequenceBuilder, + SequenceBuilderBase) + +from q1pulse import Q1Instrument + +from pulse_lib.segments.data_classes.data_IQ import IQ_data_single +from pulse_lib.segments.data_classes.data_pulse import ( + PhaseShift, custom_pulse_element, OffsetRamp) + + +def iround(value): + return int(value+0.5) + +class PulsarConfig: + ALIGNMENT = 4 # pulses must be aligned on 4 ns boundaries + + +class PulsarUploader: + verbose = True + + def __init__(self, awg_devices, awg_channels, marker_channels, + IQ_channels, qubit_channels, digitizers, digitizer_channels): + self.awg_channels = awg_channels + self.marker_channels = marker_channels + self.IQ_channels = IQ_channels + self.qubit_channels = qubit_channels + self.digitizer_channels = digitizer_channels + + self.jobs = [] + + q1 = Q1Instrument() + self.q1instrument = q1 + + for awg in awg_devices.values(): + q1.add_qcm(awg) + for module in digitizers.values(): + # QRM is passed as digitizer + q1.add_qrm(module) + + self._link_markers_to_seq() + self._get_voltage_channels() + + for name, awg_ch in self.awg_voltage_channels.items(): + q1.add_control(name, awg_ch.awg_name, [awg_ch.channel_number]) + + for name, qubit_ch in self.qubit_channels.items(): + iq_out_channels = qubit_ch.iq_channel.IQ_out_channels + out_channels = [self.awg_channels[iq_out_ch.awg_channel_name] + for iq_out_ch in iq_out_channels] + module_name = out_channels[0].awg_name + # TODO @@@ check I and Q phase. + q1.add_control(name, module_name, [out_ch.channel_number for out_ch in out_channels]) + + for name, dig_ch in self.digitizer_channels.items(): + q1.add_readout(name, dig_ch.module_name) + + for name, marker_ch in self.marker_channels.items(): + # TODO implement marker channel inversion + if marker_ch.invert: + raise Exception(f'Marker channel inversion not (yet) supported') + + + def _get_voltage_channels(self): + iq_out_channels = [] + + for IQ_channel in self.IQ_channels.values(): + iq_pair = IQ_channel.IQ_out_channels + if len(iq_pair) != 2: + raise Exception(f'IQ-channel should have 2 awg channels ' + f'({iq_pair})') + out_names = [self.awg_channels[ch_info.awg_channel_name] for ch_info in iq_pair] + awg_names = [awg_channel.awg_name for awg_channel in out_names] + + if awg_names[0] != awg_names[1]: + raise Exception(f'IQ channels should be on 1 awg: {iq_pair}') + + iq_out_channels += [ch_info.awg_channel_name for ch_info in iq_pair] + + self.awg_voltage_channels = {} + for name, awg_channel in self.awg_channels.items(): + if name not in iq_out_channels: + self.awg_voltage_channels[name] = awg_channel + + + + def _link_markers_to_seq(self): + default_iq_markers = {} + for qubit_channel in self.qubit_channels.values(): + iq_channel = qubit_channel.iq_channel + marker_channels = iq_channel.marker_channels + for marker_name in marker_channels: + awg_module_name = iq_channel.IQ_out_channels[0].awg_channel_name + m_ch = self.marker_channels[marker_name] + if awg_module_name != m_ch.module_name: + default_iq_markers[m_ch.name] = qubit_channel.channel_name + + seq_markers = {} + marker_sequencers = [] + for channel_name, marker_channel in self.marker_channels.items(): + if marker_channel.sequencer_name is not None: + seq_name = marker_channel.sequencer_name + elif channel_name in default_iq_markers: + seq_name = default_iq_markers[channel_name] + else: + seq_name = f'_M_{marker_channel.module_name}' + marker_sequencers.append(seq_name) + self.q1instrument.add_control(seq_name, marker_channel.module_name) + mlist = seq_markers.setdefault(seq_name, []) + mlist.append(channel_name) + + self.seq_markers = seq_markers + self.marker_sequencers = marker_sequencers + + + @property + def supports_conditionals(self): + return False + + def get_effective_sample_rate(self, sample_rate): + """ + Returns the sample rate that will be used by the AWG. + """ + return 1e9 + + + def create_job(self, sequence, index, seq_id, n_rep, sample_rate, neutralize=True): + # remove any old job with same sequencer and index + self.release_memory(seq_id, index) + return Job(self.jobs, sequence, index, seq_id, n_rep, sample_rate, neutralize) + + + def add_upload_job(self, job): + ''' + add a job to the uploader. + Args: + job (upload_job) : upload_job object that defines what needs to be uploaded and possible post processing of the waveforms (if needed) + ''' + ''' + Class taking care of putting the waveform on the right AWG. + + Steps: + 1) get all the upload data + 2) perform DC correction (if needed) + 3) convert data in an aprropriate upload format + 4) start upload of all data + 5) store reference to uploaded waveform in job + ''' + start = time.perf_counter() + + aggregator = UploadAggregator(self.q1instrument, self.awg_channels, + self.marker_channels, self.digitizer_channels, + self.qubit_channels, self.awg_voltage_channels, + self.marker_sequencers, self.seq_markers + ) + + aggregator.build(job) + + self.jobs.append(job) + + duration = time.perf_counter() - start + logging.debug(f'generated upload data ({duration*1000:6.3f} ms)') + print(f'Generated upload data in {duration*1000:6.3f} ms') + + + def __get_job(self, seq_id, index): + """ + get job data of an uploaded segment + Args: + seq_id (uuid) : id of the sequence + index (tuple) : index that has to be played + Return: + job (upload_job) :job, with locations of the sequences to be uploaded. + """ + for job in self.jobs: + if job.seq_id == seq_id and job.index == index and not job.released: + return job + + logging.error(f'Job not found for index {index} of seq {seq_id}') + raise ValueError(f'Sequence with id {seq_id}, index {index} not placed for upload .. . Always make sure to first upload your segment and then do the playback.') + + + def play(self, seq_id, index, release_job = True): + """ + start playback of a sequence that has been uploaded. + Args: + seq_id (uuid) : id of the sequence + index (tuple) : index that has to be played + release_job (bool) : release memory on AWG after done. + """ + + job = self.__get_job(seq_id, index) + +# # TODO @@@ cleanup frequency update hack + for name, qubit_channel in self.qubit_channels.items(): + nco_frequency = qubit_channel.reference_frequency - qubit_channel.iq_channel.LO + self.q1instrument.controllers[name].nco_frequency = nco_frequency + self.q1instrument.run_program(job.program) + + if release_job: + job.release() + + + def release_memory(self, seq_id=None, index=None): + """ + Release job memory for `seq_id` and `index`. + Args: + seq_id (uuid) : id of the sequence. if None release all + index (tuple) : index that has to be released; if None release all. + """ + for job in self.jobs: + if (seq_id is None + or (job.seq_id == seq_id and (index is None or job.index == index))): + job.release() + + + def release_jobs(self): + for job in self.jobs: + job.release() + + + +class Job(object): + """docstring for upload_job""" + def __init__(self, job_list, sequence, index, seq_id, n_rep, sample_rate, neutralize=True, priority=0): + ''' + Args: + job_list (list): list with all jobs. + sequence (list of list): list with list of the sequence + index (tuple) : index that needs to be uploaded + seq_id (uuid) : if of the sequence + n_rep (int) : number of repetitions of this sequence. + sample_rate (float) : sample rate + neutralize (bool) : place a neutralizing segment at the end of the upload + priority (int) : priority of the job (the higher one will be excuted first) + ''' + self.job_list = job_list + self.sequence = sequence + self.seq_id = seq_id + self.index = index + self.n_rep = n_rep + self.default_sample_rate = sample_rate + self.neutralize = neutralize + self.priority = priority + self.playback_time = 0 #total playtime of the waveform + + self.released = False + + logging.debug(f'new job {seq_id}-{index}') + + + def add_hw_schedule(self, hw_schedule, schedule_params): + """ + Add the scheduling to the AWG waveforms. + args: + hw_schedule (HardwareSchedule) : schedule for repetitively starting the AWG waveforms + kwargs : keyword arguments for the hardware schedule (see usage in the examples) + """ + self.hw_schedule = hw_schedule + self.schedule_params = schedule_params + + def release(self): + if self.released: + logging.warning(f'job {self.seq_id}-{self.index} already released') + return + + self.upload_info = None + logging.debug(f'release job {self.seq_id}-{self.index}') + self.released = True + + if self in self.job_list: + self.job_list.remove(self) + + + def __del__(self): + if not self.released: + logging.warn(f'Job {self.seq_id}-{self.index} was not released. ' + 'Automatic release in destructor.') + self.release() + + +@dataclass +class ChannelInfo: + # static data + delay_ns: float = 0 + amplitude: float = 0 + attenuation: float = 1.0 + dc_compensation: bool = False + dc_compensation_min: float = 0.0 + dc_compensation_max: float = 0.0 + bias_T_RC_time: Optional[float] = None + # aggregation state + integral: float = 0.0 + + +@dataclass +class JobUploadInfo: + dc_compensation_duration_ns: float = 0.0 + dc_compensation_voltages: Dict[str, float] = field(default_factory=dict) + +@dataclass +class SegmentRenderInfo: + # original times from sequence, cummulative start/end times + # first segment starts at t_start = 0 + t_start: float + npt: int # sample rate = 1GSa/s + + @property + def t_end(self): + return self.t_start + self.npt + + +@dataclass +class DigAcquisition: + start: int + t_measure: Optional[int] = None + n: int = 1 + threshold: Optional[int] = None + + +class UploadAggregator: + verbose = False + + def __init__(self, q1instrument, awg_channels, marker_channels, digitizer_channels, + qubit_channels, awg_voltage_channels, marker_sequencers, seq_markers): + + self.q1instrument = q1instrument + self.awg_voltage_channels = awg_voltage_channels + self.marker_channels = marker_channels + self.digitizer_channels = digitizer_channels + self.qubit_channels = qubit_channels + self.marker_sequencers = marker_sequencers + self.seq_markers = seq_markers + + self.channels = dict() + + delays = [] + for channel in awg_channels.values(): + info = ChannelInfo() + self.channels[channel.name] = info + + info.attenuation = channel.attenuation + info.delay_ns = channel.delay + info.amplitude = None # channel.amplitude cannot be taken into account + info.bias_T_RC_time = channel.bias_T_RC_time + delays.append(channel.delay) + + # Note: Compensation limits are specified before attenuation, i.e. at AWG output level. + # Convert compensation limit to device level. + info.dc_compensation_min = channel.compensation_limits[0] * info.attenuation + info.dc_compensation_max = channel.compensation_limits[1] * info.attenuation + info.dc_compensation = info.dc_compensation_min < 0 and info.dc_compensation_max > 0 + + for channel in marker_channels.values(): + delays.append(channel.delay - channel.setup_ns) + delays.append(channel.delay + channel.hold_ns) + + self.max_pre_start_ns = -min(0, *delays) + self.max_post_end_ns = max(0, *delays) + + + def _integrate(self, job): + + if not job.neutralize: + return + + for iseg,seg in enumerate(job.sequence): + # fixed sample rate + sample_rate = 1e9 + + for channel_name, channel_info in self.channels.items(): + if iseg == 0: + channel_info.integral = 0 + + if channel_info.dc_compensation: + seg_ch = seg[channel_name] + channel_info.integral += seg_ch.integrate(job.index, sample_rate) + logging.debug(f'Integral seg:{iseg} {channel_name} integral:{channel_info.integral}') + + + def _process_segments(self, job): + self.segments = [] + segments = self.segments + t_start = 0 + for seg in job.sequence: + # work with sample rate in GSa/s + sample_rate = 1 + duration = seg.get_total_time(job.index) + npt = int((duration * sample_rate)+0.5) + info = SegmentRenderInfo(t_start, npt) + segments.append(info) + t_start = info.t_end + + # add DC compensation + compensation_time = self.get_max_compensation_time() + compensation_time_ns = int(np.ceil(compensation_time*1e9 / 4)) * 4 # ns @@@ add align function + logging.debug(f'DC compensation time: {compensation_time_ns} ns') + + job.upload_info.dc_compensation_duration_ns = compensation_time_ns + + job.playback_time = segments[-1].t_end + compensation_time_ns + logging.debug(f'Playback time: {job.playback_time} ns') + + if UploadAggregator.verbose: + for segment in segments: + logging.info(f'segment: {segment}') + + + def get_markers(self, job, marker_channel): + # Marker on periods can overlap, also across segments. + # Get all start/stop times and merge them. + start_stop = [] + segments = self.segments + for iseg,(seg,seg_render) in enumerate(zip(job.sequence,segments)): + offset = seg_render.t_start + marker_channel.delay + self.max_pre_start_ns + seg_ch = seg[marker_channel.name] + ch_data = seg_ch._get_data_all_at(job.index) + + for pulse in ch_data.my_marker_data: + start_stop.append((offset + pulse.start - marker_channel.setup_ns, +1)) + start_stop.append((offset + pulse.stop + marker_channel.hold_ns, -1)) + + # merge markers + marker_value = 1 << marker_channel.channel_number + markers = [] + s = 0 + m = sorted(start_stop, key=lambda e:e[0]) + for t,on_off in m: + s += on_off + if s < 0: + logging.error(f'Marker error {marker_channel.name} {on_off}') + if s == 1 and on_off == 1: + markers.append((t, s, marker_value)) + if s == 0 and on_off == -1: + markers.append((t, s, marker_value)) + + return markers + + def get_markers_seq(self, job, seq_name): + marker_names = self.seq_markers.get(seq_name, []) + if len(marker_names) == 0: + return [] + + markers = [] + for marker_name in marker_names: + marker_channel = self.marker_channels[marker_name] + + markers += self.get_markers(job, marker_channel) + + s = 0 + last = -1 + m = sorted(markers, key=lambda e:e[0]) + seq_markers = [] + for t,on_off,value in m: + if on_off: + s |= value + else: + s &= ~value + if t == last: + seq_markers[-1] = (t,s) + else: + seq_markers.append((t,s)) + + return seq_markers + + def add_awg_channel(self, job, channel_name): + segments = self.segments + channel_info = self.channels[channel_name] + + t_offset = int((self.max_pre_start_ns - channel_info.delay_ns) / 4) * 4 + + seq = VoltageSequenceBuilder(channel_name, self.program[channel_name], + rc_time=channel_info.bias_T_RC_time) + scaling = 1/(channel_info.attenuation * seq.max_output_voltage*1000) + + markers = self.get_markers_seq(job, channel_name) + seq.add_markers(markers) + + for iseg,(seg,seg_render) in enumerate(zip(job.sequence,segments)): + seg_start = seg_render.t_start + t_offset + seg_ch = seg[channel_name] + data = seg_ch._get_data_all_at(job.index) + entries = data.get_data_elements() + for e in entries: + if isinstance(e, OffsetRamp): + t = iround(e.time + seg_start) + v_start = scaling * e.v_start + v_stop = scaling * e.v_stop + duration = iround(e.duration) + if abs(v_start - v_stop) > 6e-5: + # ramp only when > 2 bits on 16-bit signed resolution + seq.ramp(t, duration, v_start, v_stop) + else: + seq.set_offset(t, duration, v_start) + elif isinstance(e, IQ_data_single): + t = iround(e.start + seg_start) + duration = iround(e.stop - e.start) + amod, phmod = get_modulation(e.envelope, duration) + sinewave = SineWaveform(duration, e.frequency, e.start_phase, amod, phmod) + seq.pulse(t, duration, e.amplitude*scaling, sinewave) + elif isinstance(e, PhaseShift): + t = iround(e.time + seg_start) + e.phase_shift + raise Exception('Phase shift not supported for AWG channel') + elif isinstance(e, custom_pulse_element): + t = iround(e.start + seg_start) + duration = iround(e.stop - e.start) + seq.custom_pulse(t, duration, scaling, e) + else: + raise Exception('Unknown pulse element {type(e)}') + + t_end = seg_render.t_end + t_offset + seq.set_offset(t_end, 0, 0.0) + + compensation_ns = round(job.upload_info.dc_compensation_duration_ns) + if job.neutralize and compensation_ns > 0 and channel_info.dc_compensation: + compensation_voltage = -channel_info.integral / compensation_ns * 1e9 * scaling + job.upload_info.dc_compensation_voltages[channel_name] = compensation_voltage + logging.debug(f'DC compensation {channel_name}: {compensation_voltage:6.1f} mV {compensation_ns} ns') + seq.add_comment(f'DC compensation: {compensation_voltage:6.1f} mV {compensation_ns} ns') + seq.set_offset(t_end, compensation_ns, compensation_voltage) + seq.set_offset(t_end + compensation_ns, 0, 0.0) + + seq.close() + + def add_qubit_channel(self, job, qubit_channel): + segments = self.segments + + channel_name = qubit_channel.channel_name + + delays = [] + for i in range(2): + awg_channel_name = qubit_channel.iq_channel.IQ_out_channels[i].awg_channel_name + delays.append(self.channels[awg_channel_name].delay_ns) + if delays[0] != delays[1]: + raise Exception(f'I/Q Channel delays must be equal ({channel_name})') + t_offset = int((self.max_pre_start_ns + delays[0]) / 4) * 4 + + # TODO @@@ LO frequency can change during sweep + lo_freq = qubit_channel.iq_channel.LO + nco_freq = qubit_channel.reference_frequency-lo_freq + + + seq = IQSequenceBuilder(channel_name, self.program[channel_name], + nco_freq) + attenuation = 1.0 # TODO @@@ check if this is always true.. + scaling = 1/(attenuation * seq.max_output_voltage*1000) + + markers = self.get_markers_seq(job, channel_name) + seq.add_markers(markers) + + for iseg,(seg,seg_render) in enumerate(zip(job.sequence,segments)): + seg_start = seg_render.t_start + t_offset + + seg_ch = seg[channel_name] + data = seg_ch._get_data_all_at(job.index) + + entries = data.get_data_elements() + for e in entries: + if isinstance(e, OffsetRamp): + raise Exception('Voltage steps and ramps are not supported for IQ channel') + elif isinstance(e, IQ_data_single): + t = iround(e.start + seg_start) + duration = iround(e.stop - e.start) + amod, phmod = get_modulation(e.envelope, duration) + sinewave = SineWaveform(duration, e.frequency-lo_freq, + e.start_phase, amod, phmod) + seq.pulse(t, duration, e.amplitude*scaling, sinewave) + elif isinstance(e, PhaseShift): + t = iround(e.time + seg_start) + seq.shift_phase(t, e.phase_shift) + elif isinstance(e, custom_pulse_element): + raise Exception('Custom pulses are not supported for IQ channel') + else: + raise Exception('Unknown pulse element {type(e)}') + + # add final markers + seq.close() + + + def add_acquisition_channel(self, job, digitizer_channel): + channel_name = digitizer_channel.name + t_offset = int(self.max_pre_start_ns / 4) * 4 + acquisitions = [] + + seq = AcquisitionSequenceBuilder(channel_name, self.program[channel_name], job.n_rep) + + markers = self.get_markers_seq(job, channel_name) + seq.add_markers(markers) + + for name, value in job.schedule_params.items(): + if name.startswith('dig_trigger_') or name.startswith('dig_wait'): + time = value + t_offset + acquisitions.append(DigAcquisition(time)) + + for iseg, (seg, seg_render) in enumerate(zip(job.sequence, self.segments)): + seg_start = seg_render.t_start + t_offset + seg_ch = seg[channel_name] + acquisition_data = seg_ch._get_data_all_at(job.index).get_data() + for acquisition in acquisition_data: + if digitizer_channel.downsample_rate is not None: + period_ns = iround(1e8/digitizer_channel.downsample_rate) * 10 + n_cycles = int(acquisition.t_measure / period_ns) + t_measure = period_ns + else: + n_cycles = 1 + t_measure = acquisition.t_measure + t = iround(acquisition.start + seg_start) + acquisitions.append(DigAcquisition(t, + t_measure, + n=n_cycles, + threshold=acquisition.threshold)) + for acq in acquisitions: + seq.acquire(acq.start, acq.t_measure, acq.n) + + seq.close() + + def add_marker_seq(self, job, channel_name): + seq = SequenceBuilderBase(channel_name, self.program[channel_name]) + + markers = self.get_markers_seq(job, channel_name) + seq.add_markers(markers) + seq.close() + + def build(self, job): + job.upload_info = JobUploadInfo() + times = [] + times.append(['start', time.perf_counter()]) + + name = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + self.program = self.q1instrument.new_program(name) + job.program = self.program + + self.program._timeline.disable_update() # @@@ Yuk + + times.append(['init', time.perf_counter()]) + + self._integrate(job) + + times.append(['integrate', time.perf_counter()]) + + self._process_segments(job) + + times.append(['proc_seg', time.perf_counter()]) + + for channel_name in self.awg_voltage_channels: + self.add_awg_channel(job, channel_name) + + times.append(['awg', time.perf_counter()]) + + for qubit_channel in self.qubit_channels.values(): + self.add_qubit_channel(job, qubit_channel) + + times.append(['qubit', time.perf_counter()]) + + for dig_channel in self.digitizer_channels.values(): + self.add_acquisition_channel(job, dig_channel) + + times.append(['dig', time.perf_counter()]) + + for seq_name in self.marker_sequencers: + self.add_marker_seq(job, seq_name) + + times.append(['marker', time.perf_counter()]) + + self.program._timeline.enable_update() # @@@ Yuk + + times.append(['done', time.perf_counter()]) + + # NOTE: compilation is ~20% faster with listing=False, add_comments=False +# self.program.compile(add_comments=False, listing=False) + self.program.compile(listing=True) + + times.append(['compile', time.perf_counter()]) + + prev = None + for step,t in times: + if prev: + duration = (t - prev)*1000 + print(f'duration {step:10} {duration:9.3f} ms') + prev = t + + def get_max_compensation_time(self): + ''' + generate a DC compensation of the pulse. + As usuallly we put capacitors in between the AWG and the gate on the sample, you need to correct + for the fact that the low fequencies are not present in your transfer function. + This can be done simply by making the total integral of your function 0. + + Args: + sample_rate (float) : rate at which the AWG runs. + ''' + return max(self.get_compensation_time(channel_info) for channel_info in self.channels.values()) + + def get_compensation_time(self, channel_info): + ''' + return the minimal compensation time that is needed. + Returns: + compensation_time : minimal duration that is needed for the voltage compensation + ''' + if not channel_info.dc_compensation: + return 0 + + if channel_info.integral <= 0: + result = -channel_info.integral / channel_info.dc_compensation_max + else: + result = -channel_info.integral / channel_info.dc_compensation_min + return result + + diff --git a/pulse_lib/qblox/rendering.py b/pulse_lib/qblox/rendering.py new file mode 100644 index 0000000000000000000000000000000000000000..3245798395cc4154ebf3ae549d51161f93807497 --- /dev/null +++ b/pulse_lib/qblox/rendering.py @@ -0,0 +1,49 @@ +import numpy as np +from dataclasses import dataclass +from typing import Union + +def get_modulation(envelope_generator, duration): + if envelope_generator is None: + am_envelope = 1.0 + pm_envelope = 0.0 + else: + am_envelope = envelope_generator.get_AM_envelope(duration, 1.0) + pm_envelope = envelope_generator.get_PM_envelope(duration, 1.0) + return am_envelope, pm_envelope + + +@dataclass +class SineWaveform: + duration: int + frequency: float = None + phase: float = 0 + amod: Union[None, float, np.ndarray] = None + phmod: Union[None, float, np.ndarray] = None + + def __eq__(self, other): + res = (self.duration == other.duration + and self.frequency == other.frequency + and self.phase == other.phase + and np.all(self.amod == other.amod) + and np.all(self.phmod == other.phmod) + ) + return res + + def render(self, sample_rate=1e9): + total_phase = self.phase + self.phmod + t = np.arange(int(self.duration)) + return self.amod * np.sin(2*np.pi*self.frequency/sample_rate*t + total_phase) + + def render_iq(self, sample_rate=1e9): + total_phase = self.phase + self.phmod + t = np.arange(int(self.duration)) + cycles = 2*np.pi*self.frequency/sample_rate*t + total_phase + return (self.amod * np.cos(cycles), + self.amod * np.sin(cycles)) + +def render_custom_pulse(custom_pulse, scaling, sample_rate=1e9): + duration = custom_pulse.stop - custom_pulse.start + data = custom_pulse.func(duration, sample_rate, + custom_pulse.amplitude, **custom_pulse.kwargs) + return data * scaling + diff --git a/pulse_lib/segments/data_classes/data_pulse.py b/pulse_lib/segments/data_classes/data_pulse.py index 9d218d29f1e6aa50e0d929fd2f7afabd067beab5..aff6194b1ffc9229c8ecfa1b4226b15300b732ed 100644 --- a/pulse_lib/segments/data_classes/data_pulse.py +++ b/pulse_lib/segments/data_classes/data_pulse.py @@ -10,7 +10,7 @@ from typing import Any, Dict, Callable, List from pulse_lib.segments.utility.rounding import iround from pulse_lib.segments.data_classes.data_generic import parent_data -from pulse_lib.segments.data_classes.data_IQ import envelope_generator +from pulse_lib.segments.data_classes.data_IQ import envelope_generator, IQ_data_single total_pulse_deltas = 0 @@ -109,6 +109,21 @@ class PhaseShift: phase_shift: float channel_name: str + @property + def start(self): + return self.time + +@dataclass +class OffsetRamp: + time: float + duration: float # time till next OffsetRamp + v_start: float + v_stop: float + + @property + def start(self): + return self.time + # keep till end: start = np.inf # slicing: # consolidate all in slice on `end`, keep `inf`. delta_new = sum(p.delta for p in slice), ... @@ -551,6 +566,7 @@ class pulse_data(parent_data): steps = np.zeros(n) ramps = np.zeros(n) amplitudes = np.zeros(n) + amplitudes_end = np.zeros(n) for i,delta in enumerate(self.pulse_deltas): times[i] = delta.time steps[i] = delta.step @@ -561,7 +577,7 @@ class pulse_data(parent_data): ramps = np.cumsum(ramps) amplitudes[1:] = ramps[:-1] * intervals[:-1] amplitudes = np.cumsum(amplitudes) + np.cumsum(steps) - amplitudes_end = amplitudes - steps + amplitudes_end[:-1] = amplitudes[1:] - steps[1:] # logging.debug(f'points: {list(zip(times, amplitudes))}') self._times = times self._intervals = intervals @@ -583,7 +599,7 @@ class pulse_data(parent_data): integrated_value = 0 if len(self.pulse_deltas) > 0: - integrated_value = 0.5*np.dot((self._amplitudes[:-1] + self._amplitudes_end[1:]), + integrated_value = 0.5*np.dot((self._amplitudes[:-1] + self._amplitudes_end[:-1]), self._intervals[:-1]) for custom_pulse in self.custom_pulse_data: @@ -593,6 +609,28 @@ class pulse_data(parent_data): return integrated_value + def get_data_elements(self): + def typeorder(obj): + if isinstance(obj, PhaseShift): + return 0.1 + if isinstance(obj, OffsetRamp): + return 0.2 + if isinstance(obj, IQ_data_single): + return 0.3 + if isinstance(obj, custom_pulse_element): + return 0.4 + + elements = [] + self._pre_process() + for time, duration, v_start, v_stop in zip(self._times, self._intervals, + self._amplitudes, self._amplitudes_end): + elements.append(OffsetRamp(time, duration, v_start, v_stop)) + elements += self.custom_pulse_data + elements += self.MW_pulse_data + elements += self.phase_shifts + # Sort on rounded time, next: PhaseShift,Offset,MW_pulse,custom + elements.sort(key=lambda p:(int(p.start+0.5)+typeorder(p))) + return elements def _render_custom_pulse(self, custom_pulse, sample_rate): duration = custom_pulse.stop - custom_pulse.start @@ -622,7 +660,7 @@ class pulse_data(parent_data): pt1 = t_pt[i+1] if pt0 != pt1: if self._ramps[i] != 0: - wvf[pt0:pt1] = np.linspace(self._amplitudes[i], self._amplitudes_end[i+1], pt1-pt0+1)[:-1] + wvf[pt0:pt1] = np.linspace(self._amplitudes[i], self._amplitudes_end[i], pt1-pt0+1)[:-1] else: wvf[pt0:pt1] = self._amplitudes[i] @@ -791,7 +829,7 @@ class pulse_data(parent_data): start = self._times[i] stop = self._times[i+1] v_start = self._amplitudes[i] - v_stop = self._amplitudes_end[i+1] + v_stop = self._amplitudes_end[i] if stop == np.inf: stop = self._end_time if stop - start < 1 or (v_start == 0 and v_stop == 0):