def init_plot(self): plot = ManualPlotter("CR" + str.lower(self.cal_type.name) + "Fit", x_label=str.lower(self.cal_type.name), y_label='$<Z_{' + self.qubit_names[1] + '}>$') plot.add_data_trace("Data 0") plot.add_fit_trace("Fit 0") plot.add_data_trace("Data 1") plot.add_fit_trace("Fit 1") return plot
def init_plots(self): self.re_plot = ManualPlotter("Fidelity - Real", x_label='Bins', y_label='Real Quadrature') self.im_plot = ManualPlotter("Fidelity - Imag", x_label='Bins', y_label='Imag Quadrature') self.re_plot.add_trace("Excited", matplotlib_kwargs={ 'color': 'r', 'linestyle': '-', 'linewidth': 2 }) self.re_plot.add_trace("Excited Gaussian Fit", matplotlib_kwargs={ 'color': 'r', 'linestyle': '--', 'linewidth': 2 }) self.re_plot.add_trace("Ground", matplotlib_kwargs={ 'color': 'b', 'linestyle': '-', 'linewidth': 2 }) self.re_plot.add_trace("Ground Gaussian Fit", matplotlib_kwargs={ 'color': 'b', 'linestyle': '--', 'linewidth': 2 }) self.im_plot.add_trace("Excited", matplotlib_kwargs={ 'color': 'r', 'linestyle': '-', 'linewidth': 2 }) self.im_plot.add_trace("Excited Gaussian Fit", matplotlib_kwargs={ 'color': 'r', 'linestyle': '--', 'linewidth': 2 }) self.im_plot.add_trace("Ground", matplotlib_kwargs={ 'color': 'b', 'linestyle': '-', 'linewidth': 2 }) self.im_plot.add_trace("Ground Gaussian Fit", matplotlib_kwargs={ 'color': 'b', 'linestyle': '--', 'linewidth': 2 }) self.add_manual_plotter(self.re_plot) self.add_manual_plotter(self.im_plot)
def init_plot(self): plot = ManualPlotter("Ramsey Fit", x_label='Time (us)', y_label='Amplitude (Arb. Units)') plot.add_data_trace("Data") plot.add_fit_trace("Fit") return plot
def init_plot(self): plot = ManualPlotter("Qubit Search", x_label='Frequency (GHz)', y_label='Amplitude (Arb. Units)') plot.add_data_trace("Data") plot.add_fit_trace("Fit") return plot
def init_plots(self): """Initialize manual plotters for the mixer calibration. Three plot tabs open, one for I,Q offset, one for phase skew, one for amplitude imbalance. """ self.plt1 = ManualPlotter(name="Mixer offset calibration", x_label='{} {} offset (V)'.format( self.channel, self.mixer), y_label='Power (dBm)') self.plt1.add_data_trace("I-offset", {'color': 'C1'}) self.plt1.add_data_trace("Q-offset", {'color': 'C2'}) self.plt1.add_fit_trace("Fit I-offset", {'color': 'C1'}) #TODO: fix axis labels self.plt1.add_fit_trace("Fit Q-offset", {'color': 'C2'}) self.plt2 = ManualPlotter(name="Mixer amp calibration", x_label='{} {} amplitude (V)'.format( self.channel, self.mixer), y_label='Power (dBm)') self.plt2.add_data_trace("amplitude_factor", {'color': 'C4'}) self.plt2.add_fit_trace("Fit amplitude_factor", {'color': 'C4'}) self.plt3 = ManualPlotter(name="Mixer phase calibration", x_label='{} {} phase (rad)'.format( self.channel, self.mixer), y_label='Power (dBm)') self.plt3.add_data_trace("phase_skew", {'color': 'C3'}) self.plt3.add_fit_trace("Fit phase_skew", {'color': 'C3'}) self.plotters = [self.plt1, self.plt2, self.plt3] return self.plotters
class MixerCalibration(Calibration): """A calibration experiment that determines the optimal I and Q offsets, amplitude imbalance and phase imbalance for single-sideband modulation of a mixer. Please see Analog Devices Application Note AN-1039 for an explanation of the optimization of a mixer for maximum sideband supression. """ MIN_OFFSET = -0.4 MAX_OFFSET = 0.4 MIN_AMPLITUDE = 0.2 MAX_AMPLITUDE = 1.5 MIN_PHASE = -0.3 MAX_PHASE = 0.3 def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase", offset_range=(-0.2, 0.2), amp_range=(0.4, 0.8), phase_range=(-np.pi / 2, np.pi / 2), nsteps=101, use_fit=True, plot=True): """A mixer calibration for a specific LogicalChannel and mixer. Args: channel: The Auspex LogicalChannel for which to calibrate the mixer. spectrum_analyzer: The spectrum analyzer instrument to use for calibration. mixer (optional): 'control' or 'measure', the corresponding mixer to calibrate. Defaults to 'control'. first_cal (optional): 'phase' or 'amplitude'. Calibrate the phase skew or amplitude imbalance first. Defaults to 'phase'. offset_range (optional): I and Q offset range to sweep over. Defaults to (-0.2, 0.2). amp_range (optional): Amplitude imbalance range to sweep over. Defaults to (0.4, 0.8). phase_range (optional): Phase range to sweep over. Defaults to (-pi/2, pi/2). nsteps (optional): Number of points to sweep over for each calibraton. use_fit (optional): If true, find power minumum using a fit; otherwise use minimum value from data. Defaults to True. plot (optional): Plot the calibration as it happens. """ self.channel = channel self.spectrum_analyzer = spectrum_analyzer self.mixer = mixer self.do_plotting = plot self.first_cal = first_cal self.offset_range = offset_range self.amp_range = amp_range self.phase_range = phase_range self.nsteps = nsteps self.use_fit = use_fit super(MixerCalibration, self).__init__() def init_plots(self): """Initialize manual plotters for the mixer calibration. Three plot tabs open, one for I,Q offset, one for phase skew, one for amplitude imbalance. """ self.plt1 = ManualPlotter(name="Mixer offset calibration", x_label='{} {} offset (V)'.format( self.channel, self.mixer), y_label='Power (dBm)') self.plt1.add_data_trace("I-offset", {'color': 'C1'}) self.plt1.add_data_trace("Q-offset", {'color': 'C2'}) self.plt1.add_fit_trace("Fit I-offset", {'color': 'C1'}) #TODO: fix axis labels self.plt1.add_fit_trace("Fit Q-offset", {'color': 'C2'}) self.plt2 = ManualPlotter(name="Mixer amp calibration", x_label='{} {} amplitude (V)'.format( self.channel, self.mixer), y_label='Power (dBm)') self.plt2.add_data_trace("amplitude_factor", {'color': 'C4'}) self.plt2.add_fit_trace("Fit amplitude_factor", {'color': 'C4'}) self.plt3 = ManualPlotter(name="Mixer phase calibration", x_label='{} {} phase (rad)'.format( self.channel, self.mixer), y_label='Power (dBm)') self.plt3.add_data_trace("phase_skew", {'color': 'C3'}) self.plt3.add_fit_trace("Fit phase_skew", {'color': 'C3'}) self.plotters = [self.plt1, self.plt2, self.plt3] return self.plotters def _calibrate(self): """Run the actual calibration routine. """ offset_pts = np.linspace(self.offset_range[0], self.offset_range[1], self.nsteps) amp_pts = np.linspace(self.amp_range[0], self.amp_range[1], self.nsteps) phase_pts = np.linspace(self.phase_range[0], self.phase_range[1], self.nsteps) first_cal = self.first_cal config_dict = { "I_offset": 0.0, "Q_offset": 0.0, "phase_skew": 0.0, "amplitude_factor": 1.0, "sideband_modulation": False } I1_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:], use_fit=self.use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I1_amps) self.plt1["Fit I-offset"] = (xpts, ypts) logger.info("Found first pass I offset of {}.".format(I1_offset)) config_dict['I_offset'] = I1_offset Q1_amps = self.run_sweeps("Q_offset", offset_pts, config_dict) try: Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:], use_fit=self.use_fit) except: raise ValueError("Could not find null offset") self.plt1["Q-offset"] = (offset_pts, Q1_amps) self.plt1["Fit Q-offset"] = (xpts, ypts) logger.info("Found first pass Q offset of {}.".format(Q1_offset)) config_dict['Q_offset'] = Q1_offset I2_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:], use_fit=self.use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I2_amps) self.plt1["Fit I-offset"] = (xpts, ypts) logger.info("Found second pass I offset of {}.".format(I2_offset)) config_dict['I_offset'] = I2_offset #this is a bit hacky but OK... cals = {"phase": "phase_skew", "amplitude": "amplitude_factor"} cal_pts = {"phase": phase_pts, "amplitude": amp_pts} correct_plotter = {"phase": self.plt3, "amplitude": self.plt2} cal_defaults = {"phase": 0.0, "amplitude": 1.0} if first_cal not in cals.keys(): raise ValueError( "First calibration should be one of ('phase, amplitude'). Instead got {}" .format(first_cal)) second_cal = list(set(cals.keys()).difference({ first_cal, }))[0] config_dict['sideband_modulation'] = True amps1 = self.run_sweeps(cals[first_cal], cal_pts[first_cal], config_dict) try: offset1, xpts, ypts = find_null_offset( cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal], use_fit=self.use_fit) except: raise ValueError("Could not find null offset") correct_plotter[first_cal][cals[first_cal]] = (cal_pts[first_cal], amps1) correct_plotter[first_cal]["Fit " + cals[first_cal]] = (xpts, ypts) logger.info("Found {} of {}.".format( str.replace(cals[first_cal], '_', ' '), offset1)) config_dict[cals[first_cal]] = offset1 amps2 = self.run_sweeps(cals[second_cal], cal_pts[second_cal], config_dict) try: offset2, xpts, ypts = find_null_offset( cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal], use_fit=self.use_fit) except: raise ValueError("Could not find null offset") correct_plotter[second_cal][cals[second_cal]] = (cal_pts[second_cal], amps2) correct_plotter[second_cal]["Fit " + cals[second_cal]] = (xpts, ypts) logger.info("Found {} of {}.".format( str.replace(cals[first_cal], '_', ' '), offset2)) config_dict[cals[second_cal]] = offset2 # if write_to_file: # mce.write_to_file() logger.info( ("Mixer calibration: I offset = {}, Q offset = {}, " "Amplitude Imbalance = {}, Phase Skew = {}").format( config_dict["I_offset"], config_dict["Q_offset"], config_dict["amplitude_factor"], config_dict["phase_skew"])) assert config_dict["I_offset"] > self.MIN_OFFSET and config_dict[ "I_offset"] < self.MAX_OFFSET, "I_offset looks suspicious." assert config_dict["Q_offset"] > self.MIN_OFFSET and config_dict[ "Q_offset"] < self.MAX_OFFSET, "Q_offset looks suspicious." assert config_dict[ "amplitude_factor"] > self.MIN_AMPLITUDE and config_dict[ "amplitude_factor"] < self.MAX_AMPLITUDE, "amplitude_factor looks suspicious." assert config_dict["phase_skew"] > self.MIN_PHASE and config_dict[ "phase_skew"] < self.MAX_PHASE, "phase_skew looks suspicious." self.succeeded = True self.config_dict = config_dict def update_settings(self): """Update the channel library settings to those found by calibration routine. """ self.exp._phys_chan.amp_factor = self.config_dict["amplitude_factor"] self.exp._phys_chan.phase_skew = self.config_dict["phase_skew"] self.exp._phys_chan.I_channel_offset = self.config_dict["I_offset"] self.exp._phys_chan.Q_channel_offset = self.config_dict["Q_offset"] def run_sweeps(self, sweep_parameter, pts, config_dict): """Run the calibration sweeps. """ self.exp = MixerCalibrationExperiment(self.channel, self.spectrum_analyzer, config_dict, mixer=self.mixer) self.exp.add_sweep(getattr(self.exp, sweep_parameter), pts) self.exp.run_sweeps() return self.exp.buff.get_data()[0]
class SingleShotFidelityExperiment(QubitExperiment): """Experiment to measure single-shot measurement fidelity of a qubit.""" def __init__(self, qubit_names, num_shots=10000, expname=None, meta_file=None, save_data=False, optimize=False, set_threshold=True, stream_type='Raw', **kwargs): """Create a single shot fidelity measurement experiment. Assumes that there is a single shot measurement filter in the filter pipeline. Arguments: qubit_names: Names of the qubits to be measured. (str) num_shots: Total number of 0 and 1 measurements used to reconstruct fidelity histograms (int) expname: Experiment name for data saving. meta_file: Meta file for defining custom single-shot fidelity experiment. save_data: If true, will save the raw or demodulated data. optimize: if true, will set the swept parameters to their optimum values""" super(SingleShotFidelityExperiment, self).__init__() self.qubit_names = qubit_names if isinstance(qubit_names, list) else [qubit_names] self.qubit = [QubitFactory(qubit_name) for qubit_name in qubit_names] if isinstance( qubit_names, list) else QubitFactory(qubit_names) # make a copy of the settings to restore default self.saved_settings = config.load_meas_file(config.meas_file) self.settings = deepcopy(self.saved_settings) self.save_data = save_data self.calibration = True self.optimize = optimize self.set_threshold = True self.name = expname self.cw_mode = False self.repeats = num_shots self.ss_stream_type = stream_type if meta_file is None: meta_file = SingleShot(self.qubit) self._squash_round_robins() QubitExpFactory.load_meta_info(self, meta_file) QubitExpFactory.load_instruments(self) QubitExpFactory.load_qubits(self) QubitExpFactory.load_filters(self) if 'sweeps' in self.settings: QubitExpFactory.load_parameter_sweeps(experiment) self.ssf = self.find_single_shot_filter() self.leave_plot_server_open = True def run_sweeps(self): #For now, only update histograms if we don't have a parameter sweep. if not self.sweeper.axes: self.init_plots() self.add_manual_plotter(self.re_plot) self.add_manual_plotter(self.im_plot) else: if any([ x.save_kernel.value for x in self.filters.values() if type(x) is SingleShotMeasurement ]): logger.warning( "Kernel saving is not supported if you have parameter sweeps!" ) super(SingleShotFidelityExperiment, self).run_sweeps() if not self.sweeper.axes: self._update_histogram_plots() if hasattr(self, 'extra_plot_server'): try: self.extra_plot_server.stop() except: pass if self.set_threshold: self.update_threshold() if self.sweeper.axes and self.optimize: #select the buffers/writers whose sources are singleshot filters fid_buffers = [ buff for buff in self.buffers if self.settings['filters'][ buff.name]['source'].strip().split()[1] == 'fidelity' ] if not fid_buffers: raise NameError( "Please connect a buffer to the single-shot filter output in order to optimize fidelity." ) #set sweep parameters to the values that maximize fidelity. Then update the saved_settings with the new values for buff in fid_buffers: dataset, descriptor = buff.get_data(), buff.get_descriptor() opt_ind = np.argmax(dataset['Data']) for k, axis in enumerate(self.sweeper.axes): instr_tree = axis.parameter.instr_tree param_key = self.saved_settings['instruments'] for key in instr_tree[:-1]: # go through the tree param_key = param_key[key] opt_value = float(dataset[axis.name][opt_ind]) # special case to set APS ch12 amplitudes if instr_tree[-1] == 'amplitude' and instr_tree[ -2] in self.saved_settings['instruments'].keys(): param_key['tx_channels']['12']['1'][ 'amplitude'] = round(float(opt_value), 5) param_key['tx_channels']['12']['2'][ 'amplitude'] = round(float(opt_value), 5) else: param_key[instr_tree[-1]] = opt_value logger.info("Set{} to {}.".format( " ".join(str(x) for x in instr_tree), opt_value)) if self.set_threshold or ( self.sweeper.axes and self.optimize): # update settings if something was calibrated config.dump_meas_file(self.saved_settings, config.meas_file) def _update_histogram_plots(self): pdf_data = self.get_results() self.re_plot.set_data("Ground", pdf_data["I Bins"], pdf_data["Ground I PDF"]) self.re_plot.set_data("Ground Gaussian Fit", pdf_data["I Bins"], pdf_data["Ground I Gaussian PDF"]) self.re_plot.set_data("Excited", pdf_data["I Bins"], pdf_data["Excited I PDF"]) self.re_plot.set_data("Excited Gaussian Fit", pdf_data["I Bins"], pdf_data["Excited I Gaussian PDF"]) self.im_plot.set_data("Ground", pdf_data["Q Bins"], pdf_data["Ground Q PDF"]) self.im_plot.set_data("Ground Gaussian Fit", pdf_data["Q Bins"], pdf_data["Ground Q Gaussian PDF"]) self.im_plot.set_data("Excited", pdf_data["Q Bins"], pdf_data["Excited Q PDF"]) self.im_plot.set_data("Excited Gaussian Fit", pdf_data["Q Bins"], pdf_data["Excited Q Gaussian PDF"]) def update_threshold(self): if 'I Threshold' in self.ssf[0].pdf_data: self.saved_settings['filters'][self.qubit_to_stream_sel[ self.qubit.label]]['threshold'] = round( float(self.ssf[0].pdf_data['I Threshold']), 6) def init_plots(self): self.re_plot = ManualPlotter("Fidelity - Real", x_label='Bins', y_label='Real Quadrature') self.im_plot = ManualPlotter("Fidelity - Imag", x_label='Bins', y_label='Imag Quadrature') self.re_plot.add_trace("Excited", matplotlib_kwargs={ 'color': 'r', 'linestyle': '-', 'linewidth': 2 }) self.re_plot.add_trace("Excited Gaussian Fit", matplotlib_kwargs={ 'color': 'r', 'linestyle': '--', 'linewidth': 2 }) self.re_plot.add_trace("Ground", matplotlib_kwargs={ 'color': 'b', 'linestyle': '-', 'linewidth': 2 }) self.re_plot.add_trace("Ground Gaussian Fit", matplotlib_kwargs={ 'color': 'b', 'linestyle': '--', 'linewidth': 2 }) self.im_plot.add_trace("Excited", matplotlib_kwargs={ 'color': 'r', 'linestyle': '-', 'linewidth': 2 }) self.im_plot.add_trace("Excited Gaussian Fit", matplotlib_kwargs={ 'color': 'r', 'linestyle': '--', 'linewidth': 2 }) self.im_plot.add_trace("Ground", matplotlib_kwargs={ 'color': 'b', 'linestyle': '-', 'linewidth': 2 }) self.im_plot.add_trace("Ground Gaussian Fit", matplotlib_kwargs={ 'color': 'b', 'linestyle': '--', 'linewidth': 2 }) def _squash_round_robins(self): """Make it so that the round robins are set to 1.""" digitizers = [ _ for _ in self.settings['instruments'].keys() if 'nbr_round_robins' in self.settings['instruments'][_].keys() ] for d in digitizers: logger.info( "Set digitizer {} round robins to 1 for single shot experiment." .format(d)) self.settings['instruments'][d]['nbr_round_robins'] = 1 # disable averagers for _, f in self.settings['filters'].items(): if f['type'] == 'Averager': f['enabled'] = False def find_single_shot_filter(self): """Make sure there is one single shot measurement filter in the pipeline.""" ssf = [ x for x in self.filters.values() if type(x) is SingleShotMeasurement ] if len(ssf) > 1: raise NotImplementedError( "Single shot fidelity for more than one qubit is not yet implemented." ) elif len(ssf) == 0: raise NameError( "There do not appear to be any single-shot measurements in your filter pipeline. Please add one!" ) return ssf def get_results(self): """Get the PDF and fidelity numbers from the filters. Returns a dictionary of PDF data with the filter names as keys.""" ssf = self.find_single_shot_filter() if len(ssf) > 1: # not implemented try: return {x.name: x.pdf_data for x in ssf} except AttributeError: raise AttributeError( "Could not find single shot PDF data in results. Did you run the sweeps?" ) else: try: return ssf[0].pdf_data except AttributeError: raise AttributeError( "Could not find single shot PDF data in results. Did you run the sweeps?" )
pass async def run(self): r = np.power(self.amplitude.value, 2) + 0.1 * np.random.random() await self.voltage.push(r) await asyncio.sleep(0.01) if __name__ == '__main__': exp = TestExperiment() exp.leave_plot_server_open = True # Create the plotter and the actual traces we'll need plt = ManualPlotter("Manual Plotting Test", x_label='X Thing', y_label='Y Thing') plt.add_data_trace("Example Data") plt.add_fit_trace("Example Fit") buff = DataBuffer() edges = [(exp.voltage, buff.sink)] exp.set_graph(edges) # Create a plotter callback def plot_me(plot): ys = buff.get_data()['voltage'] xs = buff.descriptor.axes[0].points plot["Example Data"] = (xs, ys) plot["Example Fit"] = (xs, ys + 0.1)
def calibrate_mixer(qubit, mixer="control", first_cal="phase", write_to_file=True, offset_range=(-0.2, 0.2), amp_range=(0.6, 1.4), phase_range=(-np.pi / 6, np.pi / 6), nsteps=51): """Calibrates IQ mixer offset, amplitude imbalanace, and phase skew. See Analog Devices Application note AN-1039. Parses instrument connectivity from the experiment settings YAML. Arguments: qubit: Qubit identifier string. mixer: One of ("control", "measure") to select which IQ channel is calibrated. first_cal: One of ("phase", "amplitude") to select which adjustment is attempted first. You should pick whichever the particular mixer is most sensitive to. For example, a mixer with -40dBc sideband supression at 1 degree of phase skew and 0.1 dB amplitude imbalance should calibrate the phase first. """ spm = auspex.config.single_plotter_mode auspex.config.single_plotter_mode = True def sweep_offset(name, pts): mce.clear_sweeps() mce.add_sweep(getattr(mce, name), pts) mce.keep_instruments_connected = True mce.run_sweeps() offset_pts = np.linspace(offset_range[0], offset_range[1], nsteps) amp_pts = np.linspace(amp_range[0], amp_range[1], nsteps) phase_pts = np.linspace(phase_range[0], phase_range[1], nsteps) buff = DataBuffer() plt = ManualPlotter(name="Mixer offset calibration", x_label='{} {} offset (V)'.format(qubit, mixer), y_label='Power (dBm)') plt.add_data_trace("I-offset", {'color': 'C1'}) plt.add_data_trace("Q-offset", {'color': 'C2'}) plt.add_fit_trace("Fit I-offset", {'color': 'C1'}) #TODO: fix axis labels plt.add_fit_trace("Fit Q-offset", {'color': 'C2'}) plt2 = ManualPlotter(name="Mixer amp/phase calibration", x_label='{} {} amplitude (V)/phase (rad)'.format( qubit, mixer), y_label='Power (dBm)') plt2.add_data_trace("phase_skew", {'color': 'C3'}) plt2.add_data_trace("amplitude_factor", {'color': 'C4'}) plt2.add_fit_trace("Fit phase_skew", {'color': 'C3'}) plt2.add_fit_trace("Fit amplitude_factor", {'color': 'C4'}) mce = MixerCalibrationExperiment(qubit, mixer=mixer) mce.add_manual_plotter(plt) mce.add_manual_plotter(plt2) mce.leave_plot_server_open = True QubitExpFactory.load_instruments(mce, mce.instruments_to_enable) edges = [(mce.amplitude, buff.sink)] mce.set_graph(edges) sweep_offset("I_offset", offset_pts) I1_amps = np.array([x[1] for x in buff.get_data()]) try: I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:]) except: mce.extra_plot_server.stop() return plt["I-offset"] = (offset_pts, I1_amps) plt["Fit I-offset"] = (xpts, ypts) logger.info("Found first pass I offset of {}.".format(I1_offset)) mce.I_offset.value = I1_offset mce.first_exp = False # slight misnomer to indicate that no new plot is needed sweep_offset("Q_offset", offset_pts) Q1_amps = np.array([x[1] for x in buff.get_data()]) try: Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:]) except: mce.extra_plot_server.stop() return plt["Q-offset"] = (offset_pts, Q1_amps) plt["Fit Q-offset"] = (xpts, ypts) logger.info("Found first pass Q offset of {}.".format(Q1_offset)) mce.Q_offset.value = Q1_offset sweep_offset("I_offset", offset_pts) I2_amps = np.array([x[1] for x in buff.get_data()]) try: I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:]) except: mce.extra_plot_server.stop() return plt["I-offset"] = (offset_pts, I2_amps) plt["Fit I-offset"] = (xpts, ypts) logger.info("Found second pass I offset of {}.".format(I2_offset)) mce.I_offset.value = I2_offset #this is a bit hacky but OK... cals = {"phase": "phase_skew", "amplitude": "amplitude_factor"} cal_pts = {"phase": phase_pts, "amplitude": amp_pts} cal_defaults = {"phase": 0.0, "amplitude": 1.0} if first_cal not in cals.keys(): raise ValueError( "First calibration should be one of ('phase, amplitude'). Instead got {}" .format(first_cal)) second_cal = list(set(cals.keys()).difference({ first_cal, }))[0] mce.sideband_modulation = True sweep_offset(cals[first_cal], cal_pts[first_cal]) amps1 = np.array([x[1] for x in buff.get_data()]) try: offset1, xpts, ypts = find_null_offset( cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal]) except: mce.extra_plot_server.stop() return plt2[cals[first_cal]] = (cal_pts[first_cal], amps1) plt2["Fit " + cals[first_cal]] = (xpts, ypts) logger.info("Found {} of {}.".format( str.replace(cals[first_cal], '_', ' '), offset1)) getattr(mce, cals[first_cal]).value = offset1 sweep_offset(cals[second_cal], cal_pts[second_cal]) amps2 = np.array([x[1] for x in buff.get_data()]) try: offset2, xpts, ypts = find_null_offset( cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal]) except: mce.extra_plot_server.stop() return plt2[cals[second_cal]] = (cal_pts[second_cal], amps2) plt2["Fit " + cals[second_cal]] = (xpts, ypts) logger.info("Found {} of {}.".format( str.replace(cals[first_cal], '_', ' '), offset2)) getattr(mce, cals[second_cal]).value = offset2 mce.disconnect_instruments() try: mce.extra_plot_server.stop() except: logger.info('Mixer plot server was not successfully stopped.') if write_to_file: mce.write_to_file() logger.info(("Mixer calibration: I offset = {}, Q offset = {}, " "Amplitude Imbalance = {}, Phase Skew = {}").format( mce.I_offset.value, mce.Q_offset.value, mce.amplitude_factor.value, mce.phase_skew.value)) auspex.config.single_plotter_mode = spm
class SingleShotFidelityExperiment(QubitExperiment): """Experiment to measure single-shot measurement fidelity of a qubit. Args: qubit: qubit object output_nodes (optional): the output node of the filter pipeline to use for single-shot readout. The default is choses, if single output. meta_file (string, optional): path to the QGL sequence meta_file. Default to standard SingleShot sequence optimize (bool, optional): if True and a qubit_sweep is added, set the parameter corresponding to the maximum measured fidelity at the end of the sweep """ def __init__(self, qubit, sample_name=None, output_nodes=None, meta_file=None, optimize=True, set_threshold=True, **kwargs): self.pdf_data = [] self.qubit = qubit self.optimize = optimize self.set_threshold = set_threshold if meta_file: self.meta_file = meta_file else: self.meta_file = self._single_shot_sequence(self.qubit) super(SingleShotFidelityExperiment, self).__init__(self.meta_file, **kwargs) if not sample_name: sample_name = self.qubit.label if not bbndb.get_cl_session(): raise Exception("Attempting to load Calibrations database, \ but no database session is open! Have the ChannelLibrary and PipelineManager been created?" ) existing_samples = list(bbndb.get_cl_session().query( bbndb.calibration.Sample).filter_by(name=sample_name).all()) if len(existing_samples) == 0: logger.info("Creating a new sample in the calibration database.") self.sample = bbndb.calibration.Sample(name=sample_name) bbndb.get_cl_session().add(self.sample) elif len(existing_samples) == 1: self.sample = existing_samples[0] else: raise Exception( "Multiple samples found in calibration database with the same name! How?" ) def guess_output_nodes(self, graph): output_nodes = [] for qubit in self.qubits: ds = nx.descendants(graph, self.qubit_proxies[qubit.label]) outputs = [ d for d in ds if isinstance(d, (bbndb.auspex.Write, bbndb.auspex.Buffer)) ] if len(outputs) != 1: raise Exception( f"More than one output node found for {qubit}, please explicitly define output node using output_nodes argument." ) output_nodes.append(outputs[0]) return output_nodes def _single_shot_sequence(self, qubit): seqs = create_cal_seqs((qubit, ), 1) return compile_to_hardware(seqs, 'SingleShot/SingleShot') def init_plots(self): self.re_plot = ManualPlotter("Fidelity - Real", x_label='Bins', y_label='Real Quadrature') self.im_plot = ManualPlotter("Fidelity - Imag", x_label='Bins', y_label='Imag Quadrature') self.re_plot.add_trace("Excited", matplotlib_kwargs={ 'color': 'r', 'linestyle': '-', 'linewidth': 2 }) self.re_plot.add_trace("Excited Gaussian Fit", matplotlib_kwargs={ 'color': 'r', 'linestyle': '--', 'linewidth': 2 }) self.re_plot.add_trace("Ground", matplotlib_kwargs={ 'color': 'b', 'linestyle': '-', 'linewidth': 2 }) self.re_plot.add_trace("Ground Gaussian Fit", matplotlib_kwargs={ 'color': 'b', 'linestyle': '--', 'linewidth': 2 }) self.im_plot.add_trace("Excited", matplotlib_kwargs={ 'color': 'r', 'linestyle': '-', 'linewidth': 2 }) self.im_plot.add_trace("Excited Gaussian Fit", matplotlib_kwargs={ 'color': 'r', 'linestyle': '--', 'linewidth': 2 }) self.im_plot.add_trace("Ground", matplotlib_kwargs={ 'color': 'b', 'linestyle': '-', 'linewidth': 2 }) self.im_plot.add_trace("Ground Gaussian Fit", matplotlib_kwargs={ 'color': 'b', 'linestyle': '--', 'linewidth': 2 }) self.add_manual_plotter(self.re_plot) self.add_manual_plotter(self.im_plot) def _update_histogram_plots(self): self.re_plot["Ground"] = (self.pdf_data[-1]["I Bins"], self.pdf_data[-1]["Ground I PDF"]) self.re_plot["Ground Gaussian Fit"] = ( self.pdf_data[-1]["I Bins"], self.pdf_data[-1]["Ground I Gaussian PDF"]) self.re_plot["Excited"] = (self.pdf_data[-1]["I Bins"], self.pdf_data[-1]["Excited I PDF"]) self.re_plot["Excited Gaussian Fit"] = ( self.pdf_data[-1]["I Bins"], self.pdf_data[-1]["Excited I Gaussian PDF"]) self.im_plot["Ground"] = (self.pdf_data[-1]["Q Bins"], self.pdf_data[-1]["Ground Q PDF"]) self.im_plot["Ground Gaussian Fit"] = ( self.pdf_data[-1]["Q Bins"], self.pdf_data[-1]["Ground Q Gaussian PDF"]) self.im_plot["Excited"] = (self.pdf_data[-1]["Q Bins"], self.pdf_data[-1]["Excited Q PDF"]) self.im_plot["Excited Gaussian Fit"] = ( self.pdf_data[-1]["Q Bins"], self.pdf_data[-1]["Excited Q Gaussian PDF"]) def run_sweeps(self): if not self.sweeper.axes: self.init_plots() self.start_manual_plotters() else: for f in self.filters: if isinstance(f, SingleShotMeasurement): f.save_kernel.value = False super(SingleShotFidelityExperiment, self).run_sweeps() self.get_results() if not self.sweeper.axes: self._update_histogram_plots() self.stop_manual_plotters() if self.set_threshold: self.stream_selectors[0].threshold = self.get_threshold()[0] if self.sample: c = bbndb.calibration.Calibration(value=self.get_fidelity()[0], sample=self.sample, name="Readout fid.", category="Readout") c.date = datetime.datetime.now() bbndb.get_cl_session().add(c) bbndb.get_cl_session().commit() elif self.optimize: fidelities = [f['Max I Fidelity'] for f in self.pdf_data] opt_ind = np.argmax(fidelities) for k, axis in enumerate(self.sweeper.axes): set_pair = axis.parameter.set_pair opt_value = axis.points[opt_ind] if set_pair[1] == 'amplitude' or set_pair[1] == "offset": # special case for APS chans param = [ c for c in self.chan_db.channels if c.label == set_pair[0] ][0] attr = 'amp_factor' if set_pair[ 1] == 'amplitude' else 'offset' setattr(param, f'I_channel_{attr}', opt_value) setattr(param, f'Q_channel_{attr}', opt_value) else: param = [ c for c in self.chan_db.all_instruments() if c.label == set_pair[0] ][0] setattr(param, set_pair[1], opt_value) logger.info( f'Set {set_pair[0]} {set_pair[1]} to optimum value {opt_value}' ) if self.set_threshold: self.stream_selectors[0].threshold = self.get_threshold( )[opt_ind] logger.info( f'Set threshold to {self.stream_selectors[0].threshold}') def find_single_shot_filter(self): """Make sure there is one single shot measurement filter in the pipeline.""" ssf = [x for x in self.filters if type(x) is SingleShotMeasurement] if len(ssf) > 1: raise NotImplementedError( "Single shot fidelity for more than one qubit is not yet implemented." ) elif len(ssf) == 0: raise NameError( "There do not appear to be any single-shot measurements in your filter pipeline. Please add one!" ) return ssf def get_fidelity(self): if not self.pdf_data: raise Exception( "Could not find single shot PDF data in results. Did you run the sweeps?" ) return [p["Max I Fidelity"] for p in self.pdf_data] def get_threshold(self): if not self.pdf_data: raise Exception( "Could not find single shot PDF data in results. Did you run the sweeps?" ) return [p["I Threshold"] for p in self.pdf_data] def get_results(self): """Get the PDF and fidelity numbers from the filters. Returns a dictionary of PDF data with the filter names as keys.""" if len(self.pdf_data) == 0: ssf = self.find_single_shot_filter() while True: try: self.pdf_data.append(ssf[0].pdf_data_queue.get(False)) except queue.Empty as e: break if len(self.pdf_data) == 0: raise Exception( "Could not find single shot PDF data in results. Did you run the sweeps?" )